要了解SpringBoot的自动装配原理, 我们结合SpringBoot的源码一步步分析.
首先在所有的SpringBoot项目中, 一定会有一个注解@SpringBootApplication.
对于@SpringBootApplication这个注解, 相信接触过SpringBoot项目的小伙伴都会非常熟悉, 这是SpringBoot的一个核心注解, 这个注解一般会贴在我们的启动类中, 代表这是SpringBoot的一个主配置类.
@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 {
观察@SpringBootApplication源码我们可以发现, @SpringBootApplication实际上是一个复合注解, 里面包含了七个注解.
@Target 的作用是设置@SpringBootApplication注解的使用范围, 括号里面的参数 ElementType.TYPE, 表示@SpringBootApplication注解可以使用在接口, 类, 枚举, 注解中.
@Retention 的作用是设置@SpringBootApplication注解的生命周期, 类似于maven依赖的生命周期一样, 括号的参数 RetentionPolicy.RUNTIME 表示运行加载到字节码文件时, 注解依然生效.
@Documented 的作用是, 设置@SpringBootApplication注解可以使用在文档类的文件中.
@Inherited 的作用是设置子类可以继承父类的注解.
以上四个注解都是 Java 提供的原生注解, 可能有些小伙伴早就了解了, 但是为了方便不知道的小伙伴, 啰嗦一点也要说完啦!
@SpringBootConfiguration, 熟练的小伙伴估计一眼就能看出来这个注解的作用就是配置类, 我们可以继续查看 @SpringBootConfiguration 的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
这里面实际上就是一个马甲注解, 里面就是一个 @Configuration注解, 所以这个注解的作用是表示当前类是一个配置类.
@ComponentScan, 表示会扫描@SpringBootApplication所在的当前包及其子包下的内容.
与自动装配相关的注解来了:
@EnableAutoConfiguration 这个注解的作用就是开启自动装配功能.
我们继续跟进查看@EnableAutoConfiguration的相关源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
在@EnableAutoConfiguration的源码中, 我们凭感觉基本可以一眼就能注意到这个类的重点, 没错, 相信自己的直觉:
在这个类中, 通过 @import直接, 引用了另外一个类EnableAutoConfigurationImportSelector, 通过这个类的名称, 我们其实可以见名知意: 自动配置导入选择器类, 可想而知, 这个类的重要性. 我们继续查看这个类具体的核心工作是什么.
此时我们会发现, 这个类中, 只定义了一个方法, 而且这个方法看起来跟我们的自动装配没有多大关系. 但是既然这是一个选择器类, 那么它的功能一定是有相关选择具体哪些bean需要自动装配的相关功能实现的, 不可能只有这么一点代码. 此时我们可以看到, 这个类是继承于 AutoConfigurationImportSelector 类, 那我们继续查看其父类, 实现会不会就是在父类里面呢.
此时我们发现, 这个类里面会有很多方法, 而核心的方法就是
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
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;
}
这里面调用了一个SpringFactoriesLoader.loadFactoryNames的静态方法, 所以我们继续点击进去
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String propertyValue = properties.getProperty(factoryClassName);
String[] var8 = StringUtils.commaDelimitedListToStringArray(propertyValue);
int var9 = var8.length;
for(int var10 = 0; var10 < var9; ++var10) {
String factoryName = var8[var10];
result.add(factoryName.trim());
}
}
return result;
} catch (IOException var12) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var12);
}
}
而这里最重要的一步, 就是通过一个类加载器, 到classpath里面加载文件的内容, 而路径就是
META-INF/spring.factories.
而我们可以观察, 如果是SpringBoot集成的Jar, 它里面的文件结构基本都是这样的:
观察可以发现, 在这些Jar包里面, 都会有一个META-INF文件, 并且在这个文件中, 都会有一个文件spring.factories. 而spring.factories文件内容我们一德鲁伊连接池为例
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
这里面实际上就是指定了, 自动装配德鲁伊连接池时, 需要加载的配置类, 开发人员在这个配置类里面, 设置了许多默认的配置, 而SpringBoot的所做的自动装配工作, 就是通过这个配置类, 通过这些默认的配置, 或者加上我们手动在SpringBoot配置文件中配置的内容, 如德鲁伊连接池, 我们需要配置数据库连接的三要素, 通过这些一系列的配置, SpringBoot就能成功把德鲁伊连接池这个bean初始化出来供我们使用, 其他的bean自动装配也是类似, 可能有些bean甚至只需要我们导入相关的依赖, SpringBoot即可通过默认配置完成bean的创建和初始化, 所以相对于Spring来说, SpringBoot可以通过这个自动装配, 剩下许多不需要重复的配置, 但是实际上这些配置是存在的, 只不过已经默认写在了代码中而已.