SpringBoot自动配置SpringMVC原理

从前面学习SpringBoot自动配置原理我们知道,在SpringBoot启动加载时会预先从指定配置文件中读取可以配置的Config类,这些Config配置类可以在工程中通过尽可能少的配置就能让常用的框架组件发布并使用起来,那么SpringBoot是怎么帮我们做到这一点的呢?由于可配置的Config非常多,我们就选择我们最熟悉的WebMVC对应的Config进行详细解读,学习之后就可以自行举一反三了。

(一)SpringBoot怎么自动配置SpringMVC?

在学习SpringBoot怎么自动配置各种框架之前,我们先回想一下SpringBoot预先读取的可配置Config类有哪个是与WebMVC相关的?经过比对我们发现在Key=EnableAutoConfiguration,Value=WebMvcAutoConfiguration就是关于SpringBoot自动配置WebMVC的可配置类,关于SpringBoot自动配置WebMVC的玄机也正是在这个配置类里面。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

SpringBoot自动配置SpringMVC原理_第1张图片

于是,我们必须好好看一下这个可配置类到底做了什么事情,能让SpringBoot启动完成之后也自动初始化好了一个WebMVC的环境。

SpringBoot自动配置SpringMVC原理_第2张图片

点开可以看到这个可配置类上有很多注解修饰,这些注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类。关于这些注解和文字解释如下:

//声明为配置类,并且Bean的方法不进行代理
@Configuration(proxyBeanMethods = false)

//判断当前环境是一个SERVLET环境,也就是说是Web环境下这个配置类才生效
@ConditionalOnWebApplication(type = Type.SERVLET)

//判断当前环境是否含有Servlet,DispatcherServlet,WebMvcConfigurer实例(前面SpringMVC章节零XML方式介绍的接口,
//类似引入web.xml的Java实例),这些都是WebMVC必不可少的组件,只有这些都存在这个配置类才生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

//判断当前环境是否存在WebMvcConfigurationSupport,如果不存在这个配置类才生效
//为什么这里不存在WebMvcConfigurationSupport时才能让配置类生效呢?因为这个接口是一个自定义配置WebMVC的接口,
//如果实现了这个接口就意味着开发者自己手动进行了webMVC的配置,那么SpringBoot就不再帮你自动配置了,防止了配置冲突。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

//自动配置顺序:数值越低越优先配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)

//WebMVC配置的核心注解,主要是对DispatcherServlet进行Java配置,以及对任务执行和校验器进行Java配置。
//当这个配置类生效之后,就会接着进行DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,
//ValidationAutoConfiguration的配置,重点看DispatcherServletAutoConfiguration
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
      ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    .....
}

从上面可以看到,SpringBoot要想自动配置WebMVC环境,那么是需要满足上述注解定义的一些条件:

(1)处于Web环境下(2)容器中已经初始化好了WebMVC必须的组件(3)用户没有自己手工配置过WebMVC

这些条件都满足后,SpringBoot就会为我们自动配置一个WebMVC环境,但是这个环境是怎么样的呢(比如采用什么容器,端口号是什么,DispatchServlet怎么配置,Resover怎么配置,Converter怎么配置,等等)?这个就是最后一个注解@AutoConfigureAfter内声明的来定义了,实际上也就是由DispatcherServletAutoConfiguration.class通过Java零XML配置方式在代码里为我们提前写好定义的环境。

对于核心DispatcherServlet的自动配置,点开这个核心DispatcherServletAutoConfiguration.class

SpringBoot自动配置SpringMVC原理_第3张图片

可以看到这个DispatcherServlet自动配置类也有很多注解修饰:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
//如果环境中已经有了用户自己配置的DispatcherServlet,就不再自动配置
@ConditionalOnClass(DispatcherServlet.class)
//在这个DispatcherServlet自动配置之后,通过ServletWebServerFactoryAutoConfiguration对web容器进行配置
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

在这个DispatcherServlet自动配置类中,最核心的就是对DispatcherServlet的配置,可以看到对DispatcherServlet配置的核心代码在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过@Bean返回给Spring容器:

SpringBoot自动配置SpringMVC原理_第4张图片

从这个方法内部我们就看出了,SpringBoot自动配置DispatcherServlet时是直接new的,然后设置各种参数最终通过@Bean交给Spring容器。在上面的方法执行结束将一个DispatcherServlet交给Spring容器后,接下来会调用静态内部类DispatcherServletRegistrationConfiguration的dispatcherServletRegistration方法,对DispatcherServlet的参数进行配置:

SpringBoot自动配置SpringMVC原理_第5张图片

这里设置DispatcherServlet名字为dispatcherServlet;启动顺序是默认值-1(使用时加载);请求接收路径是/;配置完成后交给Spring容器,这就是对DispatcherServlet的Java代码配置了。

不过这里有一个问题,就是这里返回给Spring容器的是一个DispatcherServletRegistrationBean,那么Tomcat等Web容器时怎么将这个DispatcherServletRegistrationBean内包含的DispatcherServlet信息解析出来并生效运行呢?这个知识点有必要做一下说明。

首先看这个DispatcherServletRegistrationBean的构造是怎么样的:

这个DispatcherServletRegistrationBean继承了ServletRegistrationBean,这个ServletRegistrationBean是携带了供Tomcat容器解析加载的DispatcherServlet,让我们看下这个ServletRegistrationBean的继承关系图:

SpringBoot自动配置SpringMVC原理_第6张图片

就可以知道这个DispatcherServletRegistrationBean实际最终是实现了ServletContextInitializer接口,看到这里如果前面对Servlet的SPI有所了解很快就能反应过来,这里是有一个onStartup方法通过SPI机制让Tomcat启动时能自动调用到onStartup里面来,通过下面调用链最终Spring容器进行了addServlet操作将DispatchServlet添加到Spring容器中并生效,最后进行config方法对DispatcherServlet进行设置:

SpringBoot自动配置SpringMVC原理_第7张图片

SpringBoot自动配置SpringMVC原理_第8张图片

SpringBoot自动配置SpringMVC原理_第9张图片

SpringBoot自动配置SpringMVC原理_第10张图片

但是读了上面的源码又引发了两个很值得深思的问题:

(1)在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过new一个DispatcherServlet之后直接用@Bean返回给Spring容器了。在学习SpringMVC时我们知道DispatcherServlet需要传入一个ApplicationContext上下文,如果没有传递则会去默认配置文件解析出一个上下文。但是这里这个DispatcherServlet却没有关联任何Spring上下文的地方却能使DispatcherServlet起作用,那这个关联操作到底是在哪里进行的呢?

这需要我们看SpringBoot中DispatcherServlet的源码是怎么写的,让我们看下有参的构造是怎么样的,因为无参构造只是一个空方法:

SpringBoot自动配置SpringMVC原理_第11张图片

SpringBoot自动配置SpringMVC原理_第12张图片

使用IDEAJ的查找工具,看下这个this.webApplicationContext在哪些地方被设置,可以找到是在setApplicationContext方法内进行设置的:

SpringBoot自动配置SpringMVC原理_第13张图片

看到这个重写方法以及方法注释,根据Spring的知识我们应该猜想到这个类应该是实现了ApplicationContextAware接口。ApplicationContextAware接口是做什么的呢?这个接口是当Spring容器初始化结束之后,实现了ApplicationContextAware接口的实现类就会被调用并且执行setApplicationContext方法。回到这里也就是说当Spring容器初始化完成之后,由于实现了ApplicationContextAware接口,于是会执行setApplicationContext方法,在这个方法中将初始化完成的Spring上下文赋值给this.webApplicationContext变量(这个变量就是DispatchServlet内部的Spring上下文变量),于是就解释了为什么SpringBoot在new一个DispatchServlet时不需要传入Spring上下文的原因。

(2)理解了上面的问题和答案,就引出第二个问题:

前面学习SpringMVC时知道对DispatcherServlet的配置是将Spring容器对象作为参数传给DispatcherServlet;

但是SpringBoot自动配置WebMVC时却是将DispatcherServlet对象传给Spring容器;

这两种场景的实现是反过来的,为什么会这样设计呢?其实这两种方式的区别就在于它们分别是怎么接在Spring容器的。

回答为什么SpringBoot是将DispatchServlet传给Spring容器的问题,就要结合上面第一个问题的解析。这是因为要调用实现了ApplicationContextAware接口的实现类,则必须保证这个实现类在Spring容器当中才能生效。也就是说SpringBoot中实现了ApplicationContextAware接口的DispatchServlet要想触发接口方法,则必须作为一个Bean存在于Spring容器中,这就是SpringBoot为什么要将DispatchServlet作为Bean传入到Spring容器中的原因了。

上面介绍完SpringBoot对DispatchServlet进行自动配置的细节,那么还为我们做了哪些组件配置呢?这里要回过来看WebMvcAutoConfiguration

在这个自动配置类中,定义了一个静态内部类WebMvcAutoConfigurationAdapter,实现了WebMvcConfigurer接口。这个就和之前学习SpringMVC应用时介绍的一样,通过实现WebMvcConfigurer接口就可以拥有一个类似web.xml功能,可以对其它组件进行配置操作:

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer

在这个里面实现了默认的Resolver,MessageConverters,ViewResolver等组件。如果我们要添加自定义的组件怎么办呢?对于SpringMVC而言我们必须在方法中将这个自定义的组件添加到对应组件集合中,但是SpringBoot对此进行的拓展,使得我们可以直接通过@Bean的方式就可以添加自定义组件(这一点在SpringMVC是做不到自定义添加的):

SpringBoot自动配置SpringMVC原理_第14张图片

总结一下SpringBoot除了自动配置DispatchServlet之外,其它的配置有

spring boot会默认注入一个视图解析器:ContentNegotiatingViewResolver 
主要做2个事情:
1:整合所有的视图解析器
2: 遍历所有的视图解析器选一个最佳的方案

spring boot 
会再当前的spring容器里面找到所有HttpMessageConverter类型的Bean 封装成一个集合
1:spring boot会自己注入一个默认的实现  jackson
2:如果用户需要自己配置的话 只需要@Bean

(二)SpringBoot实现一个字符串到日期的参数转换器:

每当前端要将字符串日期传递到后台之后,要自动转换成日期格式类型时,可以写一个Converter进行转换。

SpringBoot自动配置SpringMVC原理_第15张图片

SpringBoot自动配置SpringMVC原理_第16张图片

SpringBoot自动配置SpringMVC原理_第17张图片

至此,关于SpringBoot自动配置WebMVC的源码原理就分享到这里了。

 * 以springmvc自动装配为例看底层源码细节:配置类WebMvcAutoConfiguration.
 * 这个配置类有多个注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类,
 * 其中注解@AutoConfigureAfter中DispatcherServletAutoConfiguration是自动装配的核心类,
 * 这里主要对DispatcherServlet进行配置,和servlet被tomcat调用onStartup()一样,
 * 需要先new出DispatcherServlet并放入Spring容器,然后在tomcat调用前进行addXXX参数的操作,
 * 在springboot实现的javaConfig里,filter,listener,DispatcherServlet,servlet这些servler子类都用XXXRegistrationBean来封装,
 * 这些XXXRegistrationBean都实现了RegistrationBean超类(这个超类拥有onStartup方法),于是保证这些Bean都可以被  tomcat识别并执行,在执行各种不同类型的Bean时,会调用不同的register或configure方法进行add各自不同参数的方法,
 * 从而实现了各种servlet组件参数的自动配置和加载步骤。对于web容器的配置是ServletWebServerFactoryAutoConfiguration配置类中进行主要对web容器类型IP端口连接数等参数进行配置。

 

你可能感兴趣的:(SpringBoot专栏)