从0手写实现SpringMVC框架

Posted 四阿哥胤禛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0手写实现SpringMVC框架相关的知识,希望对你有一定的参考价值。

 了解SpringMVC


  • MVC是什么?

    • MVC是模型(model)-- 视图(view)-- 控制器(controller)的缩写,它是一个设计模式


  • Spring是什么?

    • 面向接口的编程思想,解决的是业务逻辑层和其他层的松耦合问题

    • Spring主要有两个功能为我们的业务对象管理提供了非常便捷的方法

      • DI(Dependency Injection,依赖注入)

      • AOP(Aspect Oriented Programming,面向切面编程)

    • 框架优点

      • 轻量级的容器框架,没有侵入性

      • 使用IoC容器更加容易组合对象之间的关系,面向接口编程,降低耦合

      • Aop可以更加容易的进行功能扩展,遵循(Open Closed Principle,OCP)开闭原则

      • 创建对象默认是单例的,不需要再使用单例模式进行处理


  • Spring MVC又是什么

    • Spring MVC是当前最优秀的MVC框架


  • Spring MVC优点

    • 使用简单,学习成本低

    • 很容易写出性能优秀的程序。单例

    • 非常灵活,扩展性很强


从0手写实现SpringMVC框架 Spring MVC运行流程


从0手写实现SpringMVC框架

SpringMVC运行流程


假如我们在浏览器输入user.do的请求

  • 我们应该都知道Spring MVC底层实质上是Servlet,对Servlet做了各种各样的封装,我们在使用SpringMVC开发时,应该都会注意到如果有web.xml,都需要在里面添加一个DispatcherServlet 

    <servlet> <servlet-name>spring-mvc</servlet-name>  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup></servlet>
    <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <url-pattern>*.do</url-pattern></servlet-mapping>
  • 请求进来之后,匹配url-pattern,最终进入到SpringMVC的DispatcherServlet DispatcherServlet 相当于一个中转站,只负责接收请求然后转发给SpringMVC的核心组件去处理,DispatcherServlet 并不对请求做其它处理;启动项目时,除了初始化Spring上下文,还需要初始化SpringMVC的上下文内容,从DispatcherServlet 类中,我们可以发现DispatcherServlet 中初始化springmvc九大组件,其中有一个叫HandlerMapping,我们可以近似的认为这是一个HashMap,里面存放的是我们的请求和Method的映射。当我们被转到HandlerMapping中,这时候就会去查找是否存在与我们请求对应的Method,如果存在的话,则由HandlerMapping;不存在的话,就会去找我们的配置文件中是否配置了默认处理器<mvc:default-servlet-handler />,如果配置了,则会去找目标资源;如果没有配置,则会在控制台报错

    No mapping found for HTTP request with URI[/xx/xx] in DispatcherServlet

public class DispatcherServlet extends FrameworkServlet { /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) {    //用于处理上传请求,处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取 File initMultipartResolver(context);    //SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源 或者主题的时候 initLocaleResolver(context);    //用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源    //如图片、CSS样式等,SpringMVC的主题也支持国际化     initThemeResolver(context);    //用来查找 Handler的 initHandlerMappings(context);    //从名字上看,这是一个适配器。Servlet需要的处理方法的结构是固定的,都是以request和response为参数的方法    //如何让固定的Servlet处理方法调用灵活的 Handler来进行处理呢?这就是HandlerAdapter要做的事情     initHandlerAdapters(context);    //这个组件是用来对异常情况进行处理 initHandlerExceptionResolvers(context);    //用来从request中获取viewName,在Handler处理完之哦户没有设置view和viewName的情况下 initRequestToViewNameTranslator(context);    //用来渲染页面的,也就是将程序返回的参数填入模板 initViewResolvers(context);    //用来管理 FlashMap的,FlashMap主要用在 redirect重定向中传递参数 initFlashMapManager(context); }} 
  • 如果找到了HandlerMapping,接下来就会去获取HandlerAdapter对象,当我们想对数据进行转化、数据格式化时,就是在HandlerAdapter对象里面处理的。

  • 接下来调用拦截器的PreHandle方法,再调用目标Handler的目标方法得到ModelAndView对象,之后调用拦截器的PostHandler方法,判断是否存在异常,如果存在异常,则交给HandlerExceptionResolver组件处理异常,得到新的ModelAndView对象,由ViewResolver组件根据ModelAndView对象得到实际的View,然后渲染视图,调用拦截器的afterCompletetion方法


从0手写实现SpringMVC框架 Spring MVC核心组件


  • SpringMVC中的Servlet一共有三个层:

    从0手写实现SpringMVC框架

    • HttpServletBean

      • 直接继承自Java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的属性

    • FrameworkServlet

      • 初始化了WebApplicationContext

    • DispatcherServlet

      • 初始化自身的9个组件(上面已讲过)


 设计自己的Spring MVC框架


  • 读取配置

    • 通过web.xml加载我们自己写的MyDispatcherServlet和读取文件


  • 初始化

    • 九大组件只需要实现基本的

    • 加载配置文件

      private void doLoadConfig(String initParameter) { //将web.xml中的contextConfigLocation对应value值的文件加载到流 try ( InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(initParameter); ){ properties.load(resourceAsStream); } catch (Exception e) { e.printStackTrace(); }}
    • 注解开发

      @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Controller { /** * controller别名 */  String value() default ""}
    • 扫描用户配置包下的类

      private void doScanner(String packageName) { URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/")); File dir = new File(url.getFile()); Arrays.asList(dir.listFiles()).forEach(file -> { if (file.isDirectory()) { //递归读取package doScanner(packageName + "." + file.getName()); } else { String className = packageName + "." + file.getName().replace(".class", ""); classNames.add(className); System.out.println("容器扫描到的类有:" + packageName + "." + file.getName()); } });}
    • 通过反射机制实例化包下的类,并且放到ioc容器中(Map的键值对 beanName --- bean) beanName默认是首字母小写

      private void doInstance() { if (classNames.isEmpty()) { return ; } classNames.forEach(className -> { try { //通过反射实例化 Class<?> clazz = Class.forName(className); if (clazz.isAnnotationPresent(Controller.class)) { Controller annotation = clazz.getAnnotation(Controller.class); String key = annotation.value(); if (!"".equals(key) && key != null) { ioc.put(key, clazz.newInstance()); } else { ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance()); } } else if (clazz.isAnnotationPresent(Service.class)) { Service annotation = clazz.getAnnotation(Service.class); String key = annotation.value(); if (!"".equals(key) && key != null) { ioc.put(key, clazz.newInstance()); } else { ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance()); } }  } catch (Exception e) { e.printStackTrace(); } });}
    • 初始化HandlerMapping

      private void initHandlerMapping() { if (ioc.isEmpty()) { return; } try { //存放controller的 url-method Map<String, Object> url_method = new HashMap<>(); for(Entry<String, Object> entry : ioc.entrySet()) { Class<? extends Object> clazz = entry.getValue().getClass(); String baseUrl = ""; if (clazz.isAnnotationPresent(RequestMapping.class)) { RequestMapping annotation = clazz.getAnnotation(RequestMapping.class); baseUrl = annotation.value(); } Method[] methods = clazz.getMethods(); for(Method method : methods){ if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping annotation = method.getAnnotation(RequestMapping.class); String url = annotation.value();  url = (baseUrl + "/" + url).replaceAll("/+", "/"); handlerMapping.put(url, method); url_method.put(url, clazz.newInstance()); System.out.println("HandlerMapping:" + url + "," + method); } } ioc.putAll(url_method); }  } catch (Exception e) { // TODO: handle exception }}


  • 运行

    • 异常拦截

    • 获取请求传入的参数并处理参数

    • 通过初始化好的handlerMapping获取url对应的方法名,反射调用

      //转发请求,根据url找到相应的methodpublic void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (handlerMapping.isEmpty()) { return ; } String url = req.getRequestURI(); String contextPath = req.getContextPath();  //拼接url并把多个 /替换成一个 url = url.replace(contextPath, "").replaceAll("/+", "/");  if (!this.handlerMapping.containsKey(url)) { resp.getWriter().write("404 NOT FOUND"); return ; } Method method = this.handlerMapping.get(url);  //获取方法的参数列表 Class<?>[] parameterTypes = method.getParameterTypes();  //获取请求的参数 Map<String, String[]> parameterMap = req.getParameterMap();  //保存参数值 Object[] paramValues = new Object[parameterTypes.length];  //方法的参数列表 for (int i = 0; i < parameterTypes.length; i++) { String requestParam = parameterTypes[i].getSimpleName(); if (requestParam.equals("HttpServletRequest")) { paramValues[i] = req; continue ; } if (requestParam.equals("HttpServletResponse")) { paramValues[i] = resp; continue ; } if (requestParam.equals("String")) { for (Entry<String, String[]> param : parameterMap.entrySet()) { String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", ""); paramValues[i] = value; } } } //利用反射机制来调用 try { method.invoke(this.ioc.get(url), paramValues); } catch (Exception e) { e.printStackTrace(); }}



https://github.com/biaotang/demo-mvc



点个“好看”支持一下鸭

点鸭点鸭点鸭

                                                                                                       ↓↓↓

以上是关于从0手写实现SpringMVC框架的主要内容,如果未能解决你的问题,请参考以下文章

带你手写一个SpringMVC框架(有助于理解springMVC)

自己手写一个 SpringMVC 框架

荧客技荐自己手写一个 SpringMVC 框架

手写一个迷你版Spring MVC框架

手写Spring MVC框架 实现简易版mvc框架

手把手带你手写SpringMVC,剑指优秀开源框架灵魂