SpringBoot自动装配核心原理(上)
在前面的章节里,笔者对Spring的核心内容做了比较详细的介绍,本章将对SpringBoot的自动化配置的核心原理进行详细介绍,在介绍本章内容之前,读者应该了解一些关于Spring的重点知识,包含@Import注解,ImportSelector接口,以及ImportBeanDefinitionRegistrar,这三个Spring拓展方式笔者在前面的章节做了详细的分析,并对源码也做了较为深入的分析,如果读者不清楚这部分内容的,不建议看本篇文章,因为SpringBoot的核心应用就是基于这些方式改造的,好了,废话不多说,我们开始进入正题。
首先新建一个SpringBoot项目,这里就不再SpringBoot源码工程写案例了,
package com.example.boot01;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Boot01Application {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(Boot01Application.class, args);
String[] names=applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
这里我们输出一下目前SpringBoot帮我们创建了多少个对象,
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
boot01Application
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
springApplicationAdminRegistrar
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
applicationAvailability
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory
org.springframework.boot.context.internalConfigurationPropertiesBinder
org.springframework.boot.context.properties.BoundConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
lifecycleProcessor
spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
taskExecutorBuilder
applicationTaskExecutor
spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
taskSchedulerBuilder
spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties
org.springframework.aop.config.internalAutoProxyCreator
Process finished with exit code 0
可以看到SpringBoot帮我们创建了很多很多的对象,看过笔者前面的章节的同学一定会觉得里面有一些类比较熟悉,有一些类比较陌生,比如什么后置处理器,比如什么事件监听等等,这里当然不会介绍他们,而是会介绍一个类,除了我们知道的类,我们还发现有这么一个类AutoConfigurationPackages,这个是从哪创建的呢?
我们看开这个注解,找到这个@EnableAutoConfiguration,然后再跟踪进去,可以看到有个@AutoConfigurationPackage注解,再跟踪进去,可以发现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
这里就出现了@Import,笔者在前面的文章介绍过@Import有三种方式,这里就不再和大家一起复习了,这就是采用@Import的方式向SpringBoot注册了这个一个类AutoConfigurationPackages.class,我们再回到上一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
这里同样出现了@Import(AutoConfigurationImportSelector.class),@Import这么一种方式,而这个导入的类却不是一个普通类,
可以看到它实现了ImportSelector,也就是说Spring会去执行
String[] selectImports(AnnotationMetadata var1);
上面这个方法,我们写一个demo。然后导入这个类
public class TestImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
System.out.println("----------------");
return new String[0];
}
}
package com.example.boot01;
import com.example.boot02.spi.Boot02_Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(TestImport.class)
public class Boot01Application {
public static void main(String[] args) {
//Boot02_Test boot02_test= new Boot02_Test();
ApplicationContext applicationContext= SpringApplication.run(Boot01Application.class, args);
/*String[] strings= applicationContext.getBeanDefinitionNames();
for (String string : strings) {
System.out.println(string);
}*/
//System.out.println(applicationContext.getBean(Boot02_Test.class));
}
}
执行:
2020-12-19 17:53:15.755 INFO 5624 --- [ main] com.example.boot01.Boot01Application : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 5624 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)
2020-12-19 17:53:15.757 INFO 5624 --- [ main] com.example.boot01.Boot01Application : No active profile set, falling back to default profiles: default
----------------
2020-12-19 17:53:16.071 INFO 5624 --- [ main] com.example.boot01.Boot01Application : Started Boot01Application in 0.522 seconds (JVM running for 0.99)
Process finished with exit code 0
可以看到代码已经被执行了。那么这个return new String[0];就是用来创建对象的,好了我们回到@Import(AutoConfigurationImportSelector.class),同样我们这里依旧会看到一个方法
但是SpringBoot会被增强执行另外一个方法,但是这都不重要,因为它始终会执行这样一段代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
这里的代码十分重要,它就是SpringBoot自动装配的核心方法,我们调试一下
可以看到它获取了这么多类,这些类是从哪里来的?我们跟踪一下List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);这句代码,一直跟踪会看到下面的代码
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;
}
然后我们可以看到这里做了解释就是说所有的类都是来自于 META-INF/spring.factories
这里就可以看到,确实是在这个文件找到的需要加载的类,然后我们回到代码
可以看到这里并不是所有的类都加载了,而是对配置类进行了筛选,为什么筛选?这也很好理解,因为我们并没有使用它,所以就不会加载到Spring容器里面,注意是加载到Spring容器,而这里的筛选是完成自动装配的重要操作,我会在下面的章节进行描述。好了可能读者还没体会到这段代码的牛逼之处,下面我将做一个案例让大家看看SpringBoot的设计到底有多精髓。
首先新建两个Boot项目
命名为boot1,boot2,然后在boot2上加一个类
/**
* @author 七天0
* @date 2020/11/29 0029 11:52
* 概要 :
*/
@Component
public class Boot02_Test {
@Bean
public String string(){
return "";
}
}
这个很简单,就不说了,然后我们在对这个boot2打包,然后放在boo1下
然后这样我们就可以使用我们自己写的对象了,于是我们在boot1下进行测试
@SpringBootApplication
public class Boot01Application {
public static void main(String[] args) {
Boot02_Test boot02_test= new Boot02_Test();
ApplicationContext applicationContext= SpringApplication.run(Boot01Application.class, args);
/*String[] strings= applicationContext.getBeanDefinitionNames();
for (String string : strings) {
System.out.println(string);
}*/
System.out.println(applicationContext.getBean(Boot02_Test.class));
}
}
执行代码:
2020-12-19 21:28:24.680 INFO 15472 --- [ main] com.example.boot01.Boot01Application : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 15472 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)
2020-12-19 21:28:24.682 INFO 15472 --- [ main] com.example.boot01.Boot01Application : No active profile set, falling back to default profiles: default
2020-12-19 21:28:25.024 INFO 15472 --- [ main] com.example.boot01.Boot01Application : Started Boot01Application in 0.564 seconds (JVM running for 1.144)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.boot02.spi.Boot02_Test' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1179)
at com.example.boot01.Boot01Application.main(Boot01Application.java:22)
我们看到我们无法从容器找到我们自定义的Boot02_Test,但是我们可以自己手动new出来,道理很简单,这毕竟是我们自己添加的依赖,那么如果我们要将Boot02_Test交给我们的Spring管理,怎么办?只需要更改一下boot2的代码就可以了
然后我们再打包,在执行
2020-12-19 21:30:54.851 INFO 9456 --- [ main] com.example.boot01.Boot01Application : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 9456 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)
2020-12-19 21:30:54.854 INFO 9456 --- [ main] com.example.boot01.Boot01Application : No active profile set, falling back to default profiles: default
2020-12-19 21:30:55.168 INFO 9456 --- [ main] com.example.boot01.Boot01Application : Started Boot01Application in 0.528 seconds (JVM running for 1.073)
com.example.boot02.spi.Boot02_Test@4b41dd5c
这里就可以很清楚的看到我们的Boot02_Test已经交给了我们的Spring进行了管理。所以这也就解释了SpringBoot是加载所有maven里面的META-INF/spring.factories文件,也就是说SpringBoot会找到Maven下面的所有META-INF/spring.factories装在进入Spring容器,而这种装载是自动的,让然还没有结束,这里提出一个问题,我们可以看到这里面有很多的配置信息,那么我们是所有的东西都要添加吗?希望读者自行思考,我将会在下个章节做介绍。最后如果没有看懂@Import这一系列的,请一定要看前面的Spring源码章节,不然就无法理解为什么@Import这么厉害。