关于java:手写-spring-mvc-基于注解

10次阅读

共计 15393 个字符,预计需要花费 39 分钟才能阅读完成。

手写 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()));

    }
  }
}
正文完
 0