一、什么是自动装配
在使用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方法:广播事件
最后根据多次过滤、判重返回配置类合集
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返回的值
总结
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://blog.51cto.com/u_16099278/7039588
https://blog.csdn.net/z69183787/article/details/121979457