传统的spring-mvc应用中,需要在web.xml中配置DispatcherServlet并配置好映射的路径。Spring Boot以的最大特点就是自动配置,用户可以开箱即用。那Spring Boot内部是怎么配置的呢?本文就来分析一下这个问题。
本文关注两个问题:
- DispatcherServlet对象是怎么被创建的?
- DispatcherServlet是怎么注册的?
创建DispatcherServlet对象
Spring Boot提供了DispatcherServletAutoConfiguration这个自动配置类,在该类中会向bean工厂中注册DispatcherServletbean对象。
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
/*
* 创建DispatcherServlet,并将dispatchServlet注入到容器中。
*/
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
// 通过直接new创建DispatcherServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// 下面是配置dispatcherServlet的各项属性
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.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
// 处理容器中存在MultipartResolver实例,但是名字不叫作multipartResolver的情况,这里就只是起一个bean重命名的操作
return resolver;
}
}
这部分内容比较简单,后续会从bean工厂中获取该对象并进行注册。
注册DispatcherServlet
同样是在DispatcherServletAutoConfiguration这个自动配置类中,会向bean工厂中注入一个DispatcherServletRegistrationBean类型的实例,从名称可以看出来,该bean是负责servlet注册的。
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
/*
* 注入dispatcherServletRegistration,这是一个ServletContextInitializer。
* 主要负责servlet(在这里是dispatcherServlet)的注册
*/
@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);
// 设置启动时加载顺序,这里setLoadOnStartup方法的实参是-1
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
super(servlet);
Assert.notNull(path, "Path must not be null");
this.path = path;
super.addUrlMappings(getServletUrlMapping());
}
private static final String[] DEFAULT_MAPPINGS = { "/*" };
private T servlet;
private Set<String> urlMappings = new LinkedHashSet<>();
private boolean alwaysMapUrl = true;
private int loadOnStartup = -1;
private MultipartConfigElement multipartConfig;
public ServletRegistrationBean() {
}
public ServletRegistrationBean(T servlet, String... urlMappings) {
this(servlet, true, urlMappings);
}
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
Assert.notNull(servlet, "Servlet must not be null");
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.servlet = servlet;
this.alwaysMapUrl = alwaysMapUrl;
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
在理解下面的过程之前,最好梳理一下DispatcherServletRegistrationBean类的继承结构。
那这个bean是怎么进行注册的呢?注意这个bean是ServletContextInitializer的子类型的,在创建Web服务器一文中讲到,Spring Boot在调用servlet的web服务器工厂的getWebServer方法时会传入一个ServletContextInitializer,当然这里不是直接传递的DispatcherServletRegistrationBean。Spring在这里做了一层封装,向Web服务器工厂传递的是一个叫作getSelfInitializerlambda函数,而在该lambda函数中才会调用bean工厂中其他的ServletContextInitializer。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// 如果webServer为null,则新建一个。一般都是这种情况。
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
// 获取WebServer工厂,这里关注TomcatServletWebServerFactory就是了
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 创建WebServer实例,并传入ServletContainerInitializer
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
// 向容器中注入两个bean
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer)); // 负责设置running标志位和停止WebServer
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer)); // 负责实际启动和停止WebServer
}
else if (servletContext != null) { // 如果servletContext对象存在
try {
// 则通过SCI来初始化
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
// 返回一个SCI对象
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
// 该方法负责初始化servletContext对象
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 设置上下文attr属性
prepareWebApplicationContext(servletContext);
// 向spring容器中注入ServletContextScope
registerApplicationScope(servletContext);
// 将servletContext对象注入到容器中
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 遍历容器中设置的其他Servlet上下文初始化器对象
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 执行Servlet上下文初始化器
beans.onStartup(servletContext);
}
}
可以看到在selfInitialize函数中会调用到bean工厂中的ServletContextInitializerbean,那DispatcherServletRegistrationBean自然也会在这里会被调用到。
本文不介绍servlet容器是怎么调用到selfInitialize这个函数的,先知道在servlet容器初始化好servlet上下文后就会调用它就行了。
回到主线,DispatcherServletRegistrationBean的间接父类RegistrationBean中实现了ServletContextInitializer接口的onStartup方法。
// Servlet上下文初始化后会调用该方法
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
// 执行注册servlet的操作
register(description, servletContext);
}
protected abstract void register(String description, ServletContext servletContext);
这里的register方法是个模板方法,留给子类实现,其子类DynamicRegistrationBean实现了该方法。
@Override
protected final void register(String description, ServletContext servletContext) {
// 执行注册servlet的操作
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
configure(registration);
}
protected abstract D addRegistration(String description, ServletContext servletContext);
这里的addRegistration方法又是同样的套路, 来看子类ServletRegistrationBean的实现。
private static final String[] DEFAULT_MAPPINGS = { "/*" };
private T servlet;
private Set<String> urlMappings = new LinkedHashSet<>();
private boolean alwaysMapUrl = true;
private int loadOnStartup = -1;
private MultipartConfigElement multipartConfig;
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
// 将DispatchServlet添加到ServletContext中
return servletContext.addServlet(name, this.servlet);
}
终于,在这里看到调用了ServletContext的addServlet来注册dispatcherServlet了。注意,上面在创建DispatcherServletRegistrationBean时传递给其构造方法的servlet,会被其传递给父类的构造方法,也就是这里ServletRegistrationBean的构造方法,所以这里this.servlet就是dispatcherServlet。
总结
本文介绍了Spring Boot中DispatcherServlet实例的创建以及注册过程,知道了Spring Boot在背后为我们做了什么事情。文本只是关于DispatcherServlet的开胃小菜,后续会介绍其是怎么处理请求的。