【5】Spring源码-SpringMVC

6. SpringMVC SpringMVC的实现原理是通过servlet拦截所有URL来达到控制的目的。SpringMVC是基于Servlet的实现。真正的逻辑实现是在DispatcherServlet中进行的,DispatcherServlet继承了httpServlet,httpServlet继承了Servlet。httpServlet重写service方法,根据请求方法调用doGet,doPost等。Servlet接到请求就调用service方法。

  1. 对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2. 在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. 【【5】Spring源码-SpringMVC】contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个(一个web应用可以有多个DispatcherServlet),以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的父bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
6.1 DispatcherServlet的初始化 DispatcherServlet的初始化过程主要是:
  1. 通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。
  2. 初始化WebApplicationContext,DispatcherServlet中会持有一个WebApplicationContext对象的成员变量(DispatcherServlet中持有的Ioc容器中有Controller、Spring默认的各种HandlerMapping、HandlerAdapter):
    a. 初始化WebApplicationContext,并设置其parent ApplicationContext。
    b. 调用WebApplicationContext的refresh方法进行配置文件加载。
    c. 调用DispatcherServlet重写的OnRefresh方法,初始化各种:
  • initMultipartResolver:用于处理文件上传的类,使用applicationContext.getBean()方法获取,并传入DispatcherServlet的一个成员变量中
  • initLocaleResolver:处理国际化问题,也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略(DefaultStrategy:在DispatcherServlet有静态代码块初始化,从文件中读取)
  • initThemeResolver:处理网站的主题资源,也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略
  • initHandlerMappings:当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据Web Application Context的配置来回传给DispatcherServlet相应的Controller。
    可以为DispatcherServlet提供多个Handler Mapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序(@Order注解),然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略可以有多个HandlerMapping。
  • initHandlerAdapters:作为总控制器的派遣器servlet通过处理器映射得到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或者远程调用处理器类型,来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。
    也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个HandlerAdapter。
  • initHandlerExceptionResolvers:异常处理。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个HandlerExceptionResolver。
  • initRequestToViewNameTranslator:Controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略。
  • initViewResolvers:在SpringMVC中,当Controller将请求处理结果放入到ModelAndView中以后, DispatcherServlet会根据ModelAndView选择合适的视图View进行渲染。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。可以配置JSP相关的viewResolver。
    也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个ViewResolver。
  • initFlashMapManager:SpringMVC Flash attributes提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暂存(就像存在session中)以便重定向之后还能使用,并立即删除。默认开启。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略。
// DispatcherServlet protected void onRefresh(ApplicationContext context) { initStrategies(context); }protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }

6.2 DispatcherServlet的执行逻辑 请求->调用HttpServlet的doGet、doPost方法->DispatcherServlet的doService->DispatcherServlet的doDispatch
doDispatch中的逻辑:
  1. 检查是否为multipart请求,如果是则转换为MultipartHTTPServletRequest;
  2. 根据Request信息在HandlerMappings中寻找对应的handler;
    a. 变量HandlerMappings中的所有hanlder,可以根据url查找匹配相应的handler;
    i. 匹配时会将用户输入的url去除多余的”/“,如”/test//a///b”变成”/test/a/b"
    ii. RequestMapping(“/test/a/b”)在匹配时也会在最后加上”/“,保证能更”test/a/b/“匹配上
    b.将查找到的handler加入到拦截器链中;
    c. HandlerMapping中包含对应的controller和方法,启动时会将controller中的方法注入RequestMappingHandlerMapping中;
    i. Spring启动时配置好相应的HandlerMapping

    【5】Spring源码-SpringMVC
    文章图片
    image.png
    ii. RequestMappingHandlerMapping中会注入使用@RequestMapping注解的url映射关系
    【5】Spring源码-SpringMVC
    文章图片
    image.png
    d. handler是一个封装了Controller和具体要调用到Controller中的某个method的类(HandlerMethod),包含url到Controller到method的映射关系;
  3. 根据当前handler寻找对应的HandlerAdapter;
    a. 遍历所有的HandlerAdapter;
    b. 调用HandlerAdapter的supports方法判断是否匹配,supports方法实际上就是判断handler的类型(如SimpleControllerHandlerAdapter就是判断handler是否为Controller类型);
    c. HandlerAdapter主要做一些校验和准备工作(处理方法参数、相关注解、数据绑定、消息转换、返回值、调用视图解析器等等,如加了@ResponseBody就不会返回视图),并调用handler的反射执行方法;
  4. 处理lastModified缓存;
  5. 调用拦截器preHandler方法;
  6. 执行handler(调用HandlerAdapter.handle方法,该方法把具体处理逻辑委托给handler)并返回视图ModelAndView;
    a. @Controller注解由这个处理:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
    b. 最终用拿到Controller的bean,以及对应要执行的method,利用反射调用method;
    c. ModelAndView,view为要跳转的页面,model为注入的信息;

    【5】Spring源码-SpringMVC
    文章图片
    image.png
  7. 调用拦截器的postHandler方法
  8. 处理页面跳转(redirect:xx, forward:xx),并将Model中的属性设置到request中;
6.3 父子容器
  1. DispatcherServlet中持有一个Ioc容器,其父容器为Spring的根Ioc容器,DispatcherServlet可以有多个,他们持有的多个Ioc容器均为同一个Spring跟Ioc容器。
  2. 使用这种父子容器的主要原因在于:
    a. 解耦:
    i. 在J2EE三层架构中,在service层我们一般使用spring框架, 而在web层则有多种选择,如spring mvc、struts等。因此,通常对于web层我们会使用单独的配置文件。如果我们将父子容器都配置与同一个applicationContext.xml中,那么我们想要将spring mvc替换为structs,还需要修改applicationContext.xml,而分为两种配置则无需修改applicationContext.xml。
    ii. 事实上,如果你的项目确定了只使用spring和spring mvc的话,你甚至可以将service 、dao、web层的bean都放到spring-servlet.xml中进行配置,并不是一定要将service、dao层的配置单独放到applicationContext.xml中,然后使用ContextLoaderListener来加载。在这种情况下,就没有了Root WebApplicationContext,只有Servlet WebApplicationContext。
    iii. 如果在子容器中取不到bean,则会去父容器中取。

    推荐阅读