SpringMVC
SpringMVC 是如何将 DispatcherServlet 配置到 Tomcat 中的?
Tomcat 启动流程
略。启动入口 main 方法在 BootStrap 中,一系列初始化后会去解析 web.xml,通过解析 web.xml 就直接实例化 DispatcherServlet 对象了。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- 注册 SpringMVC 的框架 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.example.xxx.config.AppConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
-
通过反射创建 DispatcherServlet 对象
在org.apache.catalina.startup.ContextConfig#configureContext()
方法中会调用wrapper.setServletClass(servlet.getServletClass());
将具体的类名传递给StandardWapper
类,然后在StandardWapper#loadServlet()
中会执行servlet = (Servlet) instanceManager.newInstance(servletClass);
从而生成 DispatcherServlet 对象 -
DispatcherServlet 继承关系
DispatcherServlet 继承 FrameworkServlet 继承 HttpServletBean 继承 HttpServlet 继承 GenericServlet 实现 Servlet 接口 -
给 DispatcherServlet 对象设置值
在ContextConfig#configureContext()
方法调用wrapper.addInitParameter(entry.getKey(), entry.getValue());
,从而得到相关 initParameter。最终通过调用HttpServletBean#init()
方法,进而将 initParameter 设置到 DispatcherServlet 对象上:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
DispatcherServlet#setContextClass(Class<?>)
DispatcherServlet#setContextConfigLocation(String)
contextClass 怎么用
在 FrameworkServlet#createWebApplicationContext(ApplicationContext)
中有用到 getContextClass()
,根据 contextClass
指定的值生成对应的 ApplicationContext
对象,这里是 AnnotationConfigWebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
这是通过 HttpServletBean#init()
调用过来的:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
...
// Let subclasses do whatever initialization they like.
initServletBean();
}
会调到 FrameworkServlet#initServletBean()
方法中:
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
...
}
initWebApplicationContext()
方法中会进一步调用 createWebApplicationContext()
方法:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
区别于 Springboot,SpringMVC 中,走到 initWebApplicationContext()
方法时(浏览器请求一下才会触发),this.webApplicationContext
为 null:
而在 Springboot 中,走到 initWebApplicationContext()
方法时,this.webApplicationContext
已经有值了:
contextConfigLocation 怎么用
如上面的代码 wac.setConfigLocation(configLocation);
,会将 contextConfigLocation
指定的值传递给 ConfigurableWebApplicationContext
对象(这是一个接口),实际是 AnnotationConfigWebApplicationContext
对象(继承 AbstractRefreshableConfigApplicationContext
类,该类实现了 setConfigLocation(String)
方法)。
然后在 AnnotationConfigWebApplicationContext#loadBeanDefinitions()
方法中调用了 getConfigLocations()
得到在 web.xml 中具体配置的值,并通过反射得到具体的配置对象:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
if (beanNameGenerator != null) {
reader.setBeanNameGenerator(beanNameGenerator);
scanner.setBeanNameGenerator(beanNameGenerator);
beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
}
ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
if (scopeMetadataResolver != null) {
reader.setScopeMetadataResolver(scopeMetadataResolver);
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
if (!this.componentClasses.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Registering component classes: [" +
StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
}
reader.register(ClassUtils.toClassArray(this.componentClasses));
}
if (!this.basePackages.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Scanning base packages: [" +
StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
}
scanner.scan(StringUtils.toStringArray(this.basePackages));
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
try {
Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
if (logger.isTraceEnabled()) {
logger.trace("Registering [" + configLocation + "]");
}
reader.register(clazz);
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not load class for config location [" + configLocation +
"] - trying package scan. " + ex);
}
int count = scanner.scan(configLocation);
if (count == 0 && logger.isDebugEnabled()) {
logger.debug("No component classes found for specified class/package [" + configLocation + "]");
}
}
}
}
}
配置对象 com.example.xxx.config.AppConfig
:
@Configuration
@EnableWebMvc
@ComponentScan("com.example.xxx")
public class AppConfig {
@Bean
public XXX viewResolver() {
return new XXX();
}
}
进而,将配置类对象交给了 Spring,Spring 会进一步处理配置对象上的 ComponentScan
注解等。
最终,DispatcherServlet 对象生成并配置完毕。Tomcat 会根据 servlet-mapping 规则将请求交给 DispatcherServlet 处理:
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
SprintBoot
SprintBoot 是如何将 DispatcherServlet 配置到 Tomcat 中的?
通常,启动类代码都是如下所示:
@SpringBootApplication
public class SampleTomcatApplication {
public static void main(String[] args) {
SpringApplication.run(SampleTomcatApplication.class, args);
}
}
在 SpringApplication.run()
(静态 run 方法) 方法中会实例化 SpringApplication 对象,并调用其实例 run 方法,在 run 方法中会通过 context = createApplicationContext();
创建 ApplicationContext
对象(这里是 ServletWebServerApplicationContext
对象),并调用其 refresh
方法,进一步创建 TomcatWebServer 对象:
在 TomcatWebServer#initialize()
方法中会调用 this.tomcat.start();
,最终会走到 ApplicationContext#addServlet()
方法中,注意,这里的 ApplicationContext
是 tomcat 的 org.apache.catalina.core.ApplicationContext
,最终将 DispatcherServlet
对象交到了 tomcat 手中:
那么,DispatcherServlet 对象是在哪儿被创建的呢?在 ServletWebServerApplicationContext#createWebServer()
方法中会调用 getWebServerFactory()
方法,进而调用到 DispatcherServletAutoConfiguration$DispatcherServletConfiguration#dispatcherServlet()
方法上,该方法创建并返回了 DispatcherServlet
对象:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
// 这里最终会创建 DispatcherServlet 对象并放入容器中
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 这里调用 getWebServer 会将 DispatcherServlet 对象传递给 Tomcat
// 这里 getSelfInitializer() 是一个回调,到时候 Tomcat 通过这个回调将 ServletContext 传回给 Spring
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new 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
return resolver;
}
}
补充
ServletContext 是在哪儿创建的
在 Tomcat 的 StandardContext#getServletContext()
方法中创建的,在 StandardContext#startInternal()
中有多次调用该方法。
ServletContext 对象是怎么传递给 Spring 的
从上面 的代码可以知道,在 main->run->run->run->refresh->TomcatWebServer#initialize()->Tomcat#start()->...->StandardContext#startInternal()
的过程中,会创建 ServletContext 对象,但此时这个对象还是隶属于 StandardContext
的,那它是怎么被传递给 Spring
的呢?
StandardContext.initializers
成员包含 TomcatStarter
对象。在 StandardContext#startInternal()
方法中会用到:
protected synchronized void startInternal() throws LifecycleException {
...
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
...
}
class TomcatStarter implements ServletContainerInitializer {
...
}
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
而 TomcatStarter 实现了 ServletContainerInitializer 接口,故会走到 TomcatStarter#onStartup()
方法:
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
TomcatStarter
也有一个 initializers
成员,其包含 ServletWebServerApplicationContext$lambda@4202
对象,为 ServletWebServerApplicationContext
的一个 lambda 表达式。
这里要注意一下,这个表达式是在 createWebServer()
中传递给 WebServer 的,可以理解为一个回调:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 注册回调
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
getWebServer(ServletContextInitializer)
方法的参数是 ServletContextInitializer
接口,该接口只有一个 onStartup
方法,故可以把 void selfInitialize(ServletContext)
方法传递过去(lambda 表达式的用法):
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
因此,在 TomcatStarter#onStartup()
会调到 ServletWebServerApplicationContext#selfInitialize(ServletContext)
中来,也就把 ServletContext
对象给传递过来了。
简单来说,就是 Spring 创建 Tomcat 的 WebServer 时传递了一个一个回调过去,等 Tomcat 创建好 ServletContext 对象后再通过这个回调将 ServletContext 对象传过来。
Springboot 中,DispatcherServlet 对象注入到了哪儿
上面提到了 DispatcherServlet
最终通过 addServlet 交到了 Tomcat 手中,也提到了是在 DispatcherServletConfiguration
类中以 @Bean
的方式添加到了容器中,那么总会用一个地方注入了 DispatcherServlet 对象吧?
上面提到,Tomcat 通过回调到 ServletWebServerApplicationContext#selfInitialize(ServletContext)
把 ServletContext 对象传递回到 Spring 中,下面重点看 selfInitialize()
方法中的 for
循环部分:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
调用 getServletContextInitializerBeans()
返回了如下相关 Bean:
可以看到,第一个就是 dispatcherServlet urls=[/]
,类名为 DispatcherServletRegistrationBean
,继承自 RegistrationBean
,而 RegistrationBean
又实现了 ServletContextInitializer
接口,故会走到 RegistrationBean#onStartup
方法中:
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
进一步调用 DynamicRegistrationBean#register
方法:
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);
}
然后会走到 ServletRegistrationBean#addRegistration()
方法中:
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
这里,已经出现了 this.servlet
了,而其就是 DispatcherServlet
的实例,下面只需要看该对象是如何生成的就知道了:
注意到在 ServletRegistrationBean
的构造方法中,就有 T servlet
,而这里的泛型 T
就是 DispatcherServlet
,再看哪儿调用了这个构造方法(肯定只有子类 DispatcherServletRegistrationBean
的构造方法调 super
才可能)
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
的构造方法:
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
super(servlet);
Assert.notNull(path, "Path must not be null");
this.path = path;
super.addUrlMappings(getServletUrlMapping());
}
再看哪儿调用了该构造方法,通过 Ctrl+B
可以看到,在 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
类中有调用:
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class 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;
}
}
很明显了,DispatcherServlet
对象就是在 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
类的 dispatcherServletRegistration(DispatcherServlet, WebMvcProperties, ObjectProvider<MultipartConfigElement>)
方法中被注入的。
通过打断点可以知道,先到 DispatcherServletConfiguration#dispatcherServlet()
方法中创建DispatcherServlet
对象并放入 Spring 容器中;再到 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration#dispatcherServletRegistration()
方法中,这里注入了上面生成的 DispatcherServlet
对象:
注入 DispatcherServlet 对象.png
结论:
- 在
DispatcherServletConfiguration#dispatcherServlet()
方法中创建DispatcherServlet
对象并放入 Spring 容器中;
在 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration#dispatcherServletRegistration()
方法中注入了 DispatcherServlet
对象。
DispatcherServletRegistrationBean 对象注入到了哪儿
接上面的问题,我们知道了创建 DispatcherServlet
的地方在 DispatcherServletConfiguration#dispatcherServlet(...)
方法中(以 @Bean
的方式),也知道了 DispatcherServlet
被注入到了 DispatcherServletRegistrationConfiguration#dispatcherServletRegistration(DispatcherServlet ...)
方法的入参里(该方法返回 DispatcherServletRegistrationBean
对象),那么 DispatcherServletRegistrationBean
又注入到了哪儿去了呢?
再看一下 ServletWebServerApplicationContext#selfInitialize(ServletContext)
方法:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
看 getServletContextInitializerBeans()
方法,该方法返回一个 ServletContextInitializerBeans
对象(继承了 AbstractCollection<ServletContextInitializer>
):
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
再看 ServletContextInitializerBeans
:
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
private static final Log logger = LogFactory.getLog(ServletContextInitializerBeans.class);
/**
* Seen bean instances or bean names.
*/
private final Set<Object> seen = new HashSet<>();
private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
private final List<Class<? extends ServletContextInitializer>> initializerTypes;
private List<ServletContextInitializer> sortedList;
@SafeVarargs
@SuppressWarnings("varargs")
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values()
.stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory) {
if (initializer instanceof ServletRegistrationBean) {
Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof FilterRegistrationBean) {
Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof ServletListenerRegistrationBean) {
EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
}
else {
addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
initializer);
}
}
private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory, Object source) {
this.initializers.add(type, initializer);
if (source != null) {
// Mark the underlying source as seen in case it wraps an existing bean
this.seen.add(source);
}
if (logger.isTraceEnabled()) {
String resourceDescription = getResourceDescription(beanName, beanFactory);
int order = getOrder(initializer);
logger.trace("Added existing " + type.getSimpleName() + " initializer bean '" + beanName + "'; order="
+ order + ", resource=" + resourceDescription);
}
}
private String getResourceDescription(String beanName, ListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
return registry.getBeanDefinition(beanName).getResourceDescription();
}
return "unknown";
}
@SuppressWarnings("unchecked")
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
private MultipartConfigElement getMultipartConfig(ListableBeanFactory beanFactory) {
List<Entry<String, MultipartConfigElement>> beans = getOrderedBeansOfType(beanFactory,
MultipartConfigElement.class);
return beans.isEmpty() ? null : beans.get(0).getValue();
}
protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
RegistrationBeanAdapter<T> adapter) {
addAsRegistrationBean(beanFactory, type, type, adapter);
}
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
for (Entry<String, B> entry : entries) {
String beanName = entry.getKey();
B bean = entry.getValue();
if (this.seen.add(bean)) {
// One that we haven't already seen
RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
int order = getOrder(bean);
registration.setOrder(order);
this.initializers.add(type, registration);
if (logger.isTraceEnabled()) {
logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
+ order + ", resource=" + getResourceDescription(beanName, beanFactory));
}
}
}
}
private int getOrder(Object value) {
return new AnnotationAwareOrderComparator() {
@Override
public int getOrder(Object obj) {
return super.getOrder(obj);
}
}.getOrder(value);
}
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type) {
return getOrderedBeansOfType(beanFactory, type, Collections.emptySet());
}
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
Set<?> excludes) {
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
for (String name : names) {
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
List<Entry<String, T>> beans = new ArrayList<>(map.entrySet());
beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
return beans;
}
private void logMappings(MultiValueMap<Class<?>, ServletContextInitializer> initializers) {
if (logger.isDebugEnabled()) {
logMappings("filters", initializers, Filter.class, FilterRegistrationBean.class);
logMappings("servlets", initializers, Servlet.class, ServletRegistrationBean.class);
}
}
private void logMappings(String name, MultiValueMap<Class<?>, ServletContextInitializer> initializers,
Class<?> type, Class<? extends RegistrationBean> registrationType) {
List<ServletContextInitializer> registrations = new ArrayList<>();
registrations.addAll(initializers.getOrDefault(registrationType, Collections.emptyList()));
registrations.addAll(initializers.getOrDefault(type, Collections.emptyList()));
String info = registrations.stream().map(Object::toString).collect(Collectors.joining(", "));
logger.debug("Mapping " + name + ": " + info);
}
@Override
public Iterator<ServletContextInitializer> iterator() {
return this.sortedList.iterator();
}
@Override
public int size() {
return this.sortedList.size();
}
/**
* Adapter to convert a given Bean type into a {@link RegistrationBean} (and hence a
* {@link ServletContextInitializer}).
*
* @param <T> the type of the Bean to adapt
*/
@FunctionalInterface
protected interface RegistrationBeanAdapter<T> {
RegistrationBean createRegistrationBean(String name, T source, int totalNumberOfSourceBeans);
}
/**
* {@link RegistrationBeanAdapter} for {@link Servlet} beans.
*/
private static class ServletRegistrationBeanAdapter implements RegistrationBeanAdapter<Servlet> {
private final MultipartConfigElement multipartConfig;
ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
this.multipartConfig = multipartConfig;
}
@Override
public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/"; // always map the main dispatcherServlet to "/"
}
ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
bean.setName(name);
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
}
/**
* {@link RegistrationBeanAdapter} for {@link Filter} beans.
*/
private static class FilterRegistrationBeanAdapter implements RegistrationBeanAdapter<Filter> {
@Override
public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
bean.setName(name);
return bean;
}
}
/**
* {@link RegistrationBeanAdapter} for certain {@link EventListener} beans.
*/
private static class ServletListenerRegistrationBeanAdapter implements RegistrationBeanAdapter<EventListener> {
@Override
public RegistrationBean createRegistrationBean(String name, EventListener source,
int totalNumberOfSourceBeans) {
return new ServletListenerRegistrationBean<>(source);
}
}
}
在其构造方法里就通过 beanFactory
将 ServletContextInitializer
相关的 bean 给取了出来,存入到了 initializers
成员里,而显然 DispatcherServletRegistrationBean
也实现了 ServletContextInitializer
接口,故在这里会把 DispatcherServletRegistrationBean
取出来。
结论:
- 应用初始化时,会将
@Bean
提供的类创建出对象存入beanFactory
,这里就包含DispatcherServletRegistrationBean
对象和DispatcherServlet
对象 - 创建
DispatcherServletRegistrationBean
对象时需要DispatcherServlet
对象,DispatcherServlet
对象被注入到了对应的方法入参中 -
DispatcherServletRegistrationBean
并没有通过方法入参、Autowired
到成员变量等方式注入到某个对象中去,而是直接通过beanFactory
取了出来,然后进行调用(ServletContextInitializer
接口的onStartup
方法)。 -
onStartup
再往下会到org.apache.catalina.core.ApplicationContext#addServlet()
方法,把DispatcherServlet
对象传递给 Tomcat
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
Springboot 和 Tomcat 的交互
- Springboot 创建
ServletWebServerApplicationContext
对象,并调用refresh
方法 -
ServletWebServerApplicationContext
调用createWebServer()
方法创建org.springframework.boot.web.server.WebServer
对象 -
createWebServer()
方法中会调用factory.getWebServer(getSelfInitializer())
,通过getSelfInitializer()
注册了一个回调到 Tomcat 中去,其实是ServletWebServerApplicationContext
的void selfInitialize(ServletContext servletContext)
方法,用以接收 Tomcat 传递过来的ServletContext
对象(是一个接口),而 Tomcat 的org.apache.catalina.core.ApplicationContext
实现了该接口 - 这里的
factory
是TomcatServletWebServerFactory
,在TomcatServletWebServerFactory#getWebServer()
方法中通过一系列调用,最终在TomcatServletWebServerFactory#configureContext()
方法中将 Springboot 侧的initializers
(即selfInitialize
方法回调)传递给了 Tomcat - 桥梁搭建完毕,Tomcat 持有 Springboot 的
selfInitialize
方法回调,Springboot 持有 Tomcat 的ServletContext
对象(实现类为ApplicationContext
),二者通过该相互持有的对象进行交互调用。如 Springboot 调用ServletContext#addServlet()
方法把创建出来的DispatcherServlet
对象传递给 Tomcat - 简单来说,Springboot 创建 Tomcat 服务器并传递
selfInitialize
方法回调,Tomcat 通过该回调回传ServletContext
对象,Springboot 再通过ServletContext
对象传递DispatcherServlet
对象给 Tomcat。
网友评论