2.2.2 自动配置
能够在我们添加jar包依赖时,自动为我们进行配置一下配置,我们可以不需要配置或者少量配置就能运行编写的项目。
问题:Spring Boot到底是如何进行自动配置的,都把那些组件进行了自动配置?
2.2.2.1 @SpringBootApplication
Spring Boot 应用启动的入口是@SpringBootApplication注解标注类的main方法,
@SpringBootApplication能够扫描Spring组件并且自动配置Spring Boot
@SpringBootApplication
public class LearnApplication {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
}
@SpringBootApplication注解类
// 注解的适用范围:类、接口、枚举
@Target({ElementType.TYPE})
// 注解的生命周期:运行时
@Retention(RetentionPolicy.RUNTIME)
// 标明注解可标注在javadoc中
@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
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
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这三个核心注解组成。
2.2.2.1.1 @SpringBootConfiguration
@SpringBootConfiguration 注解标明其类为Spring Boot配置类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 配置到IOC容器
@Configuration
public @interface SpringBootConfiguration {
}
从上述可以看出,@SpringBootConfiguration注解类主要注解为@Configuration注解,该注解由Spring框架提供,表示当前类为一个配置类,并且可以被组件扫描器扫描。
2.2.2.1.2 @EnableAutoConfiguration
@EnableAutoConfiguration注解表示为自动配置类,该注解是Spring Boot最重要的注解,也是实现自动配置的注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 自动配置扫描导入
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
从源码可以发现,@EnableAutoConfiguration注解为一个组合注解,其作用就是借助@Import注解导入特定场景需要向IOC注册的Bean,并且加载到IOC容器。@AutoConfigurationPackage就是借助@Import来搜集所有符合自动配置条件的Bean定义,并且加载到IOC容器中。
-
@AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 导入Registrar中注册的组件 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
从上述源码中可以看出@AutoConfigurationPackage注解的功能是有@Import注解实现的。@Import它是Spring框架底层注解,它的作用就是给容器导入某个组件类
Registrar类源码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 将主程序类所在的包以及所有子包下的组件扫描到Spring容器 register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
从上述可以看出,@AutoConfigurationPackage注解的主要作用就是将主程序类所在的包以及所有子包下的组件加载到IOC容器中。
因此:在定义项目包目录时,要求定义的包结构必须规范,项目主程序启动类要放在最外层的根目录位置,然后在根目录的位置内部建立子包和类进行业务开发,这样才能保证定义的类才能被组件扫描器扫描。
-
@Import({AutoConfigurationImportSelector.class})
将AutoConfigurationImportSelector类导入到Spring容器中。
AutoConfigurationImportSelector可以帮助 Spring Boot 应用将所有符合条件@Configuration的配置都导入到当前Spring Boot创建并使用的IOC容器(ApplicationContext)中。
// 自动配置的过程 public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); // 获取自动配置的配置类 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } // 获取自动配置元信息 private AutoConfigurationMetadata getAutoConfigurationMetadata() { if (this.autoConfigurationMetadata == null) { // 加载自动配置元信息,需要传入beanClassLoader这个类加载器 this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); } return this.autoConfigurationMetadata; } // 获取自动配置的配置类 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 从META-INF/spring.factories配置文件中将对于的自动配置类获取到 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
深入**AutoConfigurationMetadataLoader.loadMetadata()**方法
// 文件中需要加载的配置类的类路径
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 读取spring-boot-autoconfigure-2.1.14.RELEASE.jar中spring-autoconfigure-metadata.properties的信息生成URL
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
深入**AutoConfigurationImportSelector.getCandidateConfigurations() **方法
这个方法有一个重要的loadFactoryNames方法,这个方法让SpringFactoriesLoader去加载一些组件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这个方法需要两个参数,getSpringFactoriesLoaderFactoryClass()、getBeanClassLoader()
// getSpringFactoriesLoaderFactoryClass() 返回的:EnableAutoConfiguration.class
// getBeanClassLoader() 返回的:beanClassLoader类加载器
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
继续深入loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取出的健
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 如果类加载器不为空,这加载类路径下的META-INF/spring.factories,将其中设置的配置类的类路径信息封装为Enumeration对象
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories"));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
会去读取一个 spring.factories 的文件,读取不到会报错
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
它其实是去加载一个外部的文件,而这个文件是在
@EnableAutoConfiguration 注解就是从classpath中搜寻META-INF/spring.factories配置文件,并将其org.springframework.boot.autoconfigure.EnableAutoConfiguration对于的配置通过反射实例化对应的标注了@Configuration的JavaConfig配置类,并且加载到IOC容器中。
以web项目为例,在项目中加入了web环境依赖启动器,对应的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration自动配置就会生效,打开自动配置就会发现,在配置类中通过全注解的方式对 Spring MVC 运行环境所需要环境进行了默认配置,包括前缀、后缀、试图解析器、MVC校验器等。
总结
Spring Boot 底层自动配置的步骤:
1)Spring Boot 应用启动
2)@SpringBootApplication 注解起作用
3)@EnableAutoConfiguration 注解起作用
4)@AutoConfigurationPackage 注解起作用
@AutoConfigurationPackage 这个注解主要作用就是@Import({AutoConfigurationPackages.Registrar.class}),它通过Registrar类导入容器中,而Registrar的作用就是将扫描主配置类的包以及子包,并将对应的组件导入IOC容器中。
5)@Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationImportSelector.class) 它将 AutoConfigurationImportSelector 类导入容器中,AutoConfigurationImportSelector 类的作用是通过getAutoConfigurationEntry()方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有的jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给Spring Factory加载器进行一系列的容器创建过程。
2.2.2.1.3 @ComponentScan
@ComponentScan 注解具体扫描包的路径,由Spring Boot主程序所在包的位置决定。在扫描的过程中由@AutoConfigurationPackage注解进行解析,从而得到Spring Boot主程序类所在包的具体位置