手写SpringMVC底层机制

这篇具有很好参考价值的文章主要介绍了手写SpringMVC底层机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

手写 SpringMVC 底层机制

  1. 前景提要:实现的是SpringMVC核心机制

  2. 对一些细枝末节的代码做了简化,比如字符串的处理...

  3. 完成哪些机制

    • 机制一: 通过@RequestMapping ,可以标记一个方法,编写路径url,浏览器就能通过url完成调用
    • 机制二: 进行依赖注入,使之不需要传统的new 一个对象,而是直接从IOC容器中获得
    • 机制三:通过@RequestParam,如果浏览器传递的参数名和目标方法的形参不一致,可以通过value设置进行匹配
    • 机制四:在目标方法完成后,跳转到相关页面 请求转发/重定向
    • 机制五:在目标方法完成后,通过@Response注解,向浏览器发送JSON格式数据

手写添加配置

思路

  1. 需要配置pom.xml的依赖
  2. 需要写一个Servlet 作为前端控制器
  3. 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动
  4. 需要配置spring容器配置文件
  5. 需要配置spring容器配置文件 扫描的路径 <component-scan ...>

实现

  • 需要配置pom.xml的依赖
<!--  配置原生Servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    <!--  <scope> 表示引入的jar的作用范围
          provided 表示该项目在打包 放到生产环境时,不需要带上 servlet-api.jar
          因为tomcat本身有 servlet-api.jar,到时直接使用tomcat本身的 servlet-api.jar-->
    </dependency>

  <!--  配置dom4j-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.1</version>
    </dependency>

  <!--  配置常用的工具类-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
    </dependency>
  • 需要写一个Servlet 作为前端控制器
public class ZyDispatcherServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doPost--");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doGet--");
    }
}
  • 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动

  • 需要配置spring容器配置文件 扫描的路径 <component-scan ...>

 <servlet>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <servlet-class>com.code_study.zyspringmvc.servlet.ZyDispatcherServlet</servlet-class>
    
    <!--配置参数,指定要操作的spring配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:zyspringmvc.xml</param-value>
    </init-param>

    <!--跟随tomcat自启动-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  • 需要配置spring容器配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--    要扫描的包-->
    <component-scan base-package="com.code_study.controller,com.code_study.service"></component-scan>
</beans>

完成浏览器可以请求控制层

思路

  • 创建@Controller和自己的Controller
  • 编写工具类XMLParser,解析spring容器配置文件
  • 开发自己的 Spring容器,得到扫描类的全路径列表
  • 开发自己的 前端控制器,实例化对象到容器中
  • 完成请求的URL和控制器方法的映射关系
  • 完成前端控制器分发请求到对应控制器
  1. 自定义注解@Controller
  2. 创建Controller
  3. 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
  4. 这个所有的路径 一个split(",")分隔,都进行扫描
  5. 需要写自己的 前端控制器
  6. 需要写自己的 Spring容器
  7. 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
  8. 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
  9. 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
  10. 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
  11. 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
  12. 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
  13. 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
  14. 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。

实现

  1. 自定义注解@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
  1. 创建Controller
@Controller
public class MonsterController {
}
  1. 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
public class XMLParser {

    public static String getBasePackage(String xmlFile){
        SAXReader saxReader = new SAXReader();
        ClassLoader classLoader = XMLParser.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream(xmlFile);
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            Element element = rootElement.element("component-scan");
            String basePackage = element.attribute("base-package").getText();
            return basePackage;
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 这个所有的路径 一个split(",")分隔,都进行扫描
  2. 需要写自己的 前端控制器
  3. 需要写自己的 Spring容器
  4. 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
public void scanPackage(String pack) {
        //获得包所在的工作路径 [绝对路径]
        URL url =
                this.getClass().getClassLoader().//获取类的加载器
                        //得到指定包对应的工作路径 [绝对路径]
                                getResource("/" + pack.replaceAll("\\.", "/"));

        // System.out.println("url= "+url);
        //根据得到的路径,对其进行扫描,把类的全路径 保存到 classFullPathList
        String path = url.getFile();
        //在io中 把目录也是为一个文件
        File file = new File(path);
        //遍历file 【遍历出文件和子目录】
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {//如果是目录
                //需要递归扫描 找子目录
                scanPackage(pack + "." + f.getName());
            } else {
                //的确是个文件
                //扫描到的文件可能是 .class 文件 也可能是其他文件
                //就算是.class 文件 也需要判断是否需要注入容器 有无加 @Controller注解
                //目前无法拿到注解 因为没法反射 所以先把文件的全路径都保存到 classFullPathList 之后在注入对象到容器时再处理
                String classFullPath =
                        //类的全路径不需要.class 去掉.class
                        pack + "." + f.getName().replaceAll(".class", "");

                //保存到 classFullPathList
                classFullPathList.add(classFullPath);
            }
        }
    }
  1. 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
//保存扫描的包/子包类的全路径
    private List<String> classFullPathList =
            new ArrayList<>();
  1. 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
//定义属性 ioc -> 存放反射生成的bean对象 比如Controller / Service /Dao
public ConcurrentHashMap<String, Object> ioc =
        new ConcurrentHashMap<>();
  1. 编写方法,将扫描到的类,在满足情况下 反射到ioc容器
//编写方法,将扫描到的类,在满足情况下 反射到ioc容器
    public void executeInstance() {
        if (classFullPathList.size() == 0) {
            //说明没有扫描到类
            return;
        }

        //遍历classFullList
        for (String classFullPath : classFullPathList) {
            try {
                Class<?> clazz = Class.forName(classFullPath);
                if (clazz.isAnnotationPresent(Controller.class)) {//处理@Controller
                    String className = clazz.getSimpleName();

                    Object instance = clazz.newInstance();
                    String value = clazz.getAnnotation(Controller.class).value();
                    if (!"".equals(value)) {
                        className = value;
                    } else {
                        className = StringUtils.uncapitalize(className);
                    }
                    ioc.put(className, instance);
                }
                else if (clazz.isAnnotationPresent(Service.class)) {//处理@Service
                    String className = clazz.getSimpleName();//类名

                    Service serviceAnnotation = clazz.getAnnotation(Service.class);
                    String value = serviceAnnotation.value();

                    if (!"".equals(value)) {
                        className = value;
                        Object instance = clazz.newInstance();
                        ioc.put(className, instance);
				}
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
  1. 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
ZyHandler {
    private String url;
    private Method method;
    private Object controller;

    public ZyHandler() {
    }

    public ZyHandler(String url, Method method, Object controller) {
        this.url = url;
        this.method = method;
        this.controller = controller;
    }
//需要提供getter和setter方法...
  1. 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
//定义属性 HandlerList -> 保存ZyHandler 【url 和 控制器的映射】
    private List<ZyHandler> HandlerList =
            new ArrayList<>();
  1. 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
 private void initHandlerMapping(){
        //遍历 ioc
        for (Map.Entry<String,Object> entry:  zyWebApplicationContext.ioc.entrySet()) {
            if (zyWebApplicationContext.ioc.isEmpty()){
                return;
            }
            Object bean = entry.getValue();
            Class<?> clazz = bean.getClass();
            if (clazz.isAnnotationPresent(Controller.class)){
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)){

                        String url = declaredMethod.getAnnotation(RequestMapping.class).value();

                        ZyHandler zyHandler = new ZyHandler(url, declaredMethod, bean);

                        HandlerList.add(zyHandler);
                    }
                }
            }
        }
    }
  1. 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
 //编写方法,通过request对象 返回ZyHandler对象 ,如果没有返回null
    private ZyHandler getZyHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        //遍历HandlerList
        for (ZyHandler zyHandler : HandlerList) {
            if (requestURI.equals(zyHandler.getUrl())){
                return zyHandler;
            }
        }
        return null;
    }
  1. 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。
   public void executeDispatch(HttpServletRequest request,HttpServletResponse response){
        ZyHandler zyHandler = getZyHandler(request);
        try {
            if (null == zyHandler){
               response.getWriter().write("<h1>404 NOT FOUND</h1>");
            }
            Method method = zyHandler.getMethod();
            method.invoke(zyHandler.getController(),request,response);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

从web.xml文件中动态获取spring配置文件

思路

  1. 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
  2. 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。

实现

  1. 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
  2. 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。
@Override
    public void init() throws ServletException {
        String configLocation = getServletConfig().getInitParameter("contextConfigLocation");
        System.out.println("ZyDispatcherServlet 初始化---");
        zyWebApplicationContext = new ZyWebApplicationContext(configLocation);
        zyWebApplicationContext.init();

        initHandlerMapping();
        System.out.println("HandlerList= "+HandlerList);

    }
 private  String configLocation;
    public ZyWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }
 public void init(){
        System.out.println("ZyWebApplicationContext 初始化---");
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        String[] basePackages = basePackage.split(",");
        if (basePackages.length >0) {
            for (String  pack : basePackages) {
                scanPackage(pack);
            }
        }
 }

@Service注解

思路

  1. @Service注解是写在类上的 即@Target(ElementType.TYPE)
  2. 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
  3. 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean

实现

  1. @Service注解是写在类上的 即@Target(ElementType.TYPE)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}
  1. 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
  2. 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean
 public void executeInstance() {
        if (classFullPathList.size() == 0){
            return;
        }
        //遍历 classFullPathList
        for (String classFullPath : classFullPathList) {
            try {
                Class<?> clazz = Class.forName(classFullPath);

                if (clazz.isAnnotationPresent(Controller.class)){
                    Controller controller = clazz.getAnnotation(Controller.class);
                    String value = controller.value();
                    String className = clazz.getSimpleName();
                    Object instance = clazz.newInstance();
                    if ("".equals(value)){
                        className = StringUtils.uncapitalize(className);
                    }else {
                        className = value;
                    }
                    ioc.put(className,instance);
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service serviceAnnotation = clazz.getAnnotation(Service.class);
                    String annoattionValue = serviceAnnotation.value();
                    Object instance = clazz.newInstance();

                    if ("".equals(annoattionValue)){
                        Class<?>[] interfaces = clazz.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            String simpleName = anInterface.getSimpleName();
                            simpleName = StringUtils.uncapitalize(simpleName);
                            ioc.put(simpleName,instance);
                        }

                        //可以通过类名首字母小写
                        String simpleName = clazz.getSimpleName();
                        simpleName = StringUtils.uncapitalize(simpleName);
                        ioc.put(simpleName,instance);

                    }else {
                        ioc.put(annoattionValue,instance);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

@Autowried 依赖注入

思路

  1. @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
  2. 需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配
  3. 通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
  4. value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");
  5. 如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配
  6. 即 declaredField.set(bean, beanInIOC);
  7. 由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);

实现

  1. @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}
  1. 需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配

  2. 通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值

  3. value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");

  4. 如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配

  5. 即 declaredField.set(bean, beanInIOC);

  6. 由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);

public void  executeAutoWired(){
    //遍历ioc
    if (ioc.isEmpty()){
        return;
    }

    //获取容器里的所有bean 以及 bean对应的字段
    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        Object bean = entry.getValue();

        Class<?> clazz = bean.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {

            //通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
            if (declaredField.isAnnotationPresent(Autowired.class)){
                Autowired annotation =
                        declaredField.getAnnotation(Autowired.class);
                String beanName = annotation.value();

                try {
                    //value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,
                    if ("".equals(beanName)){
                        beanName = declaredField.getType().getSimpleName();
                        beanName = StringUtils.uncapitalize(beanName);

                    }
                        Object iocBean = ioc.get(beanName);
                    //如果没有抛出空指针异常
                        if (null == iocBean){
                            throw new NullPointerException("ioc 没有该Bean");
                        }
                        declaredField.setAccessible(true);//暴力破解
                        declaredField.set(bean,iocBean);

                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

@RequestParam

思路

  1. @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
  2. 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
  3. 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
  4. 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
  5. 这两个数组的内容需要一一对应,因为反射需要顺序一致
  6. 需要获取到request 中请求的参数Map 获取参数名和参数值
  7. 可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】
  8. 将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写
  9. 完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写
  10. 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
  11. 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
  12. 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题

实现

  1. @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}
  1. 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
  2. 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
Method method = zyHandler.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
Object[] params = new Object[parameterTypes.length];
  1. 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
  2. 这两个数组的内容需要一一对应,因为反射需要顺序一致
  3. 需要获取到request 中请求的参数Map 获取参数名和参数值
 request.setCharacterEncoding("utf-8");
 Map<String, String[]> parameterMap = request.getParameterMap();
 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
    String name = entry.getKey();//参数 键
    String value = entry.getValue()[0];//参数 值
  1. 可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】

  2. 将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写

  3. 完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写

 for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = response;
                    }
                }
  1. 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
 //获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返
 private int getRequestParamterIndex(Method method,String name){
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                if (parameter.isAnnotationPresent(RequestParam.class)) {

                    String value = parameter.getAnnotation(RequestParam.class).value();

                    if (name.equals(value)) {
                        return i;//返回int 代表第几个参数
                    }
                }
        }
        return -1;
    }
int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
                    if (requestParamterIndex != -1) {
                        params[requestParamterIndex] = value;
                    } 
  1. 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
	//将目标方法的所有的形参的名称 反射保存到 List 返回
    private List<String> getParameterNames(Method method){
        List<String> parameterList = new ArrayList<>();

        //获取到所有的参数名称
        Parameter[] parameters = method.getParameters();

        for (Parameter parameter : parameters) {
            //在默认情况下 parameter.getName() 获取的名字不是形参真正的名字
            //而是[arg0,arg1,arg2...]
            //需要插件,使用java8的特性 解决
            String name = parameter.getName();
            parameterList.add(name);
        }
        System.out.println("目标方法的形参列表=" + parameterList);
        return parameterList;
    }
else {
                        //没找到@RequestParam 对应参数--使用默认机制
                        //1. 得到目标方法的所有形参名
                        //2. 对得到目标方法的所有形参名进行遍历,
                        //如果匹配就把当前请求的参数值放入params
                        List<String> parameterNames = getParameterNames(zyHandler.getMethod());
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }
                    }
  1. 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题
  <!--可以进行json操作-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.14.0</version>
    </dependency>

  • 完整代码
 public void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
        ZyHandler zyHandler = getZyHandler(request);
        try {
            if (null == zyHandler) {
                response.getWriter().write("<h1>404 NOT FOUND!</h1>");
            } else {
                Method method = zyHandler.getMethod();
                Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
                Object[] params = new Object[parameterTypes.length];


                //遍历 parameterTypes
                //获取 HttpServletRequest , HttpServletResponse 在形参数组中的位置
                //将request 和 response 保存到  params相应的位置上
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = response;
                    }
                }

                //获取request中的
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = request.getParameterMap();
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();//参数 键
                    String value = entry.getValue()[0];//参数 值

                    //获取形参数组中 带有@RequestParam 的形参 的位置
                    //将带有@RequestParam 保存到  params相应的位置上
                    int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
                    if (requestParamterIndex != -1) {
                        params[requestParamterIndex] = value;
                    } else {
                        //没找到@RequestParam 对应参数--使用默认机制
                        //1. 得到目标方法的所有形参名
                        //2. 对得到目标方法的所有形参名进行遍历,
                        //如果匹配就把当前请求的参数值放入params
                        List<String> parameterNames = getParameterNames(zyHandler.getMethod());
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }
                    }
                }

视图解析

思路

  1. 首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  3. 当返回的是String类型后 我们就可以根据 splic(":")进行分隔
  4. splic(":")[0] 就是进行跳转的方式 forward 或者 redirect
  5. splic(":")[1] 就是进行跳转的页面
  6. 如果没有":" ,就说明是默认情况,forward 处理即可

实现

  1. 首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用

  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理

    Object result = zyHandler.getMethod().invoke(zyHandler.getController(), params);
    
  3. 当返回的是String类型后 我们就可以根据 splic(":")进行分隔

  4. splic(":")[0] 就是进行跳转的方式 forward 或者 redirect

  5. splic(":")[1] 就是进行跳转的页面

  6. 如果没有":" ,就说明是默认情况,forward 处理即可文章来源地址https://www.toymoban.com/news/detail-856739.html

 if (result instanceof String){
                   String viewName =  (String) result;
                    if (viewName.contains(":")) {
                        String viewType = viewName.split(":")[0];
                        String viewPage = viewName.split(":")[1];

                        if ("forward".equals(viewType)){
                            request.getRequestDispatcher(viewPage).forward(request,response);
                        }else if (("redirect".equals(viewType))){
                            response.sendRedirect(viewPage);
                        }
                    }else {
                        request.getRequestDispatcher(viewName).forward(request,response);
                    }
                }

@ResponseBody 返回JSON数据

实现

  1. 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
  2. 没有默认值 @ResponseBody仅仅作为一个标识
  3. @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  4. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  5. 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
  6. 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
  7. 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
  8. 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可

思路

  1. 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
  2. 没有默认值 @ResponseBody仅仅作为一个标识
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
  1. @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  3. 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
  4. 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
else if (result instanceof ArrayList) {
    if (zyHandler.getMethod().isAnnotationPresent(ResponseBody.class)) {
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(result);
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(json);
        writer.flush();
        writer.close();
    }
}
  1. 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
  2. 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可

到了这里,关于手写SpringMVC底层机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手动开发-实现SpringMVC底层机制--小试牛刀

    在这里说的底层机制的实现主要是指:前端控制器、Controller、Service注入容器、对象自动装配、控制器方法获取参数、视图解析、返回json数据。 前端控制器就是核心控制器。在这里我们可以设计一个Servlet来充当核心控制器: LingDispatcherServlet.java .这个控制器的作用主要是接收

    2024年02月08日
    浏览(38)
  • 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 6-完成控制器方法获取参数-@RequestParam

    😀前言 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 6-完成控制器方法获取参数-@RequestParam 🏠个人主页:尘觉主页 🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉 在csdn获奖荣誉: 🏆csdn城市之星2名 ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣

    2024年02月11日
    浏览(29)
  • SpringMvc拦截器和手写模拟SpringMvc工作流程源码详解

    目录 1. SpringMvc简介 1.1 什么是MVC 1.2 什么是SpringMvc 1.3 SpringMvc 能干什么 1.4 SpringMvc 工作流程 2. SpringMvc拦截器和过滤器 2.1 拦截器 2.1.1 拦截器作用 2.1.2 拦截器和过滤器的区别 2.1.3 拦截器方法说明 2.1.4 多个拦截器执行顺序 2.1.5 自定义拦截器 2.2 过滤器(附加) 3. 手写模拟Spri

    2024年02月09日
    浏览(36)
  • Spring-手写模拟Spring底层原理

    模拟大致的底层原理,为学习Spring源码做铺垫。 实现的功能:扫描路径、依赖注入、aware回调、初始化前、初始化、初始化后、切面 未实现的功能:构造器推断、循环依赖 重点:BeanDefinition、BeanPostProcessor 学习Spring源码的重点:设计模式、编码规范、 设计思想、扩展点 启动

    2024年02月08日
    浏览(26)
  • 手写模拟Spring的底层原理2.1

    先来引入两个问题 第一个懒加载还是立即加载的问题,这个问题还是在于就是说,当我们xml配置了一个对象bean的时候,它在spring容器里面是什么时候开始会给我们创建这个对象 那如果我们想对某个对象启动懒加载,可以添加@lazy这个注解  这个注解一加上,它就只会在得到

    2024年02月08日
    浏览(30)
  • Spring源码系列:初探底层,手写Spring

    在学习Spring框架源码时,记住一句话:源码并不难,只需要给你各种业务场景或者项目经理,你也能实现自己的Spring。虽然你的实现可能无法与开源团队相媲美,但是你肯定可以实现一个0.0.1版本。因此,初次阅读源码时,不要陷入太深的细节中。先了解大体逻辑,再仔细研

    2023年04月12日
    浏览(35)
  • SpringMVC底层原理源码解析

    SpringMVC的作用毋庸置疑,虽然我们现在都是用SpringBoot,但是SpringBoot中仍然是在使用SpringMVC来处理请求。 我们在使用SpringMVC时,传统的方式是通过定义web.xml,比如: 我们只要定义这样的一个web.xml,然后启动Tomcat,那么我们就能正常使用SpringMVC了。 SpringMVC中,最为核心的就是

    2024年02月05日
    浏览(35)
  • 【SpringMVC】9—底层原理

    ⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记链接👉https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star~😊 如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆 9.1.1 初始化操作调用路线 类和接口之间的关系 调用路线图 调用

    2023年04月10日
    浏览(24)
  • Java中ArrayList的底层扩容机制和Vector的底层扩容机制,以及ArrayList和Vector的对比与选择。附源码

    在 Java 的 ArrayList 中,当数组的容量不足以存储新元素时,会触发扩容操作。ArrayList 的底层使用数组来存储元素,而扩容机制主要涉及到创建一个更大的数组,并将现有元素复制到新数组中。ArrayList 支持无参扩容和有参扩容两种机制。 无参扩容机制 : 无参扩容是指 首次的

    2024年02月11日
    浏览(27)
  • Ribbon LoadBalanced底层机制源码探秘

    🍊 Java学习:社区快速通道 🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想 🍊 绝对不一样的职场干货:大厂最佳实践经验指南 📆 最近更新:2023年6月18日 🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力! 通过本文你可以学习到: LoadBalanced作用原理 拦截器到Rule的调

    2024年02月09日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包