建议先看Spring-@ComponentScan分析
SpringBoot的例子就不写了,最熟悉的代码如下:
@SpringBootApplication
public class SimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleApplication.class,args);
}
}
SpringApplication的作用是什么?@SpringBootApplication注解有什么功能?Springboot怎么做自动装配是怎么做的?这篇文章就回答了相关的问题。
通过它来创建加载SpringApplication
在Spring的时候知道,得先创建Application,在启动的得把配置类,或者指定扫描的包,这类的定义信息告诉Spring,它才可以启动。比如下面的代码
public class TestAopApplication {
public static void main(String[] args) {
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 告诉扫描的路径
context.scan(TestAopApplication.class.getPackage().getName());
// 显性调用refresh方法,启动容器
context.refresh();
TestBean bean = context.getBean(TestBean.class);
bean.sayHello();
}catch (Exception e){
e.printStackTrace();
}
}
}
那么对于SpringApplication来说,它也是上面类似的逻辑,创建Application
,创建好Environment
,在增加一系列的监听方法,比如application创建好了的回调,环境创建好了的回调等等,通过这些监听就可以进一步的定制化对应的对象。
上面说的只是一个思想,实际上它大体的做法是,创建一个BootStrapApplication用来它引导创建ApplicationContext和Environment,并且创建一直到启动环节中,都有对应的监听方法,来对增强操作(比如,日志的选择,配置文件的解析(Application.yaml)等等),此外还会设置创建好的ApplicationContext加载资源的路径,设置一些属性(增加BeanFactoryPostProcess)。在调用ConfigurableApplicationContext#refresh()
方法之前,会将BootStrapApplication关闭掉。在容器跑起来之后,会发布事件,还会调用ApplicationRunner
和CommandLineRunner
。
继续上面的说,在SpringApplication
类里面利用BootStrap来创建Application的时候,会有一系列的监听类。但是这些监听类要怎么创建呢?
有这样的机制,如果想要那个接口需要拓展,直接写在里面,并且会将之前已经加载好的配置文件缓存起来,用的时候获取一下,并且实例化,就好了。回到Springboot中,看看那些接口需要拓展。
BootstrapRegistryInitializer (他是在BootstrapRegistry创建好了之后,在使用之前的一个初始化回调)
ApplicationContextInitializer (在refresh方法之前的回调,用来初始化ApplicationContext)
ApplicationListener(这是典型的Spring里面的事件监听,指定事件来监听)
SpringApplicationRunListener(在SpringBoot启动的时候提供的监听类,在各个环节都有方法)。
需要注意的是,它的实现类在Springboot中就一个
EventPublishingRunListener
,但是EventPublishingRunListener里面聚合了SimpleApplicationEventMulticaster
,通过它来做发布事件,会在创建EventPublishingRunListener的时候将ApplicationListener传递给SimpleApplicationEventMulticaster。一般来说,这个玩意基本不会动的。
SpringBootExceptionReporter(在启动报错的时候回调)
EnvironmentPostProcessor
这个是重点:
它是做Environment
对象的增强处理的。具体做法如下:
EnvironmentPostProcessorApplicationListener
监听了它感兴趣的事件,SpringApplication创建好Environment之后,会通过EventPublishingRunListener
发布对应的事件,EnvironmentPostProcessorApplicationListener来处理,它再从处理的时候又会从spring.factories
中加载EnvironmentPostProcessor
的实现类,来做Environment
的后置处理。其中就包括解析Application.yaml文件
。
AutoConfigurationImportListener(在自动配置类导入的时候回调)
EnableAutoConfiguration(自动配置类)
这个是重点:
还记得之前说配置类中@Import注解嘛?有两个特殊的类,ImportSelector
和ImportBeanDefinitionRegistrar
。其中ImportSelector
可以返回一个String数组,它里面包含的是类的全类名,Spring拿到这个之后,会加载对应类,继续解析一遍。这其实就是自动装配,这个功能不是Springboot搞出来的,之前就存在这样的支持。
那么key是EnableAutoConfiguration,value是对应的实现类,用逗号分隔,在ImportSelector
的实现类里面从spring.factories
里面读取到,返回给Spring,Spring就会继续把他们解析一遍。可以在自动配置类上面写那些之前在@Configuration类上面写的注解,Spring会解析他们,将Bean注册到Spring中。
… 因为涉及的地方太多了,这里就列举几个有经典的。
对应的代码在 SpringFactoriesLoader#loadFactories(Class , @Nullable ClassLoader )
这里的代码不难理解,总体就是利用classLoader.getResource加载配置文件,读取配置文件,将v逗号分割。
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 通过classload和指定需要的lei找到对应的实现类的集合了,
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
// 实例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
// order接口排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
// 真正做加载的地方
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中拿,缓存的key是对应的classloader,v是属于它的数据
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载配置文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 去掉左右的空格,逗号分割,
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
当然,SpringApplication绝不是我说的这么简单,这里只是说了一些大体的思路,具体的实现的细节这里不展开说了
他是一个复合注解,
而它下面的属性对应的都是元注解里面的属性。主要分析如下:
就是一个配置类。没有什么可说的了。
可以看到它导入了一个ImportSelector,可以用它来读取spring.factories
里面的自动装配的全类名,经过处理返回给Spring来解析。处理其实就是它的属性excludeName了,拿到所有的自动装配的类的全类名之后,从集合中将指定的移除就好了,所以,上面的两个属性是一个意思,并且exclude最后还是要变为String的。调用的是Class.getName()。
此外,还可以在这里可以做一些Condition的判断,比如AutoConfigureAfter
,AutoConfigureBefore
,关于这个判断,之后专门说说
还得注意,还有一个注解AutoConfigurationPackage
他会往Spring容器中注册一个BasePackages,bean的名字叫做 AutoConfigurationPackages.class.getName()
,它里面保存了 属性所指定的包名,如果属性为空,就是当前注解所在的类。放在容器里面之后就可以通过AutoConfigurationPackages
来操作,存储自动注入的包是为了延迟引用,比如说JPA的实体扫描。
这注解之前已经分析过了,相当于调用context.scan(String);
方法,但是它搞了两个过滤器。还都是excludeFilters
TypeExcludeFilter
可以从Spring容器里面获取TypeExcludeFilter的实现类,循环调用。
AutoConfigurationExcludeFilter
在做@ComponentScan的时候排除自动配置类
详细的SpringApplication类的运行过程,就不展开说了,Springboot的自动装配和启动就说到这里了。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。