Spring自动配置
Spring Boot会根据类路径中的jar包、类,为jar包里的类自动配置,这样可以极大的减少配置的数量。简单点说就是它会根据定义在classpath下的类,自动的给你生成一些Bean,并加载到Spring的Context中。自动配置充分的利用了spring 4.0的条件化配置特性,能够自动配置特定的Spring bean,用来启动某项特性。
条件注解
@Conditional注解表示在满足某种条件后才初始化一个bean或者启用某些配置。它一般用在由 @Component、 @Service、 @Configuration等注解标识的类上面,或者由 @Bean标记的方法上。如果一个 @Configuration类标记了 @Conditional,则该类中所有标识了 @Bean的方法和 @Import注解导入的相关类将遵从这些条件。
条件注解 | 配置生效条件 |
---|---|
@ConditionalOnBean | 仅在当前上下文中存在某个Bean时 |
@ConditionalOnClass | 某个class在类加载器中存在 |
@ConditionalOnExpression | 当表达式为true时 |
@ConditionalOnMissingBean | 仅仅在当前上下文中不存在某个Bean时 |
@ConditionalOnMissingClass | 在类加载器中不存在对应的类 |
@ConditionalOnWebApplication | 是web应用 |
@ConditionalOnNotWebApplication | 不是web应用 |
@ConditionalOnProperty | 应用环境中的属性存在 |
@ConditionalOnResource | 存在指定的资源文件 |
@ConditionalOnSingleCandidate | 容器中只存在一个对应的实例 |
SpringFactoriesLoader详解
通过SpringFactoriesLoader.loadFactoryNames()读取了ClassPath下面的META-INF/spring.factories文件。
public abstract class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
简单看一下spring.factories文件,它是一个典型的java properties文件,配置的格式为Key = Value形式。
spring-boot-autoconfigure-1.4.3.RELEASE.jar中的spring.factories文件包含以下内容:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# 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.MessageSourceAutoConfiguration,\
………………………………省略很多
其中的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值都是Spring Boot中的自动配置相关类;在启动过程中会解析对应类配置信息。每个Configuation都定义了相关bean的实例化配置。都说明了哪些bean可以被自动配置,什么条件下可以自动配置,并把这些bean实例化出来。
如果我们新定义了一个starter的话,也要在该starter的jar包中提供spring.factories文件,并且为其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置类。
Spring Boot应用的启动入口
SpringBootApplication
Spring Boot的启动类说起。Spring Boot应用通常有一个入口类,入口类中有一个main方法,在main方法中使用SpringApplication.run()来启动整个应用。这个入口类要使用@SpringBootApplication注解声明。@SpringBootApplication是Spring Boot的核心注解,是一个组合注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
Class<?>[] exclude() default {};
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication注解主要包含 @SpringBootConfiguration、@EnableAutoConfiguration等几个注解。
EnableAutoConfiguration
@EnableAutoConfiguration注解可以让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置。
@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,这就是Spring Boot自动化配置真实原因。
EnableAutoConfigurationImportSelector
@Import 注解可以普通类导入到 IoC容器中。
ImportSelector : 返回需要导入的组件的全类名数组。
EnableAutoConfigurationImportSelector会去扫描classpath下的所有spring.factories文件,然后进行bean的自动化配置。
public interface DeferredImportSelector extends ImportSelector {
}
public class EnableAutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled(metadata)) {
return NO_IMPORTS;
}
try {
AnnotationAttributes attributes = getAttributes(metadata);
//获取自动配置的类
List<String> configurations = getCandidateConfigurations(metadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(metadata, attributes);
configurations.removeAll(exclusions);
configurations = sort(configurations);
recordWithConditionEvaluationReport(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
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;
}
}
EnableAutoConfigurationImportSelector.selectImports()会在容器启动过程中执行,AbstractApplicationContext.refresh()。
Configuation
从spring-boot-autoconfigure-1.4.3.RELEASE.jar中的spring.factories文件随便找一个Configuration,看看他是如何自动加载bean的。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
上面AopAutoConfiguration 在决定对哪些bean进行自动化配置的时候,使用了两个条件注解:ConditionalOnProperty和ConditionalOnClass。
只有满足这种条件的时候,对应的bean才会被创建。这样可以保证某些bean在没满足特定条件的情况下就可以不必初始化,避免在bean初始化过程中由于条件不足,导致应用启动失败。
selectImports何时执行
SpringBoot应用启动过程中解析添加@Configuration的配置类时,如果发现注解中存在@Import(ImportSelector)的情况,就会创建相应的ImportSelector对象, 并调用方法 public String[] selectImports(AnnotationMetadata annotationMetadata)。
待处理的任务
有时间可以写一篇@Configuration注解解析的相关文章,其解析器ConfigurationClassParser。