【5】Spring源码-SpringMVC

2019-10-12  本文已影响0人  小毛1221

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. 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方法,初始化各种:

// 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

    image.png
        ii. RequestMappingHandlerMapping中会注入使用@RequestMapping注解的url映射关系
    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为注入的信息;


    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,则会去父容器中取。

上一篇下一篇

猜你喜欢

热点阅读