简介

说明

        本文介绍SpringBoot是如何实现自动配置的。

问题的引出

SpringBoot有如下功能:


  1. 创建好的SpringBoot项目(假如启动类是DemoApplication),可以直接运行
  2. 可以在配置文件自定义配置
  3. 在启动类上加个注解就可以使用某个功能

那么,SpringBoot是如何实现这些功能的呢?

入口

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

 @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 {
...
}

  • @SpringBootConfiguration  // 对@Configuration 注解的封装,与@Configuration 作用相同。
  • @Configuration // 通过javaConfig的方式来添加组件到 IoC 容器中
  • @EnableAutoConfiguration

  • @AutoConfigurationPackage // 扫描@ComponentScan指定的路径下的类,添加到IOC
  • @Import(AutoConfigurationImportSelector.class) // 将META-INF/spring.factories中定义的bean 添加到 IoC 容器中

  • @ComponentScan // 包扫描。

  • 默认扫描的包的根路径是 Spring Boot 主程序启动类所在包的位置。
  • 在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Springboot 项目主程序启动类所在包的具体位置。


@SpringBootConfiguration

简介

        对@Configuration 注解的封装,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中

源码

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration

简介

        @EnableAutoConfiguration 注解表示开启自动配置功能。

        @Import注解用于导入配置类,本处导入AutoConfigurationImportSelector。容器刷新时,会调用AutoConfigurationImportSelector类的selectImports方法,扫描META-INF/spring.factories文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项通过反射实例化为对应的标注了@Configuration 的JavaConfig形式的配置类,并加载到 IoC 容器中。

@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

自动配置包 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入 Registrar 组件类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
public abstract class AutoConfigurationPackages {
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 将主程序类所在包及其子包下的组件扫描到 Spring 容器中
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
// 其他代码
}

@Import(AutoConfigurationImportSelector.class)

        将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中。AutoConfigurationImportSelector 可以帮助 SpringBoot 应用将所有符合条件的配置都加载到当前 SpringBoot 创建并使用的 IoC 容器(ApplicationContext) 中。

这个类中是通过 selectImports() 这个方法告诉 SpringBoot 都需要导入哪些组件

selectImports  //给容器中导入组件

简述

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 这个方法告诉springboot都需要导入那些组件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
// 自动配置的类全名.条件=值
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

//其他方法
}

详解

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}

// 获取自动配置项
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有默认支持的自动配置类名列表
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类,自己写的starter 可能存在重复的
configurations = removeDuplicates(configurations);
// 若不希望某些自动配置类自动配置,可通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
// 也可在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
checkExcludedClasses(configurations, exclusions);
// 从 configurations 中,移除所有不希望自动配置的配置类
configurations.removeAll(exclusions);
// 筛选候选的自动配置类,根据项目pom.xml中加入的依赖筛选出最终符合当前项目运行环境对应的自动配置类
// 判断是否要加载某个类的两种方式:
// 根据spring-autoconfigure-metadata.properties进行判断。
// 判断@Conditional是否满足
configurations = getConfigurationClassFilter().filter(configurations);
将自动配置导入事件通知监听器
// 过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
// 并触发fireAutoConfigurationImportEvents事件。
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass 获取默认的 EnableAutoConfiguration.class 类名,传入 loadFactoryNames 方法
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;
}

// 默认的 EnableAutoConfiguration.class 类名
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
}
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 {
// 如果类加载器不为 null,则加载"META-INF/spring.factories",将其中设置的配置类的全路径信息封装为 Enumeration
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,
// 通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}

  • 首先注意到 selectImports 方法,其实从方法名就能看出,这个方法用于给容器中导入组件,然后跳到 getAutoConfigurationEntry 方法就是用于获取自动配置项的。
  • getCandidateConfigurations 方法就是 获取一个自动配置 List ,这个 List 就包含了所有的自动配置的类名 。
  • SpringFactoriesLoader#loadFactoryNames :跳转到 loadSpringFactories 方法发现 ClassLoader 类加载器指定了一个 FACTORIES_RESOURCE_LOCATION 常量。
  • 然后利用PropertiesLoaderUtils 把 ClassLoader 扫描到的这些文件的内容包装成 properties 对象,从 properties 中获取到 EnableAutoConfiguration.class 类(类名)对应的值,然后把他们添加在容器中。

总结

Springboot 底层实现自动装配的步骤是:

Springboot 应用启动;

@SpringBootApplication 起作用;

@EnableAutoConfiguration;

@AutoConfigurationPackage: 这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class), 它通过将 Registrar 类导入到容器中,而 Registrar 类作用是扫描主配置类同级目录及其子包,并将相应的组件导入到 Springboot创建管理容器中

@Import(AutoConfigurationImportSelector.class): 它通过将 AutoConfigurationImportSelector 类导入到容器中,AutoConfigurationImportSelector 类作用是通过selectImports方法中,使用内部工具类SpringFactoriesLoader查找 classpath 上所用 jar 包中的  MATE-INF/spring.factories 进行加载, 实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。

默认配置

简介

说明

springboot里边已经有了很多默认的配置,对应的依赖为:spring-boot-starter-xxx,对于这些依赖,大多数情况下,导入它们就可以直接使用,不需要加@EnableXxx。

springboot默认支持的spring-boot-starter-xxx对应的自动配置类见:​​自动配置类(英文官网)​

实例分析:AOP自动配置

其他网址

​带着问题看源码-spring aop(一) - 简书​

配置类 

在上边“简介”中已经说过如何找配置类,本处spring-boot-starter-aop的配置类为:

spring-boot-autoconfigure-2.3.0.RELEASE.jar包里的org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.weaver.Advice;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring's AOP support. Equivalent to enabling
* {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration.
* <p>
* The configuration will not be activated if {@literal spring.aop.auto=false}. The
* {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
* overridden by specifying {@literal spring.aop.proxy-target-class=false}.
*
* @author Dave Syer
* @author Josh Long
* @since 1.0.0
* @see EnableAspectJAutoProxy
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {

}

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {

ClassProxyingConfiguration(BeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}

}

}

分析

从上边代码可以看到如下两点:

aop的开启与关闭

类上有注解:@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

也就是说,默认情况下,会启用aop。(可通过spring.aop.auto=false关闭)

 AspectJ的开启

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration{

}

添加spring-boot-starter-aop依赖后,会自动引入aspectjweaver-1.9.4.jar,而aspectjweaver-1.9.4.jar里边有Advice.class。所以,@ConditionalOnClass这个注解需要的条件已经满足了。

SpringBoot原理--自动配置_SpringBoot

SpringBoot原理--自动配置_原理_02

CGLIB的开启

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}

也就是说,如果不配置apring.aop.proxy-target-class,会使用CGLIB,而且,@EnableAspectJAutoProxy(proxyTargetClass = true)会生效。

也在另一处得到验证:在spring-boot-autoconfigure-2.3.0.RELEASE.jar的META-INFO/spring-configuration-metadata.json中找到如下配置信息:

SpringBoot原理--自动配置_SpringBoot_03

其他网址

​SpringBoot启动流程是怎样的?​

​SpringBoot 原理深入及源码剖析​

​SpringBoot应用启动过程分析 - 知乎​