Spring源码分析之SpringBoot的自动配置和ComponentScan
1.自动配置
自动配置就是说在Spring需要进行手动配置那么这个就是说会比较麻烦那么SpringBoot就是说实现了自动配置这个就帮我们减少了很多的繁琐的事情(尤其是我们引用了很多的第三方的jar包的时候),现在的几乎可以说是全部的公司都是使用的都是SpringBoot框架(只是就是通过jar包以及在配置文件中配置相关的内容就行了)
2.原理的分析
对于原理的分析的话首先的话我们肯定是查看的项目的运行的入口,下面的就是说SpringBoot的项目的启动方式
@SpringBootApplication
public class SpringSimpleDemoApplication {
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(SpringSimpleDemoApplication.class, args);
}
}
我们一看的话其实就是没有什么特别的地方,在这里其实我就是想说这个就是最特别的地方因为人们常说"人不可貌相 ",这个这是如此,我们点击SpringApplication这个注解发现里面别有洞天
这个我们会发现这个是一个符合的注解,那么对于自动的装配的话我们就是我们这篇文章的重点接下来的话我们就是好好研究这个注解做了什么事情能够实现自动装配
2.1@EnableAutoConfiguration
我们点击进去 @EnableAutoConfiguration 查看。这里我们发现 @EnableAutoConfiguration 注解通过 @Import(AutoConfigurationImportSelector.class) 引入了AutoConfigurationImportSelector这一个类
2.2 AutoConfigurationImportSelector
通过上面的一个实现的图片其实Aware接口的话其实就是不用多说了(这个就是在Bean的生命周期的时候就说过就是进行一些设置的回调接口) 然后order接口的其实就是一个优先级的那么对于自动装配来说的话其实最重要的就是说DeferredImportSelector和ImportSelector接口这个的话我后面说完之后会进行一些拓展的,下面的话就是来说一下这个类的核心方法(getAutoConfigurationEntry):
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 判断是否启动自动装配
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 @SpringBootApplication 的注解属性exclude、excludeName。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取未筛选前的配置类。这里读取了spring.factories 文件的内容
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类
configurations = removeDuplicates(configurations);
// 获取 需要排除的 配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查需要排除的配置类
checkExcludedClasses(configurations, exclusions);
// 移除需要排除的配置类
configurations.removeAll(exclusions);
// 根据 spring-autoconfigure-metadata.properties 中的装配条件进行过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 给 AutoConfigurationImportListener 发布一个onAutoConfigurationImportEvent事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 封装成一个 AutoConfigurationEntry 返回,其中包含,需要自动装配的类(configurations) 和需要排除的类 (exclusions)
return new AutoConfigurationEntry(configurations, exclusions);
}
// 这里我们看到可以通过设置 EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY 属性来 控制是否开启自动化配置 这个就是说在上面我说的那个重要的注解里面的一个属性
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
// 这个就是说加载了Spring.factories 文件的内容也就是说在这一步实现了自动装配
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass() 返回的类型是 EnableAutoConfiguration.class。
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这个方法里面就是实现了自动装配的核心的逻辑:
spring.properties配置文件
我们这里找到 META-INF/spring.factories
看一下
方法总结:
通过上面的图片其实就是不难发现第一点的话就是说META-INF/spring.factories这个文件里面的内容的其实也就是按照key(就是EnableAutoConfiguration.class的全路径) value的形式进行保存的那么这一切都是命中注定的,那么就是通过这个全路径然后获得文件里面对应的value的值(就是说哪些类应该被视为配置类)
第二点: 就是说获得之后还要继续进行一次过滤主要就是通过(这个类的相关的信息正好在上面的图片就能够看清楚哈哈哈)AutoConfigurationImportFilter.class的全路径然后从META-INF/spring.factories获得对应的value值对配置类进行一次过滤
spring-autoconfigure-metadata.properties文件
这个文件的话其实就是相对于spring.factories二者之间是类似的,这个两个文件都是在META-INF目录下面这种格式就是说key.自动配置条件=value,所以总的来说这个文件里面管理的就是说是Bean的装配的条件
假如我的B类要进行自动配置的话必须是要在A类自动配置之后才能够进行装配下面的话我写了一个Demo:
com.example.A.AutoConfigureAfter=com.example.B
那么综上我们其实就是可以发现spring.properties配置文件这个主要就是说负责的就是类的自动装配(也就是说哪些类可以自动配置)但是spring-autoconfigure-metadata.properties文件这个就是说类实现自动配置的条件
2.3 ImportSelector:
这个接口的作用的话其实我们可以通过只要这个就是一个引入的选择器,那么这个接口的目的就是Spring调用selecrImports方法然后获得一个数组然后数据里面对应的类的话就是通过反射加载到Spring容器里面(数组里面的就是被导入的配置类的全路径名)
//这个就是说基于这个类然后获得应该被导入到容器里面的类
String[] selectImports(AnnotationMetadata importingClassMetadata);
//这个就是说对selectImports方法获得的类进行一个过滤不应该装配的类
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
2.4DeferredImportSelector:
这个接口的话就是 ImportSelector的子接口,那么对于这个接口而言在所有 @Configuration
bean 处理完毕后,选择要导入的类除此以外可以通过实现 Ordered
接口或使用 @Order
注解来指定多个 DeferredImportSelector
之间的执行顺序。这个的话不需要那么进行深究
原理的总结:
就是通过SpringBootApplication注解里面的EableAotoConfiguration注解里面的AutoConfigurationImportSelector类来实现的,具体来说的话就是通过classLoader去获得classPath路径下面的META/INF下面的spring.factories文件中内容,然后我们查看这个文件的话其实也就能够以发现这个文件的内容就是通过key value的形式进行数据的存储然后就是获取这个里面的以
EnableAutoConfiguration.class为key的所有符合条件的配置类根据注解@Conditional过滤到不必要的配置类(动态决定是否应用某个自动配置类。这确保了只有在必要的情况下才会加载和配置相应的组件,避免了不必要的资源消耗)当然配置文件中的属性值可以通过@ConfigurationProperties 注解与配置类进行绑定,实现属性的自动注入然后注册到Spring容器里面
新的知识点: @Condition:这个注解就是说只有满足某一个条件的时候才会向IOC容器注入相对应的组件(其实这个就是可以理解为if判断语句)
自动配置实现一手:
1.自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DemoImportSelector.class)
@Documented
public @interface EnableAutoConfigurationDemo {
}
2.Demo的注入选择器
public class DemoImportSelector implements ImportSelector {
//这个的话其实也是可以写你这个META-INF/spring.factories文件里面进行配置
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这里的话我就写了一个类的路径来实现一个自动配置
return new String[]{"com.example.springsimpledemo.autoconfigurationdemo.Demo"};
}
}
3.要进行自动装配的类
public class Demo {
public void say(){
System.out.println("Hello Demo!");
}
}
4.SpringRunnner
@Component
public class DemoSpringRunner implements ApplicationRunner {
@Autowired
Demo demo;
@Override
public void run(ApplicationArguments args) throws Exception {
demo.say();
}
}
5.测试
@SpringBootApplication
@EnableAutoConfigurationDemo
public class DemoSpringApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringApplication.class,args);
}
}
结果显示: 大功告成
2.ComponentScan注解:
其实对于这个注解的话大家就是比较熟悉了他的目的就是说用于扫描指定包中的组件类,并将它们注册为Spring容器中的Bean实现了一个对象创建的解耦的操作(这里的话可以看看我前面的文章这样的话就是更好便于你的理解)
二者之间的联系以及区别:
其实在我看来二者都是说为了依赖注入做准备但是侧重点不一样 ComponentScann这个注解的话就是更加侧重于不需要第三方或者配置文件的参与(可以自己进行设置),但是自动装配不一样他是需要基于第三方配置文件的,但是二者的目的其实都是为了进行解耦以及简便开发
下面我们就简单进行测试
测试类(componentScann):
@Component
public class Demo {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "Demo{name = " + name + ", age = " + age + "}";
}
}
测试代码:
@Test
public void test() {
AnnotationConfigApplicationContext context=
new AnnotationConfigApplicationContext(AppConfig.class);
Demo bean = (Demo) context.getBean(Demo.class);
bean.setName("我是一个测试类");
bean.setAge(1);
System.out.println(bean);
}
测试结果:
测试类(autoConfiguration)
测试1:
//这个注解就是说明只有当Demo.enable=true的时候才会被注入
@ConditionalOnProperty(prefix = "Demo", name = "enable", havingValue = "true")
@Component
public class Demo {
@Value("${Demo.name}")
private String name;
@Value("${Demo.age}")
private Integer age;
public String toString() {
return "Demo{name = " + name + ", age = " + age + "}";
}
}
配置文件(application.yaml):
Demo:
enable: true
name: "我是一个测试类"
age: 1
测试1结果
其实从测试1中就会产生一个疑问那就是说我的ConditionalOnProperty中如果没有第二个属性或者说第三个属性的话那么会不会生效的(这个是肯定是不能的因为后面的两个的属性就是这个类能够实现自动配置的)那么也就是不能注册到Spring容器当中这个的话如果感兴趣的话可以自己写一写进行测试。