Spring Boot的启动过程一文分析了Spring Boot整体的启动过程,本文深入准备应用上下文阶段分析启动时的自动配置特性。
应用上下文
SpringApplication类的prepareContext方法用于准备先前创建的应用上下文,在其中调用了load成员方法将bean定义加载到应用上下文中,其代码如下所示:
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
- sources数组参数即是SpringApplication构造函数或者run静态函数中的表示加载源的source/sources参数;
- bean定义即来自于加载源表示的资源。
BeanDefinitionLoader类的load函数如下:
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
从上述代码可以看到加载源的类型只能是以下四种之一:
- java.lang.Class类型;
- org.springframework.core.io.Resource类型;
- java.lang.Package类型;
- java.lang.CharSequence类型,可以是类的完全限定名,或者是文件名,或者是包名。
在准备上下文之后,refreshContext方法刷新创建的应用上下文时从解析加载源代表的注解或者XML配置并实例化其中的各个单例bean。
注解自动配置
@SpringBootApplication注解
Spring Boot工程利用注解自动配置特性时都会使用@SpringBootApplication注解,该注解的代码如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
- @SpringBootApplication注解由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解组成;
- @SpringBootApplication注解的exclude属性是@EnableAutoConfiguration注解exclude属性的别名,其余别名类似。
@EnableAutoConfiguration注解
@EnableAutoConfiguration注解开启了自动配置,代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
它使用@Import注解导入了EnableAutoConfigurationImportSelector类,@Import注解上的值可以有以下四种:
- @Configuration注解修饰的类,这种类唯一的构造函数会被自动依赖注入参数。因为对@Component注解修饰的类,若只有一个构造函数,那么构造函数注入时可以省略构造函数上的@Autowired注解,参见官方文档;
-
ImportSelector接口:ImportSelector接口的用途是决定哪些被@Configuration注解修饰的类可以被导入,它只有一个selectImports方法,返回应该被导入的类名;
public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata); }
- ImportBeanDefinitionRegistrar接口;
- 其他一般组件类。
EnableAutoConfigurationImportSelector类从1.5版本开始已经不鼓励使用,转而使用它的父类AutoConfigurationImportSelector。
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
AutoConfigurationImportSelector类
AutoConfigurationImportSelector类实现了ImportSelector接口,其实现的selectImports方法如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
该方法按顺序主要做了以下工作:
- 从jar包中的META-INF/spring-autoconfigure-metadata.properties文件加载自动配置元数据;
- 从参数表示的注解元数据获得注解属性;
- 利用注解元数据和属性从jar包中的META-INF/spring.factories文件查找org.springframework.boot.autoconfigure.EnableAutoConfiguration类型的所有工厂实现类;
- 对第3步得到的工厂实现类的完全限定名首先去重,然后根据第1步获得的自动配置元数据排序,再移除要排除的类,最后过滤掉符合过滤规则的类;
- 触发各AutoConfigurationImportListener监听器的回调方法,AutoConfigurationImportListener监听器的工厂实现类也定义在META-INF/spring.factories文件中;
- 返回经过第4步一系列处理过程的工厂实现类的完全限定名,它们就是需要被导入的类。
查找工厂实现类
getCandidateConfigurations方法做了第3步的工作,其代码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
- 注意在该类的默认实现中两个参数均没有使用,子类可以重写该方法;
- SpringFactoriesLoader.loadFactoryNames静态方法从jar包中的META-INF/spring.factories文件查找org.springframework.boot.autoconfigure.EnableAutoConfiguration类型的所有工厂实现类;
- 以spring-boot-autoconfigure-1.5.15.RELEASE.jar中的META-INF/spring.factories文件为例,部分自动配置工厂实现类如下。
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ ... org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
自动配置工厂实现类
下面重点分析几个与Web有关的自动配置工厂实现类。
EmbeddedServletContainerAutoConfiguration
EmbeddedServletContainerAutoConfiguration工厂实现类自动配置了嵌入式容器,其代码如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class 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();
}
}
// 省略一些代码
}
该自动配置类实现了对Tomcat、Jetty和Undertow的自动配置,下面以Tomcat为例分析配置生效过程,Jetty和Undertow自动配置同理。
- @AutoConfigureOrder注解指定配置的优先级;
- @ConditionalOnWebApplication注解表明只有当当前应用上下文是Web应用上下文时本配置才启用;
- @ConditionalOnClass注解表示只有当javax.servlet.Servlet和org.apache.catalina.startup.Tomcat均在CLASSPATH中时才启用Tomcat自动配置;
- @ConditionalOnMissingBean注解表示如果在当前应用上下文中没有EmbeddedServletContainerFactory类型的bean才启用Tomcat自动配置;
- 只有上述条件均满足才会创建TomcatEmbeddedServletContainerFactory这个bean。
ServerPropertiesAutoConfiguration
ServerPropertiesAutoConfiguration工厂实现类自动配置了嵌入式容器的属性如端口、地址等,其代码如下:
@Configuration
@EnableConfigurationProperties
@ConditionalOnWebApplication
public class ServerPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ServerProperties serverProperties() {
return new ServerProperties();
}
@Bean
public DuplicateServerPropertiesDetector duplicateServerPropertiesDetector() {
return new DuplicateServerPropertiesDetector();
}
// 省略一些代码
}
- 该类的@EnableConfigurationProperties注解启用了@ConfigurationProperties注解,@ConfigurationProperties注解可以修饰一个普通的类用以映射外部配置文件;
- 如果当前上下文没有名为serverProperties的bean则实例化一个ServerProperties对象。
ServerProperties类代表了嵌入式容器的端口、地址等属性,其代码如下:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind to.
*/
private InetAddress address;
/**
* Context path of the application.
*/
private String contextPath;
/**
* Display name of the application.
*/
private String displayName = "application";
// 省略一些代码
}
ServerProperties类实现了EmbeddedServletContainerCustomizer接口,该接口允许自定义嵌入式容器,配置文件中的属性正是通过该接口的回调方法绑定到嵌入式容器的。根据该接口的javadoc,使用它时有一点需要注意:该接口一般是由EmbeddedServletContainerCustomizerBeanPostProcessor调用,而BeanPostProcessor在ApplicationContext的生命周期中会被较早调用,所以延迟查找依赖会更安全,而不是使用@Autowired。
DispatcherServletAutoConfiguration
DispatcherServletAutoConfiguration工厂实现类自动配置了DispatcherServlet,其部分代码如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/*
* 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
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
private final WebMvcProperties webMvcProperties;
public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
this.webMvcProperties = webMvcProperties;
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
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;
}
}
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final ServerProperties serverProperties;
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(
ServerProperties serverProperties, WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.serverProperties = serverProperties;
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
@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;
}
}
// 省略一些代码
}
- 该自动配置会在EmbeddedServletContainerAutoConfiguration自动配置之后进行;
- DispatcherServletConfiguration静态内部类创建了DispatcherServlet;
- DispatcherServletRegistrationConfiguration静态内部类利用ServletRegistrationBean向嵌入式servlet容器添加DispatcherServlet,添加原理可参见Spring Boot与嵌入式servlet容器。