手写 spring mvc 基于注解

微信扫一扫,分享到朋友圈

手写 spring mvc 基于注解

手写 spring mvc 基于注解

author: huifer

前置知识

  • 在 spring 中我们会有如下几个注解来帮助我们定义 web-mvc 的语义

    1. Controller
    2. Service
    3. RequestParam
    4. Autowired
    5. RequestMapping
  • 这些注解相比大家都使用过在这里就不具体展开描述了. 在后面的开发中我们再来细说

配置篇

  • web.xml 的配置
  • 在 web.xml 中我们需要配置

    1. servlet-class
    2. spring的配置(伪)
    3. url-pattern
  • spring 配置这里简化为一个包的扫描路径. component-scan
  • 配置详情
  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>HuiFer web application</display-name>
<servlet>
<servlet-name>HuiFer mvc</servlet-name>
<servlet-class>org.huifer.spring.servlet.v1.HFDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HuiFer mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
  • application.properties
scanPackage=org.huifer.spring

注解篇

  • HFAutowired
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HFAutowired {
String value() default "";
}
  • HFController
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HFController {
}
  • HFRequestMapping
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HFRequestMapping {
String name() default "";
}
  • HFRequestParam
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HFRequestParam {
String name() default "";
}
  • HFService
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HFService {
String name() default "";
}
  • 注解篇没什么可以多说的. 只是定义一些语义标记. 接下来就是具体的实现了

servlet 篇

  • 开发流程

    1. 读取web.xml中的配置,读取包扫描路径
    2. 根据包扫描路径加载类命.
    3. 实例化类.(service注解)

      1. 根据名称注入
      2. 根据类型注入
    4. 读取 requestMapping 注解

      1. key: url , value: method
    5. 执行http请求
  • 首先继承 javax.servlet.http.HttpServlet
  • 重写的方法

    1. init
    2. doPost
    3. doGet
  • 主要部分都在init方法中, 按照行为拆分如下几个方法

    1. doLoadConfig 读取配置
    2. doScan 进行包扫描
    3. instance 实例化
    4. autowired 注入
    5. initHandlerMapping url和method映射关系
  • 读取配置文件. 通过 getServletConfig
    获取 servlet 的配置, 并读取 resource 文件夹中的配置.
/**
* 从 web.xml 读取contextConfigLocation <br> 把 {@code classpath*:application.properties} 载入配置
*/
private void doLoadConfig() {
InputStream resourceAsStream = null;
try {
// 获取servlet的配置
ServletConfig servletConfig = this.getServletConfig();
String configInitParameter = servletConfig.getInitParameter(CONTEXT_CONFIG_LOCATION)
.replace("classpath*:", "");
// 读取配置文件
resourceAsStream = this.getClass().getClassLoader()
.getResourceAsStream(configInitParameter);
SPRING_CONTEXT_CONFIG.load(resourceAsStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resourceAsStream != null) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
  • 包扫描. 递归的扫描基本路径下的class. 放入类名字列表
/**
* 包扫描
*/
private void doScan(String scanPackage) {
// 类路径
URL resource = this.getClass().getClassLoader()
.getResource("/" + scanPackage.replaceAll("\\.", "/"));
File classPath = new File(resource.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScan(scanPackage + "." + file.getName());
}
else {
if (!file.getName().endsWith(".class")) {
continue;
}
else {
String className = (scanPackage + "." + file.getName()).replace(".class", "");
classNameList.add(className);
}
}
}
}
  • 实例化.

    • 那些类需要实例化. 带有 spring 注解的类需要初始化. 在spring里面是 @Component 这里做例子就直接写死了几个需要初始化的标记接口

      1. HFController
      2. HFService
    • 实例化后的存储

      1. Map 结构毋庸置疑

        • 在spring中我们注入有 byName 和 byType. 我们这个简单的ioc容器也会有

          • byName 的存储方式: key: beanName value: object
        • 通过反射方法 getInterfaces
          我们可以获取这个类实现了那些接口. 因此可以直接进行注入

          • byType 的存储方式: key: interfaceName value:Object
/**
* 实例化
*/
private void instance() {
if (this.classNameList.isEmpty()) {
return;
}
try {
for (String clazz : this.classNameList) {
Class<?> aClass = Class.forName(clazz);
// 1. 接口的实现类初始化
if (!aClass.isInterface()) {
System.out.println(clazz);
Object o = aClass.newInstance();
// 1. 带有注解的初始化
if (aClass.isAnnotationPresent(HFController.class)) {
IOC_NAME.put(aClass.getSimpleName(), o);
}
else if (aClass.isAnnotationPresent(HFService.class)) {
HFService annotation = aClass.getAnnotation(HFService.class);
// 名字注入
if (annotation.name().equals("")) {
IOC_NAME.put(aClass.getSimpleName(), o);
}
else {
IOC_NAME.put(annotation.name(), o);
}
// 类型注入
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
if (!IOC_NAME.containsKey(anInterface.getName())) {
IOC_NAME.put(anInterface.getName(), o);
}
}
}
else {
continue;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
  • 自动注入

    • 从ico容器中获取实例化的对象并且强制设置属性

      1. 那些字段需要呗强制设置? 带有 HFAutowired 注解的字段
private void autowired() {
if (IOC_NAME.isEmpty()) {
return;
}
for (Entry<String, Object> entry : IOC_NAME.entrySet()) {
try {
String k = entry.getKey();
Object v = entry.getValue();
Field[] declaredFields = v.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
// 是否又自动注入的注解
if (declaredField.isAnnotationPresent(HFAutowired.class)) {
HFAutowired annotation = declaredField.getAnnotation(HFAutowired.class);
String beanName = annotation.value().trim();
// byType 获取具体的实现类
if (beanName.equals("")) {
beanName = declaredField.getType().getName();
}
declaredField.setAccessible(true);
declaredField.set(v, IOC_NAME.get(beanName));
}
else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
  • 绑定url和执行方法

    • 先找到 HFController. 再找到 HFRequestMapping 反射获取所有 method 判断method是否有 HFRequestMapping. 最终组装, 类上面的HFRequestMapping和方法上的HFRequestMapping属性值拼接起来就是url
private void initHandlerMapping() {
if (IOC_NAME.isEmpty()) {
return;
}
for (Entry<String, Object> entry : IOC_NAME.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(HFController.class)) {
if (clazz.isAnnotationPresent(HFRequestMapping.class)) {
HFRequestMapping annotation = clazz.getAnnotation(HFRequestMapping.class);
String baseUri = annotation.name();
for (Method method : clazz.getMethods()) {
HFRequestMapping annotation1 = method.getAnnotation(HFRequestMapping.class);
if (annotation1 != null) {
String uri = ("/" + baseUri + "/" + annotation1.name()).replaceAll("/+", "/");
HandlerMapping.put(uri, method);
}
}
}
}
}
}

运行时

  • 处理请求

    1. 从请求的url转换为method
    2. 参数匹配

      1. 读取method的参数列表,类型列表
      2. 根据类型我们有两个可以直接设置HttpServletRequest,HttpServletResponse
      3. 获取参数的注解. HFRequestParam . 从url中获取对应的名称. 放入参数列表.
      4. method.invoke 执行.
      5. 获取method的执行结果. response 写出
private void dispath(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String requestURI = req.getRequestURI();
String contextPath = req.getContextPath();
requestURI = requestURI.replaceAll(contextPath, "").replaceAll("/+", "/");
if (!HandlerMapping.containsKey(requestURI)) {
throw new RuntimeException("不存在的url");
}
Method method = HandlerMapping.get(requestURI);
if (method != null) {
// 获取 method 所在的class
String simpleName = method.getDeclaringClass().getSimpleName();
Object o = IOC_NAME.get(simpleName);
// 请求参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 参数动态赋值
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < paramValues.length; i++) {
// 获取 controller 中的参数类型
Class<?> parameterType = parameterTypes[i];
if (parameterType.equals(req.getClass())) {
paramValues[i] = req;
}
else if (parameterType.equals(resp.getClass())) {
paramValues[i] = resp;
}
// todo: 2020/7/25 参数内容的设置
else if (parameterType.equals(String.class)) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation a : parameterAnnotations[i]) {
if (a instanceof HFRequestParam) {
String paramName = ((HFRequestParam) a).name();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(parameterMap.get(paramName))
.replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
paramValues[i] = value;
}
}
}
}
else if (parameterType.equals(Integer.class) || parameterType.equals(int.class)) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation a : parameterAnnotations[i]) {
if (a instanceof HFRequestParam) {
String paramName = ((HFRequestParam) a).name();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(parameterMap.get(paramName))
.replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
paramValues[i] = Integer.valueOf(value);
}
}
}
}
}
Object invoke = method.invoke(o, paramValues);
resp.getWriter().write(invoke.toString());
}
}

完整的类

package org.huifer.spring.servlet.v1;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.huifer.spring.annotation.HFAutowired;
import org.huifer.spring.annotation.HFController;
import org.huifer.spring.annotation.HFRequestMapping;
import org.huifer.spring.annotation.HFRequestParam;
import org.huifer.spring.annotation.HFService;
public class HFDispatcherServlet extends HttpServlet {
public static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
public static final String SCAN_KEY = "scanPackage";
private final Properties SPRING_CONTEXT_CONFIG = new Properties();
private final List<String> classNameList = new ArrayList<>();
private final Map<String, Method> HandlerMapping = new HashMap<>();
/**
*
*/
Map<String, Object> IOC_NAME = new HashMap<>();
@Override
public void init() throws ServletException {
// 配置读取
doLoadConfig();
// 注解扫描.
doScan(this.SPRING_CONTEXT_CONFIG.getProperty(SCAN_KEY));
// 初始化类
instance();
// 依赖注入
autowired();
// 初始化 handlerMapping
initHandlerMapping();
System.out.println();
}
private void initHandlerMapping() {
if (IOC_NAME.isEmpty()) {
return;
}
for (Entry<String, Object> entry : IOC_NAME.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(HFController.class)) {
if (clazz.isAnnotationPresent(HFRequestMapping.class)) {
HFRequestMapping annotation = clazz.getAnnotation(HFRequestMapping.class);
String baseUri = annotation.name();
for (Method method : clazz.getMethods()) {
HFRequestMapping annotation1 = method.getAnnotation(HFRequestMapping.class);
if (annotation1 != null) {
String uri = ("/" + baseUri + "/" + annotation1.name()).replaceAll("/+", "/");
HandlerMapping.put(uri, method);
}
}
}
}
}
}
private void autowired() {
if (IOC_NAME.isEmpty()) {
return;
}
for (Entry<String, Object> entry : IOC_NAME.entrySet()) {
try {
String k = entry.getKey();
Object v = entry.getValue();
Field[] declaredFields = v.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
// 是否又自动注入的注解
if (declaredField.isAnnotationPresent(HFAutowired.class)) {
HFAutowired annotation = declaredField.getAnnotation(HFAutowired.class);
String beanName = annotation.value().trim();
// byType 获取具体的实现类
if (beanName.equals("")) {
beanName = declaredField.getType().getName();
}
declaredField.setAccessible(true);
declaredField.set(v, IOC_NAME.get(beanName));
}
else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 实例化
*/
private void instance() {
if (this.classNameList.isEmpty()) {
return;
}
try {
for (String clazz : this.classNameList) {
Class<?> aClass = Class.forName(clazz);
// 1. 接口的实现类初始化
if (!aClass.isInterface()) {
System.out.println(clazz);
Object o = aClass.newInstance();
// 1. 带有注解的初始化
if (aClass.isAnnotationPresent(HFController.class)) {
IOC_NAME.put(aClass.getSimpleName(), o);
}
else if (aClass.isAnnotationPresent(HFService.class)) {
HFService annotation = aClass.getAnnotation(HFService.class);
// 名字注入
if (annotation.name().equals("")) {
IOC_NAME.put(aClass.getSimpleName(), o);
}
else {
IOC_NAME.put(annotation.name(), o);
}
// 类型注入
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
if (!IOC_NAME.containsKey(anInterface.getName())) {
IOC_NAME.put(anInterface.getName(), o);
}
}
}
else {
continue;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 包扫描
*/
private void doScan(String scanPackage) {
// 类路径
URL resource = this.getClass().getClassLoader()
.getResource("/" + scanPackage.replaceAll("\\.", "/"));
File classPath = new File(resource.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScan(scanPackage + "." + file.getName());
}
else {
if (!file.getName().endsWith(".class")) {
continue;
}
else {
String className = (scanPackage + "." + file.getName()).replace(".class", "");
classNameList.add(className);
}
}
}
}
/**
* 从 web.xml 读取contextConfigLocation <br> 把 {@code classpath*:application.properties} 载入配置
*/
private void doLoadConfig() {
InputStream resourceAsStream = null;
try {
// 获取servlet的配置
ServletConfig servletConfig = this.getServletConfig();
String configInitParameter = servletConfig.getInitParameter(CONTEXT_CONFIG_LOCATION)
.replace("classpath*:", "");
// 读取配置文件
resourceAsStream = this.getClass().getClassLoader()
.getResourceAsStream(configInitParameter);
SPRING_CONTEXT_CONFIG.load(resourceAsStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resourceAsStream != null) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
try {
dispath(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write(Arrays.toString(e.getStackTrace()));
}
}
private void dispath(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String requestURI = req.getRequestURI();
String contextPath = req.getContextPath();
requestURI = requestURI.replaceAll(contextPath, "").replaceAll("/+", "/");
if (!HandlerMapping.containsKey(requestURI)) {
throw new RuntimeException("不存在的url");
}
Method method = HandlerMapping.get(requestURI);
if (method != null) {
// 获取 method 所在的class
String simpleName = method.getDeclaringClass().getSimpleName();
Object o = IOC_NAME.get(simpleName);
// 请求参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 参数动态赋值
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < paramValues.length; i++) {
// 获取 controller 中的参数类型
Class<?> parameterType = parameterTypes[i];
if (parameterType.equals(req.getClass())) {
paramValues[i] = req;
}
else if (parameterType.equals(resp.getClass())) {
paramValues[i] = resp;
}
// todo: 2020/7/25 参数内容的设置
else if (parameterType.equals(String.class)) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation a : parameterAnnotations[i]) {
if (a instanceof HFRequestParam) {
String paramName = ((HFRequestParam) a).name();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(parameterMap.get(paramName))
.replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
paramValues[i] = value;
}
}
}
}
else if (parameterType.equals(Integer.class) || parameterType.equals(int.class)) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation a : parameterAnnotations[i]) {
if (a instanceof HFRequestParam) {
String paramName = ((HFRequestParam) a).name();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(parameterMap.get(paramName))
.replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
paramValues[i] = Integer.valueOf(value);
}
}
}
}
}
Object invoke = method.invoke(o, paramValues);
resp.getWriter().write(invoke.toString());
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
dispath(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write(Arrays.toString(e.getStackTrace()));
}
}
}

微信扫一扫,分享到朋友圈

手写 spring mvc 基于注解

LeetCode.28 实现 strStr (JS sunday算法)

上一篇

python爬虫requests&BeautifulSoup

下一篇

你也可能喜欢

手写 spring mvc 基于注解

长按储存图像,分享给朋友