但是在 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);
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
我们通过调试,知道
getServletContextInitializerBeans()
返回的是一个
ServletContextInitializer
集合,集合中有以下几个对象
然后依次去调用对象的 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
总结 SpringBoot 自动装配 SpringMvc 其实就是往 ServletContext 中加入了一个 Dispatcherservlet 。 Servlet3.0 规范中有这个说明,除了可以动态加 Servlet, 还可以动态加 Listener , Filter
- addServlet
- addListener
- addFilter