一、什么是自动装配
在使用SpringBoot的时候,会自动将Bean装配到IOC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到Ioc容器中进行后续使用。
自动装配的大致过程如下:
- 获取到组件(spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件
- spring.factories文件中列出需要注入Ioc容器的类
- 将实体类注入到Ioc容器中使用
二、自动装配原理源码分析
自动装配原理的大致流程是通过@SpringBootApplication进行实现,这个注解声明在SpringBoot的启动类上。
1.@SpringBootApplication注解
@SpringBootApplication是一个组合注解,主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan组成
@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 {
@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 {};
}
@Target({ElementType.TYPE})等前四个注解JDK中的元注解,用于修饰注解的注解
@Filter用于实现过滤,比如可以将某些类排除在外
@AliasFor注解用于为注解属性声明别名
2.@SpringBootConfiguration
其中@SpringBootConfiguration的本质其实是@Configuration,标识该Java类是一个配置类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
3.@ComponentScan注解
@ComoponentScan是用来指定扫描路径的,如果不指定特定的扫描路径的话,扫描的路径是当前修饰类所在的包及其子包。
4.@EnableAutoConfiguration注解
@EnableAutoConfiguration这个注解是SpringBoot自动装配的关键。它也有两个注解@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class}).
@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
其中@AutoConfigurationPackage(basePackages =" ")的作用是指定SpringBoot要扫描的包,将包中的Bean注入IOC容器中。该注解内部是一个@Import注解,引入了一个Registrar.class。Registrar类的作用将将主启动类所在包以及子包里的所有组件扫描到Spring容器里。
注意:@AutoConfigurationPakage与@ComponentScan都是将Spring Boot启动类所在的包及其子包里面的组件扫描到IOC容器中,但区别在于@AutoConfigurationPackage还会扫描@Enitity、@Mapper等第三方依赖的注解,@ComponentScan只扫描@Controller/@Service/@Component/@Repository等Spring容器相关的注解。
参考:https://www.jb51.net/program/288508ml1.htm
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
@EnableAutoConfiguration注解最重要的是AutoConfigurationImportSelector.class,将需要装配的类装配到IoC容器中,下面重点分析一下这个类的实现
5.AutoConfigurationImportSelector.class
AutoConfigurationImportSelector实现了ImportSelector接口,那么我们清楚只需要关注selectImports方法的返回结果即可。
AutoConfigurationImportSelector中的selectImports方法是自动装配的核心实现,它主要是读取META-INF/spring.factories文件,经过去重、过滤,返回需要装配的配置类集合
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
// 返回的就是需要注册到IoC容器中的对象对应的类型的全类路径名称的字符串数组
// ["com.bobo.pojo.User","com.bobo.pojo.Person", ....]
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
我们清楚了该方法的作用就是要返回需要注册到IoC容器中的对象对应的类型的全类路径名称的字符串数组。那么我接下来分析的关键是返回的数据是哪来的?所以呢进入getAutoConfigurationEntry方法中。
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 获取注解的属性信息
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选配置信息 加载的是 当前项目的classpath目录下的 所有的 spring.factories 文件中的 key 为
// org.springframework.boot.autoconfigure.EnableAutoConfiguration 的信息
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
getAttributes方法:获取@EnableAutoConfiguration中的exclude、excludeName等
getCandidateConfigurations方法:获取所有自动装配的配置类,也就是读取spring.factories文件,后面会再次说明
removeDuplicates方法:去除重复的配置项
getExclusions方法:根据@EnableAutoConfiguration中的exclude、excludeName移除不需要的配置类
fireAutoConfigurationImportEvents方法:广播事件
最后根据多次过滤、判重返回配置类合集
6.getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
通过loadFactoryNames方法,扫描classpath下的META-INF/spring.factories文件,里面是以key=value形式存储,读取其中key=EnableAutoConfiguration,value就是需要装配的配置类,也就是getCandidateConfigurations返回的值
7.loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 此处的类加载器是appClassLoader
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
//如果为空则再次获取
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 类的全限定名称(即解析properties文件中需要的key值)
String factoryTypeName = factoryType.getName();
// 根据类加载器,加载classpath下/META-INF/spring.factories下所有的类名称列表
// 从结果Map<String, List<String>>中,根据指定类型获取所有实现类名称的集合List<String>
// 核心
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
- loadSpringFactories(ClassLoader classLoader)返回的是所有META-INF/spring.factories文件解析完的结果
- 返回值: 根据指定类名获取结果,没有则返回空列表
8. loadSpringFactories方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 根据类加载器去缓存中获取加载好的结果集
//因为SpringApplication实例化时就加载过一次了,这里就是从缓存中获取到值了
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
//结果不为空,则返回
return result;
}
//已从缓存获取到结果,后面代码省略
//已从缓存获取到结果,后面代码省略
//已从缓存获取到结果,后面代码省略
}
因为SpringApplication实例化时就加载过一次了,所以这里可以从缓存中获取到值。具体原理查看Alian解读SpringBoot 2.6.0 源码(一):SpringApplication对象创建(Spring工厂加载机制)
总结
1)通过注解@SpringBootApplication=>@EnableAutoConfiguration=>@Import({AutoConfigurationImportSelector.class})实现自动装配
2)AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类
3)通过Spring提供的SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要自动装配的配置类
4)依据条件筛选的方式,把不符合的配置类移除掉,最终完成自动装配
参考:
链接1
链接2
相关问题:
1.@Component,@Bean,@Import的区别
@Component作用于类,@Bean作用于方法,@Import作用于类
@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。
@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。@Bean通常用于配置类中声明Bean,配合@Configuration使用。
@Import注解是Java中的元注解,,@Import注解是用于引入其他配置类或Bean的注解。它可以帮助将特定的配置类或Bean注册到Spring容器中,使其可供应用程序使用。
参考:https://blog.csdn.net/Ascend1977/article/details/131391041
2.@Bean一定需要搭配@Configuration才能使用吗?
不需要。Spring允许通过@Bean注解方法来向容器中注册Bean.默认情况下,bean应该是单例的,但是如果我们手动去调用@Bean方法,bean会被实例化多次,这破坏了bean的单例语义。
于是,Spring提供了@Configuration注解,当一个配置类被加上@Configuration注解后,Spring会基于该配置类生成CGLIB代理类,子类会重写@Bean方法,来保证bean是单例的.文章来源:https://www.toymoban.com/news/detail-810119.html
参考:https://blog.51cto.com/u_16099278/7039588
https://blog.csdn.net/z69183787/article/details/121979457文章来源地址https://www.toymoban.com/news/detail-810119.html
到了这里,关于SpringBoot自动装配原理及分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!