Spring Boot 自动装配原理 / 2 在上一篇内容中,我们简述了关于Spring Boot应用的几个核心注解,核心注解的元注解,以及Spring Boot应用的粗粒度启动过程,还没有看的同学可以去看一看,链接在这里 。这一节我们会对自动装配属性的解析,到自动装配的过程进行一个详细的分析论述,本篇源码会比较多,也希望大家耐心阅读。
主类是如何被加载注册的 通过第一篇内容我们可以知道,自动装配注解@EnableAutoConfiguration
是@SpringBootApplication
的元注解,而@SpringBootApplication
又注解于我们的应用启动类上,那么我们的应用启动类(即主类)是如何被加载进Spring的容器中的呢?
Spring Boot应用通过SpringApplication.run(primarySource, args)
方法进行启动,而参数primarySource
需要传入的是应用加载的主要来源,即我们主类的类定义。从Spring initializer 构建的Demo我们可以看到,primarySource
的传入值是DemoApplication.class
。
1 2 3 4 5 6 @SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
在Spring Boot应用初始化阶段(上篇有提到,没看过的去看一下,明确一下概念),run方法会构建一个SpirngApplication
对象实例,并将我们传入的主类定义保存在对象实例的primarySources
字段中。
1 2 3 4 5 6 7 8 9 public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet <>(Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = deduceMainApplicationClass(); }
这个字段有什么用我们过会再说,现在先让我们记下SpringApplication
对象实例的primarySources
储存了我们的启动类定义。
我们先顺着往下看deduceFromClasspath()
方法,该方法用于推断我们的Spring应用类型(Servlet、Reactive、None),而推断的方法则是通过类加载器对几种Spring应用类型的核心类进行加载,返回true则表示加载成功,反之抛出异常则表示加载失败,类路径中不存在该类。这种推断方式很聪明,在之后的自动装配部分也会有类似的逻辑。再之后是setInitializers()
方法,这个方法会设置ApplicationContextInitializer
到SpringApplication
实例,这不是我们的重点,我们的重点在于它的嵌套方法getSpringFactoriesInstances(type)
,也是我在上篇内容标记的第一个重点。
``getSpringFactoriesInstances()`的内部逻辑如下,具体的操作我用注释标在了代码上面。
1 2 3 4 5 6 7 8 9 10 11 private <T> Collection<T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet <>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
进入SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法我们就可以看到在之前反复提到的META-INF/spring.factories
文件加载过程。在阅读加载过程之前,我们先找一个spring.factories
文件看看文件结构。在这边我们就看一下spring-boot-autoconfigure-2.2.5.RELEASE.jar
里的META-INF/spring.factories
文件内容,spring.factories
文件在本质上还是一个properties
类型文件,由于该文件内容较多,我将从其中抽取部分展示,具体内容还请各位同学把Jar包Down下来研究一下。
1 2 3 4 5 6 7 8 9 10 11 12 org.springframework.context.ApplicationContextInitializer =\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener org.springframework.context.ApplicationListener =\ org.springframework.boot.autoconfigure.BackgroundPreinitializer org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ ...
我从其中抽取了些比较具有代表性的自动装配类,就包括在初始化阶段要加载得初始化器类、应用监听器、以及和我们主题密切相关得自动装配类列表。在自动装配类列表中我抽取了Redis、AOP、MongoDB的自动装配类在此作展示,其实autoconfigure
包中还有很多我们日常开发中使用到的许多类库、中间件的自动装配类,譬如ElasticSearch、oauth2、Jap等等,就算autoconfigure
包中没有包含我们需要的自动装配类,我们也可以依据此规则自行实现,具体实现方法我会在后面的内容进行分析,此处先行搁置。
在看过spring.factories
的文件结构之后,去分析SpringFactoriesLoader.loadFactoryNames
方法就会简单很多。传入参数type
为需要在spring.factories
文件集中检索的key,而返回的结果则是文件集中与之相关的所有类的全限定名。具体逻辑如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public static List<String> loadFactoryNames (Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories (@Nullable ClassLoader classLoader) { 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); } }
看完这spring.factories
文件的加载逻辑之后,我们已经知晓了自动装配的装配类来源是哪,接下来我们就需要继续弄明白,主类是如何被加载注册进容器,以及spring是如何解析注解在主类上的注解@SpringBootApplication
及其元注解EnableAutoConfiguration
的。
Spring应用上下文的创建和准备 在完成Spring应用的初始化阶段后,就需要开始进行启动阶段的准备工作,构建Spring应用上下文可以作为这一阶段的核心工作进行解读。
在Spring应用的run方法中我们可以看到Spring应用上下文的构建过程。在Spring应用初始化阶段通过类加载器推断出来的应用类型在此处就派上了用场。Spring会根据不同的应用类型初始化对应的Spring应用上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected ConfigurableApplicationContext createApplicationContext () { Class<?> contextClass = this .applicationContextClass; if (contextClass == null ) { try { switch (this .webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break ; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break ; default : contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException ( "Unable create a default ApplicationContext, please specify an ApplicationContextClass" , ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
在初始化Spring上下文的同时,我们的Bean容器,BeanFactory
也会同时完成初始化操作。在完成初始化操作之后Spring应用上下文及Bean容器还需要完成一些准备工作以进入应用启动阶段。而主类则是在这一时间点进行的注册装配工作。
在此时间点,Spring上下文会完成环境变量配置、应用初始化器、通知监听器等一系列事件,但与我们所关心(主类装配)的关键代码是这么几行。
1 2 3 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty" ); load(context, sources.toArray(new Object [0 ]));
先通过getAllSources
方法获取到Spring应用在初始化及准备阶段所有配置的资源内容。还记得前边说SpringApplication
对象实例的primarySources
储存了我们的启动类么?在这儿就用上了。我们看一看getAllSources()
方法的内部逻辑就明白了。
1 2 3 4 5 6 7 8 9 10 11 12 public Set<Object> getAllSources () { Set<Object> allSources = new LinkedHashSet <>(); if (!CollectionUtils.isEmpty(this .primarySources)) { allSources.addAll(this .primarySources); } if (!CollectionUtils.isEmpty(this .sources)) { allSources.addAll(this .sources); } return Collections.unmodifiableSet(allSources); }
获取到了主类的信息后,就可以通过load(contenxt, source)
方法将我们的主类装在进bean容器中了。Load()
方法的内部逻辑是Spring应用上下文的BeanFactoy
和source构建一个BeanDefinitionLoader
,之后检测类资源是否持有@Component
注解(由于主类持有@SpringBootApplication
注解,而该注解的元注解持有@Configuration
注解,@Configuration
的元注解中持有@Compontent
注解,即持有判定通过),判定通过则进行类注册操作,由于这部分逻辑入栈较多,所以就不在此贴代码了,想要深入了解的同学可以自己跟着方法堆栈看一看。
小结 通过上面的内容,我们了解了Spring boot应用的元注解、自动装配的信息来源以及带有自动装配注解的主类是如何被装载进Bean容器的,Spring应用也进入了应用启动阶段。接下来的部分将会具体分析Spring在启动阶段是如何读取主类的自动装配开关以及自动装配过程是如何自动过滤不需要的自动装配类的(在spring.factories
中我们看到了很多自动装配类,但实际我们只需要我们需求的部分,而其他的则需要被过滤掉)。