SpringBoot源码剖析-自动配置SpringMVC
2022/1/15 22:03:52
本文主要是介绍SpringBoot源码剖析-自动配置SpringMVC,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
在上一小节,我们介绍了 SpringBoot 是如何启动一个内置 tomcat 的。我们知道我们在 SpringBoot 项目里面是可以直接使用诸如 @RequestMapping 这类的 SpringMVC 的注解,那么同学们会不会奇 怪,这是为什么?我明明没有配置 SpringMVC 为什么就可以使用呢? 其实仅仅引入 starter 是不够的,回忆一下,在一个普通的 WEB 项目中如何去使用 SpringMVC ,我 们首先就是要在 web.xml 中配置如下配置![](/images/baidian.png)
但是在 SpringBoot 中,我们没有了 web.xml 文件,我们如何去配置一个 Dispatcherservlet 呢? 其实 Servlet3.0 规范中规定,要添加一个 Servlet ,除了采用 xml 配置的方式,还有一种通过代码的 方式,伪代码如下 :
servletContext . addServlet ( name , this . servlet );那么也就是说,如果我们能动态往 web 容器中添加一个我们构造好的 DispatcherServlet 对象, 是不是就实现自动装配 SpringMVC 了
一. 自动配置DispatcherServlet和DispatcherServletRegistry
springboot 的自动配置基于 SPI 机制,实现自动配置的核心要点就是添加一个自动配置的类, SpringBoot MVC 的自动配置自然也是相同原理。 所以,先找到 springmvc 对应的自动配置类。
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConf iguration
(一)DispatcherServletAutoConfiguration自动配置类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) public class DispatcherServletAutoConfiguration {1 、首先注意到, @Configuration 表名这是一个配置类,将会被 spring 给解析。 2 、 @ConditionalOnWebApplication 意味着当时一个 web 项目,且是 Servlet 项目的时候才会被解 析。 3 、 @ConditionalOnClass 指明 DispatcherServlet 这个核心类必须存在才解析该类。 4 、 @AutoConfigureAfter 指明在 ServletWebServerFactoryAutoConfiguration 这个类之后再解 析,设定了一个顺序。 总的来说,这些注解表明了该自动配置类的会解析的前置条件需要满足。 其次, DispatcherServletAutoConfiguration 类主要包含了两个内部类,分别是 1 、 DispatcherServletConfiguration 2 、 DispatcherServletRegistrationConfiguration 顾名思义,前者是配置 DispatcherServlet ,后者是配置 DispatcherServlet 的注册类。什么是注册 类?我们知道 Servlet 实例是要被添加(注册)到如 tomcat 这样的 ServletContext 里的,这样才能 够提供请求服务。所以, DispatcherServletRegistrationConfiguration 将生成一个 Bean ,负责将 DispatcherServlet 给注册到 ServletContext 中。
(二)配置DispatcherServletConfiguration
我们先看看 DispatcherServletConfiguration 这个配置类@Configuration(proxyBeanMethods = false) @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class }) protected static class DispatcherServletConfiguration {@Conditional 指明了一个前置条件判断,由 DefaultDispatcherServletCondition 实现。主要是判 断了是否已经存在 DispatcherServlet ,如果没有才会触发解析。 @ConditionalOnClass 指明了当 ServletRegistration 这个类存在的时候才会触发解析,生成的 DispatcherServlet 才能注册到 ServletContext 中。 最后, @EnableConfigrationProperties 将会从 application.properties 这样的配置文件中读取 spring.http 和 spring.mvc 前缀的属性生成配置对象 HttpProperties 和 WebMvcProperties 。 再看 DispatcherServletConfiguration 这个内部类的内部代码
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails()); return dispatcherServlet; }
@Bean @ConditionalOnBean(MultipartResolver.class) @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) public MultipartResolver multipartResolver(MultipartResolver resolver) { // Detect if the user has created a MultipartResolver but named it incorrectly return resolver; }这个两个方法我们比较熟悉了,就是生成了 Bean 。 dispatcherServlet 方法将生成一个 DispatcherServlet 的 Bean 对象。比较简单,就是获取一个实 例,然后添加一些属性设置。 multipartResolver 方法主要是把你配置的 MultipartResolver 的 Bean 给重命名一下,防止你不是用 multipartResolver 这个名字作为 Bean 的名字。
(三)配置DispatcherServletRegistrationConfiguration
再看注册类的 Bean 配置@Configuration(proxyBeanMethods = false) @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration {同样的, @Conditional 有一个前置判断, DispatcherServletRegistrationCondition 主要判断了该 注册类的 Bean 是否存在。 @ConditionOnClass 也判断了 ServletRegistration 是否存在 @EnableConfigurationProperties 生成了 WebMvcProperties 的属性对象 @Import 导入了 DispatcherServletConfiguration ,也就是我们上面的配置对象。 再看 DispatcherServletRegistrationConfiguration 的内部实现
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; }内部只有一个方法,生成了 DispatcherServletRegistrationBean 。核心逻辑就是实例化了一个 Bean ,设置了一些参数,如 dispatcherServlet 、 loadOnStartup 等 总结 springboot mvc 的自动配置类是 DispatcherServletAutoConfigration ,主要做了两件事: 1 )配置 DispatcherServlet 2 )配置 DispatcherServlet 的注册 Bean(DispatcherServletRegistrationBean)
二. 注册DispatcherServlet到ServletContext
在上一小节的源码翻阅中,我们看到了 DispatcherServlet 和 DispatcherServletRegistrationBean 这两个 Bean 的自动配置。 DispatcherServlet 我们很熟悉, DispatcherServletRegistrationBean 负 责将 DispatcherServlet 注册到 ServletContext 当中(一)DispatcherServletRegistrationBean的类图
既然该类的职责是负责注册 DispatcherServlet ,那么我们得知道什么时候触发注册操作。为此, 我们先看看 DispatcherServletRegistrationBean 这个类的类图(二)注册DispatcherServlet流程
1. ServletContextInitializer
我们看到,最上面是一个 ServletContextInitializer 接口。我们可以知道,实现该接口意味着是用 来初始化 ServletContext 的。我们看看该接口public interface ServletContextInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
2. RegistrationBean
看看RegistrationBean是怎么实现onStartup方法的
@Override public final void onStartup(ServletContext servletContext) throws ServletException { //获取当前到底是一个filter 还是一个servlet 还是一个listener String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); }调用了内部 register 方法,跟进它
3. DynamicRegistrationBean
再看 DynamicRegistrationBean 是怎么实现 register 方法的@Override protected final void register(String description, ServletContext servletContext) { D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } configure(registration); }跟进 addRegistration 方法
protected abstract D addRegistration(String description, ServletContext servletContext);
4.ServletRegistrationBean
再看 ServletRegistrationBean 是怎么实现 addRegistration 方法的@Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); //负责将DispatcherServlet注册到servletContext中 return servletContext.addServlet(name, this.servlet); }我们看到,这里直接将 DispatcherServlet 给 add 到了 servletContext 当中。
(三)SpringBoot启动流程中具体体现
这段代码其实就是去加载 SpringMVC ,那么他是如何做到的呢? getSelfInitializer() 最终会 去调用到 ServletWebServerApplicationContext 的 selfInitialize 方法,该方法代码如下getSelfInitializer().onStartup(servletContext);
![](/images/baidian.png)
private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }我们通过调试,知道 getServletContextInitializerBeans() 返回的是一个 ServletContextInitializer 集合,集合中有以下几个对象
![](/images/baidian.png)
然后依次去调用对象的 onStartup 方法,那么对于上图标红的对象来说,就是会调用到 DispatcherServletRegistrationBean 的 onStartup 方法,这个类并没有这个方法,所以最终 会调用到父类 RegistrationBean 的 onStartup 方法,该方法代码如下
@Override public final void onStartup(ServletContext servletContext) throws ServletException { //获取当前到底是一个filter 还是一个servlet 还是一个listener String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); }这边 register(description, servletContext) ; 会调用到 DynamicRegistrationBean 的 register 方法,代码如下:
@Override protected final void register(String description, ServletContext servletContext) { D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } configure(registration); }addRegistration(description, servletContext) 又会调用到 ServletRegistrationBean 中的 addRegistration 方法,代码如下 :
@Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); //负责将DispatcherServlet注册到servletContext中 return servletContext.addServlet(name, this.servlet); }看到了关键的 servletContext.addServlet 代码了,我们通过调试,即可知到 this.servlet 就 是 dispatcherServlet
![](/images/baidian.png)
总结 SpringBoot 自动装配 SpringMvc 其实就是往 ServletContext 中加入了一个 Dispatcherservlet 。 Servlet3.0 规范中有这个说明,除了可以动态加 Servlet, 还可以动态加 Listener , Filter
- addServlet
- addListener
- addFilter
这篇关于SpringBoot源码剖析-自动配置SpringMVC的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-07-02springboot项目无法注册到nacos-icode9专业技术文章分享
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现
- 2024-06-03为什么以及如何要进行架构设计权衡?
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)