一. 前言
- 我们在写Spring Boot的程序代码的时候, 可以注入很多我们没有定义过的Bean.
- 例如:
@Autowired
private ApplicationContext applicationContext;
@Autowired
public DataSourceTransactionManager transactionManager;
@Autowired
public AutowireCapableBeanFactory autowireCapableBeanFactory;
- 这些对象是什么时候注入进去的, Spring 是如何做的,我们可以通过阅读源码来解决这个问题.
二.Spring Boot自动装配的原理分析.
2.1 源码阅读
-
要搞清楚自动装配的原因, 我们需要去分析Spring Boot的启动类.
-
@SpringBootApplication 标注的类 就是SpringBoot项目的启动类, 这个注解也是SpringBoot实现自动配置的核心
-
元注解:
- JDK中提供了4个标准的用来对注解类型进行注解的注解类, 我们称之为 meta-annotation(元注
解) - @Target 描述注解的使用范围(即被修饰的注解可以用在什么地方).
- @Retention 描述注解保留的时间范围.
- @Documented 描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
- @Inherited 使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则
其子类将自动具有该注解)
- JDK中提供了4个标准的用来对注解类型进行注解的注解类, 我们称之为 meta-annotation(元注
-
@SpringBootConfiguration
里面就是@Configuration, 标注当前类为配置类, 其实只是做了一层封装改了个名字而已.
(@Indexed注解,是用来加速应用启动的, 不用关心), 本质上就是将启动类交给Spring进行管理
- @ComponentScan (包扫描)
可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包, 如果没有定义
特定的包, 将从声明该注解的类的包开始扫描, 这也是为什么SpringBoot项目声明的注解类必须要在启动类的目录下,这样通过@Controller , @Service , @Repository , @Component , @Configuration ,@Bean标注的类/对象,就会被Spring进行管理. excludeFilters 自定义过滤器, 通常用于排除一些类, 注解等
2.2 @EnableAutoConfiguration(自动装配的核心注解)
- 观察@EnableAutoConfiguration注解的实现:
这个注解包含两部分:
2.2.1@AutoConfigurationPackage
源码如下:
这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class
-
registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry): 这个方法是ImportBeanDefinitionRegistrar接口的一部分,用于在Spring应用程序上下文中注册额外的bean定义。当一个@Configuration类使用了@Import注解并且指定了实现ImportBeanDefinitionRegistrar接口的类时,这个方法会被调用。在这个例子中,它使用PackageImports(一个假定存在的辅助类)来获取包名,并将这些包名作为字符串数组传递给另一个重载的register方法,以便在注册表中注册这些包中的类作为Spring bean定义。
-
determineImports(AnnotationMetadata metadata): 这个方法是DeterminableImports接口的一部分,用于确定哪些类应该被导入到当前的@Configuration类或@SpringBootApplication类中。在这个例子中,它返回了一个单例集合(Set),其中包含了一个PackageImports对象。这个对象封装了应该被扫描的包的信息,这样Spring就能自动发现并注册这些包中的@Component、@Service、@Repository等注解的类。
Registrar实现了 ImportBeanDefinitionRegistrar 类, 就可以被注解@Import导⼊到spring
容器里. (String[])(new PackageImports(metadata)).getPackageNames().toArray(new
String[0]) : 当前启动类所在的包名. 所以:@AutoConfigurationPackage 就是将启动类所在的包下⾯所有的组件都扫描注冊到spring 容器中.
2.2.2 @Import({AutoConfigurationImportSelector.class})
-
使用@Import注解,导入了实现ImportSelector接口的实现类.
-
selectImports() 方法底层调用 getAutoConfigurationEntry() 方法, 获取可自动配置的配置类信息集合.
-
getAutoConfigurationEntry() 方法通过调用getCandidateConfigurations(annotationMetadata, attributes) 方法获取在配置文件中配置的所有自动配置类的集合
-
getCandidateConfigurations 方法的功能:
- 获取所有基METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imp
orts 文件, META-INF/spring.factories 文件中配置类的集合. 在引入的起步依赖中, 通常都有包含以上两个文件
- 获取所有基METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imp
这里面包含了很多第三方依赖的配置文件(连续按两下shift可以查看对应的源码)
- 在加载自动配置类的时候, 并不是将所有的配置全部加载进来, 而是通过@Conditional等注解的判
断进行动态加载.@Conditional是spring底层注解, 意思就是根据不同的条件, 来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效. - META-INF/spring.factories文件是Spring内部提供的一个约定俗成的加载方式, 只需要在模块的
META-INF/spring.factories文件中配置即可, Spring就会把相应的实现类注入到Spring容器中.
注: 会加载所有jar包下的classpath路径下的META-INF/spring.factories文件, 这样文件不止一个
- 比如 Redis的配置: RedisAutoConfiguration
由于我这个项目的pom依赖中,没有加入redis相关的依赖, 所以会根据注解@ConditionalOnMissingBean不进行加载
三.总结.
-
SpringBoot 自动配置原理的大概流程如下:
当SpringBoot程序启动时, 会加载配置文件当中所定义的配置类, 通过 @Import 注解将这些配置类全
部加载到Spring的IOC容器中, 交给IOC容器管理 -
SpringBoot的自动配置原理源码口是 @SpringBootApplication 注解, 这个注解封装了3个注
解- @SpringBootConfiguration 标志当前类为配置类
- @ComponentScan 进行包扫描(默认扫描的是启动类所在的当前包及其子包)
- @EnableAutoConfiguration
- @Import 注解 : 读取当前项目下所有依赖jar包中 META-INF/spring.factories ,
METAINF/spring/org.springframework.boot.autoconfigure.AutoConfigurat
ion.imports 两个文件里面定义的配置类(配置类中定义了 @Bean 注解标识的方法) - @AutoConfigurationPackage : 把启动类所在的包下面所有的组件都注入到 Spring
容器中
- @Import 注解 : 读取当前项目下所有依赖jar包中 META-INF/spring.factories ,