> 文档中心 > 【Spring】四、Spring MVC 源码分析

【Spring】四、Spring MVC 源码分析

上一篇【Spring】三、SpringIOC Bean的初始化流程-循环依赖

文章目录

  • Spring MVC 源码分析
  • 注解版SpringMVC如何启动
    • 没有web.xml配置,SpringMVC如何通过继承于
    • 以上步骤进一步总结补充如下;
        • 1、SPI机制
        • 2、spring-web中的具体应用
        • 3、SpringServletContainerInitializer
    • SpringMVC注册版本启动源码分析
    • Spring MVC 请求处理
    • SpringMVC 的工作原理
    • Spring MVC总结

Spring MVC 源码分析

  • Tomcat是运行Java EE规范下的web程序的服务器,它实现了Java EE 规范,Java EE本身是提出了一系列的技术标准和规范(接口),但是没有具体实现,具体的实现都是各个软件厂商实现的,在Java生态下,要实现web项目开发,切入点是JavaEE规范下的Servlet,Servlet是规范和接口,Servlet可以在Tomcat服务器上运行。
  • 像我们此次需要分析SpringMVC的源码,SpringMVC就是把JavaEE规范下的Servlet整合进来,这样就可以实现使用SpringMVC开发Java web项目,整个逻辑可以如下进行表述:
    Oracle(原来sun公司) --> Web开发的Servlet规范 -->被SpringMVC整合 --> 部署到Tomcat中运行;

注解版SpringMVC如何启动

没有web.xml配置,SpringMVC如何通过继承于

AbstractAnnotationConfigDispatcherServletInitializer
的类来启动SpringMVC?

  • 1、Tomcat在启动的过程中会查找所有实现
    javax.servlet.ServletContainerInitializer接口的实现类;

  • 2、javax.servlet.ServletContainerInitializer的实现类通过@HandlesTypes注解进行扩展;

  • 3、Tomcat启动时会通过加载被@HandlesTypes注解的类,并读取@HandlesTypes注解的value值,将读取出来的值作为参数传给
    javax.servlet.ServletContainerInitializer实现类的onStartup ()方法

  • 4、Tomcat内部启动时调用 javax.servlet.ServletContainerInitializer的onStartup()方法,这个onStartup()方法的第一个参数就是@HandlesTypes注解的value 值指定的类的集合;

  • 5、而在Spring MVC中,对javax.servlet.ServletContainerInitializer的实现就是org.springframework.web.SpringServletContainerInitializer, 而
    org.springframework.web.SpringServletContainerInitializer类上面标注了@HandlesTypes注解,该注解指定的类就是org.springframework.web.WebApplicationInitializer接口,这样就将javax.servlet.ServletContainerInitializer和javax.servlet.WebApplicationInitializer关联起来了;

  • 6、然后我们只需要在项目中写一个类实现org.springframework.web.WebApplicationInitializer接口,然后通过覆盖该接口里面的方法将我们的配置类告诉SpringMVC,即可完成对SpringMVC在Tomcat启动初始化过程中进行扩展,后续SpringMVC启动时就知道我们配置了什么信息,这就相当于把原来在web.xml里面做的工作移到了我们的配置类里面来完成,所以就可以废除web.xml文件;
    【Spring】四、Spring MVC 源码分析

以上步骤进一步总结补充如下;

1、SPI机制

SPI即service privider interface,是JDK为提供的一种扩展和解耦机制。
SPI的具体规范为:当服务的提供者提供了服务接口的一种实现后,在jar包的META-INF/services/目录下创建一个以包名+接口名命名的文件,该文件就是服务接口的具体实现类,而当外部程序加载这个jar包的时候就能通过META-INF/services/里的配置文件找到接口的具体实现类,基于这样一个约定规则就能避免在代码中指定和写死,并且jdk提供服务接口的实现类查找工具:java.util.ServiceLoader帮助我们完成查找;

2、spring-web中的具体应用

从servlet 3.0开始,web容器启动时为第三方组件提供了做一些初始化工作的机会,例如注册servlet或者filter等,servlet规范中通过ServletContainerInitializer实现此功能,每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么当web容器启动时就会运行这个初始化器做一些组件的初始化工作;
一般伴随着ServletContainerInitializer一起使用的还有@HandlesTypes注解,通过@HandlesTypes注解可以将感兴趣的一些类作为参数注入到ServletContainerInitializerde的onStartup方法中;
spring-web的jar定义了一个ServletContainerInitializerde的具体实现类,SpringServletContainerInitializer,并且在META-INF/services/目录下定义了如下文件:
【Spring】四、Spring MVC 源码分析

文件的具体的内容为:
org.springframework.web.SpringServletContainerInitializer。

3、SpringServletContainerInitializer

配合注解@HandlesTypes它可以将其指定的Class对象作为参数传递到onStartup方法中,进而在onStartup方法中获取Class对象的具体实现类,进而调用实现类中的具体方法,SpringServletContainerInitializer类中@HandlesTypes指定的是Class对象是WebApplicationInitializer,
利用这个机制,只要实现WebApplicationInitializer这个接口,我们就可以自定义注入Servlet,或者Filter,也可以做其他扩展,即可以不再依赖web.xml的配置;

SpringMVC注册版本启动源码分析

1SpringServletContainerInitializer.onStartup(Set<?> WebApplicationInitializer, ServletContext)2WebApplicationInitializer.onStartup(ServletContext)3AbstractDispatcherServletInitializer.onStartup(ServletContext) 4、注册ContextLoaderListenerregisterContextLoaderListener(servletContext);5、创建AnnotationConfigWebApplicationContextSpring父容器)6、把配置类RootConfig.class放在AnnotationConfigWebApplicationContext的annotatedClasses集合中;7、创建ContextLoaderListener监听器对象,把AnnotationConfigWebApplicationContextSpring父容器)赋给ContextLoaderListener的context8、把ContextLoaderListener监听器添加到ServletContext中;把监听器添加到servlet的上下文中,这是为后续启动埋下伏笔,到时候tomcat启动的某个步骤会调用监听器的方法;9、注册DispatcherServletregisterDispatcherServlet(servletContext);10、创建AnnotationConfigWebApplicationContext(spring子容器)11、把配置类WebServletConfig.class放在AnnotationConfigWebApplicationContext的annotatedClasses集合中;12、创建DispatcherServlet,把第11步创建的AnnotationConfigWebApplicationContext赋值给FrameworkServlet里面的webApplicationContext13、把创建的DispatcherServlet加入到servletContext上下文中,ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);并设置servlet优先级为1,并且添加servlet的mapping映射为斜杠/14、进入到Tomcat源码,调用事件监听器ServletContextListener,而在第8步已经往ServletContext上下文中添加了ServletContextListener监听器,此时调用ServletContextListener监听器的contextInitialized(..)方法;15、执行initWebApplicationContext(event.getServletContext());16、配置和刷新WebApplicationContextconfigureAndRefreshWebApplicationContext(cwac, servletContext);17、刷新和创建Spring容器,里面就是spring ioc的12个步骤wac.refresh();18、然后进入到tomcat源码,执行loadOnStartup(findChildren())19、加载执行DispatchServlet的init方法,DispatchServlet没有init方法,执行父类HttpServletBean的init方法20、执行FrameworkServletinitServletBean()方法初始化WebApplicationContext容器(spring子容器),里面又是走spring ioc的12个步骤;两个容器里面的对象是重复的,但是两个容器里面的每个单例对象不是同一个对象,不equals(),子容器里面有一个父容器parentBeanFactory,如果一个bean在子容器里面拿不到,那么就会去父容器获取,如果都获取不到,那就报错;在实际开发中,也可以只配置一个容器,这样项目启动会更快,占用更少内存;

Spring MVC 请求处理

  • 1、请求入口是servlet规范中Servlet接口的service()方法;
  • 2、子类HttpServlet实现GenericServlet这个servlet的service()方法,根据请求类型转换成doGet,doPost,doPut,dDelete方法去执行;
  • 3、Spring mvc写了一个DispatcherServlet以及其父servlet实现了doGet、doPost…等方法,那么此时就相当于一个页面的请求会首先转发到DispatcherServlet或者其父servlet中其中的某一个方法去执行,通过断点跟踪我们发现是进入了FrameworkServlet的doGet()方法;
  • 4、FrameworkServlet的doGet()方法 -->调用
    processRequest(request, response); -->再调用doService(request, response); -->调用doDispatch(request, response); 该方法里面有三个核心步骤;
    • 4.1. 确定当前请求的HandlerExecutionChain执行链(也就是controller及其执行方法,是一个请求方法映射对象),也就是拿到controller的信息
    • 4.2 获取到一个handler的适配器对象;(其实也没有做什么,就是一个适配器对象)
    • 4.3 通过handler的适配器对象调用controller的方法执行请求处理,里面主要是反射调用controller的方法;
    • 4.4 渲染视图进行请求跳转;
      以上代码在我们的spring中文注释版的源码中都标注了蓝色字体,可以课后多加阅读,提高源码阅读能力;

SpringMVC 的工作原理

【Spring】四、Spring MVC 源码分析

  • 1)浏览器提交请求到中央调度器。
  • 2)中央调度器直接将请求转给处理器映射器。
  • 3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。
  • 4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器
  • 5)处理器适配器调用处理器。
  • 6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器。
  • 7)处理器适配器直接将结果返回给中央调度器。
  • 8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。
  • 9)视图解析器将封装了的视图对象返回给中央调度器
  • 10)中央调度器调用视图对象,让其自己进行渲染,完成数据填充,形成响应对象。
  • 11)最终中央调度器响应浏览器。

Spring MVC总结

Spring MVC底层是servlet,是对servlet的封装,所有的请求首先到达DispatcherServlet,DispatcherServlet这个servlet里面从spring容器中获取controller,基于请求路径的映射,把请求转发到controller的各个方法上;
传统的基于servlet开发mvc模式的项目是如下流程:
请求 --》 servlet --》 调service --》调dao --》 查数据库
|
把数据放入request.setAttribute()
|
在servlet中通过forwoard跳转到页面
由于一个servlet只能对应一个 url请求映射,所以
/user/login 需要写一个servlet
/uer/register 又需要写一个servlet
如果采用servlet开发项目,导致项目中的servlet类爆炸式增长,所以spring mvc对servlet进行了封装,spring mvc变成了如下的模式:
请求 --》 servlet --》controller --》调service --》调dao --》 查数据库
|
把数据放入request.setAttribute()
|
servlet中forwoard跳转到页面
这样以来,servelt只需要写一个,所有请求首先都是该servlet接收,然后该servlet把请求映射到不同的controller方法进行处理;

天天排行榜