解析springboot自动配置springmvc的秘密之DispatcherServlet
Posted kingtopest
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解析springboot自动配置springmvc的秘密之DispatcherServlet相关的知识,希望对你有一定的参考价值。
我们知道springboot虽说简化了spring那套繁琐的xml文件配置,但是springboot的底层本质上还是spring和springmvc的那套东西。所以提升开发内功,不能仅仅只是停留在使用的层面,还需要深入了解springboot背后运作的底层原理。
所以,今天来谈谈springboot是如何自动配置springmvc的,当然重点是DispatcherServlet。
因为DispatcherServlet是springmvc的五大核心组件最重要的一个组件,用户请求首先会经过DispatcherServlet前端控制器,然后由它分发给其他组件进行处理:
另外我们知道在springmvc开发中通过@Controller注解就可以简化servlet的开发,但是这里有一个重要的规则需要提前了解:
Controller要能工作,需要两个条件:
1. 至少有一个DispatcherServlet,注意:不能是空构造的DispatcherServlet,而且必须是跟WebAppContext绑定的
2. Controller和DispatcherServlet必须在同一个IOC容器 这两个条件必须同时满足, 否则即便controller注解被扫描到还是会报404错误
所以springmvc要能运行需要有一个DispatcherServlet的bean, 也就是前端控制器。
传统springmvc开发通常需要有一个web.xml文件对它进行配置,基本格式如下如下:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 配置springmvc配置文件的位置 -->
<!-- 注意: contextConfigLocation不是必须的, 如果不配置contextConfigLocation,
springmvc的配置文件则默认为:WEB-INF/servlet的name+"-servlet.xml" -->
<param-name>contextConfigLocation</param-name>
<!-- springmvc配置文件的路径 -->
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--Servlet默认是在第1次访问才创建,但配置load-on-startup为1则会在tomcat启动时提前创建 -->
<load-on-startup>1</load-on-startup> <!--启动级别为1 -->
</servlet>
<!-- servlet映射声明 -->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- 定义url拦截规则,未匹配到则报404错误 -->
</servlet-mapping>
springmvc通过编程方式实现xml零配置,有以下两种方式:
1. 实现WebApplicationInitializer接口(原理是spi机制),并重写其onStartup方法:
2. 继承类AbstractAnnotationConfigDispatcherServletInitializer,并重写其getRootConfigClasses方法, getServletConfigClasses方法。
那么springboot又是如何自动加载DispatcherServlet,然后又如何add到tomcat的ServletContext上下文的呢?
因为从本质上说DispatcherServlet仍然是一个servlet,那么就会涉及到serlvet的生成以及如何绑定到tomcat上下文context。
下面从springboot源码开始分析这个过程:
首先是DispatcherServlet bean对象的自动生成和配置:
1. 查看org.springframework.boot.autoconfigure依赖jar包META-INF/spring.factories文件。
备注:springboot的SPI机制跟Servlet 3.0规范的spi略有不同,它查找的是META-INF/spring.factories文件,具体实现细节,会单独开一篇文章来讲,不在本章的范围
spring.factories文件里面有DispatcherServletAutoConfiguration,也就是说这个类会在springboot启动时被加载:
DispatcherServletAutoConfiguration这个配置类从上面的注解都可以看出来:是对dispatcherServlet进行自动配置的,上面的一些条件注解大致也猜的出来:必须是servlet类型才会生效。
这个里面还有个内部类:DispatcherServletRegistrationConfiguration
下面重点看这个内部类里面的两处代码:
a. 自动生成dispatcherServlet bean对象
b. ,生成一个用于注册dispatcherServlet的bean对象DispatcherServletRegistrationBean:
然后查看一下DispatcherServletRegistrationBean类图层级结构:
发现ServletContextInitializer是其顶级父级接口,而且这是一个函数式接口:有且仅有一个抽象方法,这种情况通常需要通过匿名内部类或者lambda表达式重写该方法。
那么看看这个接口里面有什么方法:
这个接口只有一个onStartup方法, 而且方法说明的大概意思是:将servlet,过滤器,监听器等参数加入到ServletContext上下文,进行初始化配置。
因为springboot的web容器通常是tomcat, 这里可以理解为将servlet,过滤器,监听器等参数加入到tomcat上下文
然后根据之前的DispatcherServletRegistrationBean类图层级结构,应选择onStartup方法的实现类是:RegistrationBean:
然后查看RegistrationBean的onStartup方法实现,这里面有个register方法
而register是一个抽象方法,需要进一步看看子类DynamicRegistrationBean的具体实现
子类DynamicRegistrationBean中register方法中:重点看addRegistration方法:
继续跟进addRegistration方法,发现该方法为抽象方法,所以继续跟进子类ServletRegistration的实现:
可以看到ServletRegistration中的addRegistration方法最终会将servlet添加到tomcat上下文
而ServletRegistration是DispatcherServletRegistrationBean的父类,
DispatcherServletRegistrationBean会通过它的构造方法中的super方法将DispatcherServlet传递给ServletRegistration,所以最终DispatcherServlet也会通过addRegistration方法添加到tomcat上下文
阶段性总结一下: DispatcherServletRegistrationConfiguration会完成两件事:
a. 创建DispatcherServlet bean对象
b. 创建DispatcherServletRegistrationBean对象,这个对象的作用是:外部调用能够通过其顶级父接口ServletContextInitializer的onStartup方法最终层层调用到ServletRegistration中的addRegistration方法:最终将DispatcherServlet添加到tomcat上下文
那么问题来了:这个onStartup方法是怎么触发的
跟踪springboot的启动代码,最终会追踪到ServletWebServerApplicationContext的createWebServer方法(由于过程篇幅较长,这里就不展开了)
这里特别关注一下getSelfInitializer方法:
这里可以看到:getSelfInitializer实际返回的是ServletContextInitializer对象,通过selfInitialize方法重写了ServletContextInitializer的onStartup方法(this::selfInitialize是lambda表达式,通过它重写函数式接口的方法)
selfInitialize方法内部恰好有一段for循环遍历:调用了ServletContextInitializer的onStartup方法。
从debug调试的结果来看这个for循环中getServletContextInitializerBeans返回的集合当中果然包含了DispatcherServletRegistrationBean,那么根据之前的分析,最终必然会从其顶级父接口ServletContextInitializer的onStartup方法层层调用,一直到==》ServletRegistration中的addRegistration方法,将DispatcherServlet添加到tomcat的上下文。
但问题是:getServletContextInitializerBeans方法是如何生成DispatcherServletRegistrationBean的,所以继续跟进这个方法,跳到ServletContextInitializerBeans方法:
这个方法代码很长:但是重点只有两个:
1. ServletContextInitializerBeans的构造方法是如何生成一个ServletContextInitializer集合的
2. DispatcherServletRegistrationBean是如何生成的
第一个问题其实很隐蔽,不容易发现。
通过仔细分析,发现ServletContextInitializerBeans继承了AbstractCollection,而AbstractCollection实现了Collection接口。那么突然灵光一闪:既然ServletContextInitializerBeans是一个集合,那么它必然有iterator迭代器属性,会不会是重写了iterator迭代器方法
然后发现ServletContextInitializerBeans果然重写了iterator迭代器方法!
返回值是sortedList, 那么也就是说ServletContextInitializerBeans构造方法返回的集合其实就是sortedList, 这才是问题的关键!
@Override
public Iterator<ServletContextInitializer> iterator()
return this.sortedList.iterator();
而sortedList本身就是ServletContextInitializer的List集合
继续跟进ServletContextInitializerBeans构造方法中的addServletContextInitializerBeans方法
接着跟进addServletContextInitializerBeans方法中的getOrderedBeansOfType方法:
在 getOrderedBeansOfType方法中可以看到:通过beanFactory.getBeanNamesForType方法,就能将IOC容器中所有已加载的bean对象获取出来(本质还是通过反射)
然后回顾一下本文的开头:DispatcherServletRegistrationConfiguration类中已经自动生成了DispatcherServletRegistration的bean对象
所以getOrderedBeansOfType方法必然可以通过反射获取到DispatcherServletRegistrationBean对象
然后回到addServletContextInitializerBeans方法,打个断点看看getOrderedBeansOfType方法的返回值:
可以看到这里果然返回了DispatcherServletRegistrationBean对象,正好印证了上面的观点。
然后层层跟进addServletContextInitializerBean方法:
可以看到: 最终DispatcherServletRegistrationBean会添加到initializers集合
而这个initializers会影响到sortedList, 也就是ServletContextInitializerBeans重写的iterator迭代器返回的集合。
这也就是为什么通过构造一个ServletContextInitializerBeans实例,就能返回一个包含DispatcherServletRegistrationBean集合的原因
然后getServletContextInitializerBeans方法就能获取到包含DispatcherServletRegistrationBean的集合,然后就能从其顶级父接口ServletContextInitializer的onStartup方法开始层层调用,一直到==》ServletRegistration中的addRegistration方法,将DispatcherServlet添加到tomcat的上下文。
SpringBoot——MVC自动配置原理
目录
Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。
官方文档描述:
自动配置在 Spring 的默认值之上添加了以下功能:
从官方描述解析:
If you want to keep Spring Boot MVC features and you want to add
additionalMVC configuration (interceptors, formatters, view controllers,
and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
扩展SpringMVC
由于WebMvcConfigurer是个接口
创建一个MyMvcConfig实现这个接口
之前我们学的视图解析器是我们手动配置
ViewResolver 实现了视图解析器接口的类,就看做是视图解析器
搜索ContentNegotiatingViewResolver,找到如下方法resolveViewName!找到对应的视图解析代码
从 getCandidateViews中看到它是把所有的视图解析器拿来,进行遍历循环
结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
可以看出它是从容器中去找视图解析器,我们也可以在容器中实现一个视图解析器!
容器中实现一个视图解析器
1、我们在主程序中去写一个视图解析器;
2、如何看我们自己写的视图解析器是否起了作用呢?
我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中!
3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息,找到this
4、找到视图解析器,我们看到我们自己定义的就在这里了;
所以说,我们如果想要使用diy定制的东西,我们只需要给容器中添加这个组件就好了,SpringBoot就会帮我们自动装配!
转换器和格式化器
找到格式化转换器:
点进去可以看到
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:可以看到在我们的Properties文件中,我们可以进行自动配置它!
修改SpringBoot的默认配置
结论:我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
分析一下原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
这个父类中有这样一段代码:
4、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
5、我们点进去看一下
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用
全面接管SpringMVC
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们开发中,不推荐使用全面接管SpringMVC
1、这里看到它是导入了一个类,继续点进去看
2、它继承了一个父类 WebMvcConfigurationSupport
3、回顾一下Webmvc自动配置类
总结:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
以上是关于解析springboot自动配置springmvc的秘密之DispatcherServlet的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot | 4.1 SpringMVC的自动配置 #yyds干货盘点#
解读SpringBoot和SpringMVC中配置类的@Impot等导入是如何解析的