SpringBoot自动装配原理
SpringBoot自动装配原理
一、Spring将类注入到IOC容器
1、Bean注入
Spring将Bean注入到IOC容器中主要有三种方式
使用
@CompontScan
注解来扫描声明了@Controller、@Service、@Repository、@Component
注解的类。使用
@lmport
注解,导入配置类或者普通的Bean
使用
@Bean
注解,用于注入第三方 jar 包到SpringIOC容器中
2、第三方Bean导入
如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component
及衍生注解声明Bean的。
1)用@Bean注解,可以通过@Configuration
注解声明一个配置类,例如:
@Configuration
public class CommonConfig {
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
2)使用@Import({Order.class, Member.class, MyImportBeanDefinitionRegistrar.class})
注解,可以注入多个类,多个类之间使用 , 分割,主要用注入第三方的 jar 包到SpirngIOC容器中。
二、什么是SpringBoot自动装配
现在提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的
META-INF/spring.factories
文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
自 Spring Boot 3.0 开始,自动配置包的路径从
META-INF/spring.factories
修改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
。
没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。
自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。
三、Springboot 是如何实现自动装配的
我们先看一下 SpringBoot 的核心注解 SpringBootApplication
。
@SpringBootApplication
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
}
}
进入 @SpringBootApplication
可以发现@SpringBootApplication
看作是 @SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration # 1
@EnableAutoConfiguration # 2
@ComponentScan( # 3
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
根据 SpringBoot 官网,这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@SpringBootConfiguration
:允许在上下文中注册额外的 bean 或导入其他配置类@ComponentScan
:扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。
提示
@EnableAutoConfiguration
是实现自动装配的重要注解,我们以这个注解入手。
1、@EnableAutoConfiguration
:实现自动装配的核心注解
在注解@EnableAutoConfiguration
中通过@Import
导入了AutoConfigurationImportSelector
类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration
2、AutoConfigurationImportSelector
类实现了 ImportSelector
接口,也就实现了这个接口中的 selectImports
方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) { //判断自动装配开关是否打开
return NO_IMPORTS;
} else { //获取所有需要装配的bean
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); //关键
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
3、selectImports
方法会调用getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) { //1、判断自动装配开关是否打开
return EMPTY_ENTRY;
} else {
//2、用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3、获取需要自动装配的所有配置类,读取META-INF/spring.factories
//不光是这个依赖下的META-INF/spring.factories被读取到,
//所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4、筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
4、getAutoConfigurationEntry
调用getCandidateConfigurations
方法获取需要自动装配的所有配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.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;
}
5、getCandidateConfigurations
调用SpringFactoriesLoader.loadFactoryNames()
方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
6、SpringFactoriesLoader.loadFactoryNames()
调用loadSpringFactories()
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
Enumeration<URL> 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()) {
Map.Entry<?, ?> entry = (Map.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);
}
}
}
7、loadSpringFactories()
从文件META-INF/spring.factories
中读取配置信息类
四、自定义一个 Starter
步骤:
1、创建xx-starter
工程,只保留.iml
和.xml
文件,该工程只进行依赖管理,不需要写代码
2、创建xx-autoconfigure
工程,并在xx-starter
中引入该模块
3、在 xx-autoconfigure
模块中的定义自动配置功能,并定义自动配置文件 META-INF/spring/xxxx.imports
4、 在xx-autoconfigure
添加一个configure
类,并使用@Bean
注解注入要生成的Bean
5、在xx-autoconfigure
工程的 resources 包下创建META-INF/spring.factories
文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.ThreadPoolAutoConfiguration
参考
[1] https://javaguide.cn/system-design/framework/spring/spring-boot-auto-assembly-principles.html
[2] https://blog.csdn.net/qq_52971866/article/details/122017539