Spring Boot一般使用starter来整合第三方框架与扩展功能。其套路是:
- 定义一个XXXXAutoConfiguration的类。其中XXXX一般可以写成所需集成的功能或框架。其上可定义@Conditional相关注解,指定何时可初始化该功能。
- 在该类上@Import一个ImportBeanDefinitionRegistrar接口的实现类来注入指定的Bean。
- 添加自定义的BeanPostProcessor来实现ImportBeanDefinitionRegistrar接口,以便在Bean初始化之前或之后完成配置功能或者初始化某些依赖功能。
- 在META-INF/spring.factories中定义org.springframework.boot.autoconfigure.EnableAutoConfiguration=<XXXXAutoConfiguration>的完整类名,包括所在包名。Spring Boot在初始化时,会自动扫描spring.factories文件中的相关类,然后初始化并注入到IoC容器中。
SpringBoot整合并启动Spring MVC,基本上也符合这个套路。整合Spring MVC需要增加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
该依赖中默认包含了Tomcat的依赖。
下面对整合过程进行分析,启动Spring MVC,首先需要一个Servlet Container,以便与Client端转换Request和Response。
1. Servlet Container是在哪里启动的?
我们在spring-boot-autoconfigure包的META-INF/spring.factories文件中找到了Servlet Container的配置类:EmbeddedServletContainerAutoConfiguration.class。其实现如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
......
}
从该类的注解可以看出,使用@Import导入了BeanPostProcessorRegistrar.class类。从该类中可以看出,该类首先检查是否存在自定义的EmbeddedServletContainerCustomizerBeanPostProcessor,如果没有,则注册默认的EmbeddedServletContainerCustomizerBeanPostProcessor类。其实现如下:
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(
this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
EmbeddedServletContainerCustomizerBeanPostProcessor在EmbeddedServletContainer对象初始化之前,进行必要的参数配置。其代码实现如下:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
在Spring的容器配置加载完之后,就会执行EmbeddedWebApplicationContext.refresh()方法。在其onRefresh方法中调用createEmbeddedServletContainer()方法。在此方法中初始化EmbeddedServletContainer:
首先,需要获取EmbeddedServletContainerFactory:
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
那EmbeddedServletContainerFactory的默认实现有哪些呢?还是在一开始的EmbeddedServletContainerAutoConfiguration中:
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
如果实现第三方Factory,与上类似。根据Factory获取相应的ServletContainer。该container初始化之后回调onStartup()。EmbeddedWebApplicationContext在onStartup回调中完成SpringMvc功能注入。
在ServletContextInitializer类的selfInitialize方法中获取到所有ServletContextInitializer对象,并调用其onStartup方法。
2. 初始化Servlet,Filter等
以Servlet为例,ServletRegistrationBean的onStartup实现如下:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
String name = getServletName();
if (!isEnabled()) {
logger.info("Servlet " + name + " was not registered (disabled)");
return;
}
logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
Dynamic added = servletContext.addServlet(name, this.servlet);
if (added == null) {
logger.info("Servlet " + name + " was not registered "
+ "(possibly already registered?)");
return;
}
configure(added);
}
/**
* Configure registration settings. Subclasses can override this method to perform
* additional configuration if required.
* @param registration the registration
*/
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = this.urlMappings
.toArray(new String[this.urlMappings.size()]);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
其他组件如Filter等,初始化方式类似。
3. 初始化Spring MVC的DispatcherServlet:
DispatcherServlet是由DispatcherServletAutoConfiguration来启动,其实现如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
......
}
从注解@AutoCondifureAfter可以看出,该类是在EmbeddedServletContainerAutoConfiguration之后初始化。
其DispatcherServlet定义如下:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
初始化了DispatcherServlet之后,Spring MVC也就启动起来了。