什么是 SpringBoot 自动配置
????????我们现在提到自动配置的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。
????????SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
? ? ? ? 在没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 jpa的话,直接在项目中引入对应的 starter(spring-boot-starter-data-jpa) 即可。
结构图
结构体图解析
1、首先springboot的核心注解@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}
)}
)
......
????????其中比较重要三个注解为:
@SpringBootConfiguration:我们点进去以后可以发现底层是Configuration注解,支持JavaConfig的方式来进行配置(使用Configuration配置类等同于XML文件)。 @EnableAutoConfiguration:开启自动配置功能 @ComponentScan:扫描注解,默认是扫描当前类下的package。将@Controller、 @Service、@Component、@Repository等注解加载到IOC容器中。
2、@EnableAutoConfiguration就是自动装配注解,源码如下:
@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 {};
}
????????关注@AutoConfigurationPackage和@Import注解
@AutoConfigurationPackage :自动配置包@Import :给IOC容器导入组件
3、先分析@AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
????????里面有注解@Import({Registrar.class}),查看Registrar.class
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
????????在默认的情况下就是将:主配置类(@SpringBootApplication )的所在包及其子包里边的组件扫描到Spring容器中。是不是与@ComponentScan功能重复了呢?
????????文档里有这样一句描述
it will be used when scanning for code @Entity classes. It is generally recommended that you place EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.
????????eg:用了spring-boot-starter-data-jpa,可能会在实体类上写@Entity 注解。这个@Entity 注解由@AutoConfigurationPackage 扫描并加载,而我们平时用的@Controller、@Service、@Component、@Repository 这些注解是由ComponentScan 来扫描并加载的。
? ? ? ? 也就是说:他俩扫描的对象是不一样的。
4、返到@EnableAutoConfiguration,查看@Import(AutoConfigurationImportSelector.class)注解,点开AutoConfigurationImportSelector.class,里面有这样一个方法;请仔细查看下面方法,作者已经把方法调用依次按顺序写入,根据注释查看;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//查看getAutoConfigurationEntry方法
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//查看getCandidateConfigurations方法
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
//查看loadFactoryNames方法 和 getBeanClassLoader方法
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//查看loadSpringFactories方法
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//加载META-INF/spring.factories文件
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
????????由上可知
- Spring启动的时候会扫描所有jar路径下的
META-INF/spring.factories ,将其文件包装成Properties对象 - 从Properties对象获取到key值为
EnableAutoConfiguration 的数据,然后添加到容器里。
4、来看一下META-INF/spring.factories文件
总结
? ? ? ? 通过各种注解实现了类与类之间的依赖关系,容器在启动的时候SpringApplication.run(),调用EnableAutoConfigurationImportSelector.class的selectImports方法 ????????selectImports方法调用SpringFactoriesLoader.loadFactoryNames方法扫描jar包类路径下的META-INF/spring.factories文件下,获取BeanConfiguration列表 ????????loadFactoryNames方法会读取spring.factories中EnableAutoConfiguration.class类名对应的值根据这些类上的注解判断,若条件满足,则该配置类生效,将配置文件中自己设置的属性值配置到对应的配置类中,最后注入到IOC容器中实现自动配置
|