1 简介
本文是SpringCloud源码系列第一篇 — SpringBoot自动配置源码剖析,抛砖引玉。
在剖析SpringCloud源码之前,我们先了解一下SpringBoot的自动配置原理。熟悉SpringBoot自动配置原理能够有助于我们更好地理解SpringCloud原理。SpringBoot是由Pivotal团队提供的Java快速开发框架,基于Spring框架,快速整合一些常用第三方依赖(例如:内置Tomat/Jetty/Undertow等常用Http服务器),最终以可执行JAR的方式启动并运行Java进程。
SpringBoot的核心理念是“约定优于配置”,但享受这一便利的前提是了解SpringBoot相关的“约定”,并对其背后实现原理有一定理解。知其然更要知其所以然,这是本文希望能够带给大家的一点收获。
2 Hello World
SpringBoot上手很简单,只需在项目中引入SpringBoot的POM,然后给项目启动类加上@SpringBootApplication注解,以及在Resourece目录下添加SpringBoot的核心配置文件(application.properties或application.yml,下文统一以application.yml为例)即可。
SpringBoot Maven POM:
org.springframework.boot spring-boot-starter-parent 2.1.0.RELEASE
启动类注解:
@SpringBootApplicationpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args); }}
配置文件(application.yml):
spring: application: name: eureka-serverserver: port: 9000 #端口
项目启动之后,控制台打印SpringBoot启动标志即表示项目已经启动成功。
本文后续的源码剖析依赖的SpringBoot和Spring Core的版本分别是:
spring-boot:2.1.0.RELEASE
spring-core:5.1.2. RELEASE
3 自动配置源码剖析
在上述简单的Hello World项目基础之上,如果我们需要增加一些实际开发过程中用到的第三方组件(如:RabbitMq、Redis等),只需在SpringBoot配置文件(
application.yml)中增加一些全局配置。然后我们就可以在项目当中直接注入并使用
RabbitTemplate,
RedisTemplate等。这些第三方组件是如何引入依赖?相关Bean又是如何声明及注入?我们带着这些问题开始代码之旅。
3.1 万物伊始@SpringBootApplication
一切的开始都源于
@SpringBootApplication,我们先看看它的源码:
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { ... @AliasFor(annotation = EnableAutoConfiguration.class) Class>[] exclude() default {}; ... @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class>[] scanBasePackageClasses() default {};}
它主要传递依赖三个注解:
@SpringBootConfiguation:声明所注解的类是一个SpringBoot配置类。
@ComponentScan:Spring核心注解,主要完成classpath的扫描及加载。默认加载启动类所在的包路径及其子包下的class文件。
@EnableAutoConfiguation:SpringBoot实现自动配置的关键注解。
3.2 自动配置的奥义@EnableAutoConfiguation
@EnableAutoConfiguation注解的源码如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class>[] exclude() default {}; String[] excludeName() default {};}
它依赖的核心注解是@Import(AutoConfiguarationImportSelector)。借助AutoConfigurationImportSelector,SpringBoot应用扫描所有符合条件的@Configuration配置并将其加载到当前IOC容器。@Import是Spring核心注解,详细可以参考Spring doc相关文档和源码,这里就不做过多展开。
进一步深入到
AutoConfiguarationImportSelector源码,粗一看,其中
AutoConfiguarationImportSelector重载
ImportSelector接口的
selectImports方法源码如下:
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
如果认为Spring实际加载各种@Configuration逻辑即是如此(网上很多SpringBoot源码剖析文章也都认为如此),那就错了。
仔细分析发现,
AutoConfiguarationImportSelector是实现
DeferredImportSelector接口。
DeferredImportSelector继承
ImportSelector接口的同时,提供了一个默认返回
null的
default方法:
getImportGroup()。
AutoConfiguarationImportSelector在实现
DeferredImportSelector接口的同时,重载了
getImportGroup方法,其源码如下:
@Overridepublic Class extends Group> getImportGroup() { return AutoConfigurationGroup.class;}
进一步阅读AutoConfigurationGroup的源码,可以看到其核心的2个方法源码如下:
(1) process方法(主要加载各种自动配置类xxxAutoConfiguration)
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state( deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); }}
(2) selectImports方法(将加载好的自动配置类以迭代器的模式返回)
public Iterable selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } Set allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions) .flatMap(Collection::stream).collect(Collectors.toSet()); Set processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations) .flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); processedConfigurations.removeAll(allExclusions); return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) .stream() .map((importClassName) -> new Entry( this.entries.get(importClassName), importClassName)) .collect(Collectors.toList());}
为何SpringBoot不直接利用ImportSelector接口的selectImports方法来加载自动配置类,而要曲折地通过Group方式来加载,SpringBoot在DeferredImportSelector类的JavaDoc中也给出了解释:
Implementations may also provide an {@link #getImportGroup() import group} whichcan provide additional sorting and filtering logic across different selectors.
简单总结就是:提供Group方法来加载,是为了给不同的ImportSelector提供额外的排序和过滤逻辑扩展。
继续跟踪
process方法中的关键步骤(
getAutoConfigurationEntry方法),进一步查看源码:
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions);}
在执行完getCandidateConfigurations(annotationMetadata, attributes)之后,所有需要的组件自动配置类(xxxAutoConfiguration)都以全类名的方式返回(例如我们看到的mongo,redis等),然后这些配置类会自动注入到Spring容器。
那么,这些自动配置类是从哪里来的呢?继续跟踪源码:
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;}
public static List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } }}
上述loadSpringFactories方法最终依赖classLoader加载项目resources目录下所有的/META-INF/spring.factories。截取其中一部分配置,我们会很惊奇的发现里面包含有我们熟悉的RabbitMq等配置。
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfigurationorg.springframework.boot.autoconfigure.aop.AopAutoConfigurationorg.springframework.boot.autoconfigure.amqp.RabbitAutoConfigurationorg.springframework.boot.autoconfigure.batch.BatchAutoConfigurationorg.springframework.boot.autoconfigure.cache.CacheAutoConfigurationorg.springframework.boot.autoconfigure.cassandra.CassandraAutoConfigurationorg.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfigurationorg.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigurationorg.springframework.boot.autoconfigure.context.MessageSourceAutoConfigurationorg.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
至此,我们就可以追踪溯源了SpringBoot的自动配置原理:
(1) 通过@EnableAutoConfiguation开启自动配置的奥义;
(2) 借助AutoConfigurationImportSelector魔法工具来完成XXXAutoConfiguration类加载。它实际上是通过启动类加载所有resources目录下的spring.factories文件,而SpringBoot的spring.factories文件默认包含了很多第三方中间件的配置。
3.3 关键先生XXXAutoConfiguration
我们挑选其中
RabbitAutoConfiguration这个配置类,进一步深入源码。
RabbitAutoConfiguration内部通过
@Bean注解声明RabbitTemplate对象,这就是为什么在项目当中没有定义
RabbitTemplate,而我们却能直接注入这个对象并使用的根本原因。
@Bean@ConditionalOnSingleCandidate(ConnectionFactory.class)@ConditionalOnMissingBeanpublic RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { PropertyMapper map = PropertyMapper.get(); RabbitTemplate template = new RabbitTemplate(connectionFactory); MessageConverter messageConverter = this.messageConverter.getIfUnique(); if (messageConverter != null) { template.setMessageConverter(messageConverter); } template.setMandatory(determineMandatoryFlag()); RabbitProperties.Template properties = this.properties.getTemplate(); if (properties.getRetry().isEnabled()) { template.setRetryTemplate(new RetryTemplateFactory( this.retryTemplateCustomizers.orderedStream() .collect(Collectors.toList())).createRetryTemplate( properties.getRetry(), RabbitRetryTemplateCustomizer.Target.SENDER)); } map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis) .to(template::setReceiveTimeout); map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis) .to(template::setReplyTimeout); map.from(properties::getExchange).to(template::setExchange); map.from(properties::getRoutingKey).to(template::setRoutingKey); map.from(properties::getQueue).whenNonNull().to(template::setQueue); return template;}
细心的同学会发现XXXAutoConfiguration 自动配置类在其内部通过@Bean注解声明对象的同时,还会带上@ConditionalOnSingleCandidate或@ConditionalOnMissingBean的注解。这是SpringBoot基于项目依赖上下文实现条件装配Bean的核心(例如:项目依赖中有tomcat依赖Jar,则自动加载tomcat容器,有Jetty依赖Jar,则自动加载Jetty容器),关于条件装配,我们将在SpringCloud源码系列的后续文章中详细讲解。
4 SpringApplication初始化启动
任何一个Spring Boot项目,都会有一个类似如下的启动类:
@SpringBootApplicationpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args); }}
为什么执行main方法SpringBoot项目就启动了呢?启动过程中,又是在什么时机触发SpringBoot自动配置呢?在此,我们揭开SpringBoot最后的一层神秘面纱:
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
从以上代码可以发现,SpringBoot整个启动流程分为两个步骤:
(1) 初始化一个SpringApplication对象;
(2) 执行该对象的run方法;
4.1 SpringApplication初始化
@SuppressWarnings({ "unchecked", "rawtypes" })public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //判断应用类型,常用的一般都是servlet this.webApplicationType = WebApplicationType.deduceFromClasspath(); //初始化classpath下METF-INF/spring.factories已配置好的ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //初始化classpath下已配置的ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();}
初始化
SpringApplication对象主要做了3件事:
(1) 初始化primarySources,指定SpringBoot应用启动时加载业务相关Bean的上下文路径(通常对应声明@SpringBootApplication注解的类,即提供main方法的类)。所以SpringBoot应用通常要求声明@SpringBootApplication注解的Application类放在顶层目录(默认包含所有声明@Bean注解的子包或子类)。除此之外,SpringBoot通过扫描primarySources上传递依赖的一系列注解(@SpringBootApplication、@EnableAutoConfiguration等),才能启动后续的自动配置;
(2) 推断SpringBoot应用的应用类型(Reactive Web Application、Servlet-Based Web Application还是普通的Spring应用)
(3) 初始化ApplicationContextInitializer和ApplicationListener
4.2 run()
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); }}
run()方法执行过程中,主要包含以下几个步骤:
(1) 初始化应用参数(DefaultApplicationArguments)
(2) 准备上下文环境(ConfigurableEnvironment)
(3) 打印启动Banner(通常是Log或控制台输出Spring字符图形)
(4) 创建应用上下文(ConfigurableApplicationContext)
(5) 准备上下文(prepareContext)
(6) 刷新上下文(refreshContext)
(7) 调用上下文中注册的Runner(ApplicationRunner和CommandLineRunner)
穿插其中的是利用SpringApplicationRunListeners来完成执行过程中的状态监听。上述步骤中最核心是准备上下文(prepareContext)和刷新上下文(refreshContext),我们进一步剖析其中的源码。
(1) 准备上下文(prepareContext)
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context);}
上述方法中关键代码步骤为
getAllSources和
load(context, sources.toArray(new Object[0]))。进一步深入
getAllSources方法源码:
public Set getAllSources() { Set 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);}
这里我们看到之前讲到的primarySources(指定SpringBoot应用启动时加载业务相关Bean的上下文路径),还有一个sources(在SpringApplication启动之前,通过setSources方法指定,用以添加额外的上下文Class、Package或XML文件路径)。再一步深入load方法源码:
protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load();}
这里我们看到了熟悉的BeanDefinitionLoader初始化,然后启动load(扫描指定的上下文路径,注册BeanDefinitionHolder到之前创建好的ConfigurableApplicationContext)。
(2) 刷新上下文(refreshContext)
refreshContext方法内部会委托调用之前创建好的
ConfigurableApplicationContext的refresh方法,至此,启动了Spring Core的核心模板方法。其源码如下:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } }}
这个模板方法中每一步的具体作用,可以在网上找到很多分析的文章,我们在此不做赘述。此处,我们重点回答跟本文主题相关的一个问题:SpringBoot应用启动过程中是在何时触发SpringBoot自动配置呢?
要回答上面这个问题,关键逻辑在
invokeBeanFactoryPostProcessors方法源码:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }}
可以看到关键逻辑是委托给PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法,其中关键部分的源码如下:
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) { // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set processedBeans = new HashSet<>(); if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List regularPostProcessors = new ArrayList<>(); List registryProcessors = new ArrayList<>(); for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; registryProcessor.postProcessBeanDefinitionRegistry(registry); registryProcessors.add(registryProcessor); } else { regularPostProcessors.add(postProcessor); } } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // Separate between BeanDefinitionRegistryPostProcessors that implement // PriorityOrdered, Ordered, and the rest. List currentRegistryProcessors = new ArrayList<>(); // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate = true; while (reiterate) { reiterate = false; postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (!processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate = true; } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); } // Now, invoke the postProcessBeanFactory callback of all processors handled so far. invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); } else { // Invoke factory processors registered with the context instance. invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); } ...}
其中针对BeanDefinitionRegistry类型的BeanFactory做了特殊处理(因为要触发Configuration中的Bean加载)。对应处理SpringBoot自动配置的BeanFactory为ConfigurationClassPostProcessor(实现了BeanDefinitionRegistryPostProcessor接口)。上述方法实现中的关键逻辑方法是invokeBeanDefinitionRegistryPostProcessors,其源码如下:
private static void invokeBeanDefinitionRegistryPostProcessors( Collection extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}
循环调用对应的BeanDefinitionRegistryPostProcessor,并委托其进行BeanDefinition注册。
进一步深入SpringBoot自动配置相关的
ConfigurationClassPostProcessor类的
postProcessBeanDefinitionRegistry方法源码:
/** * Derive further bean definitions from the configuration classes in the registry. */@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry);}
这里也可以从方法的JavaDoc中看到,其主要功能是驱动@Configuration注解的配置类中的Bean加载(@SpringBootConfiguration注解传递依赖了@Configuration注解,上文提到的各类XXXAutoConfiguration也标注了@Configuration注解)。
继续深入到
processConfigBeanDefinitions方法,截取其中核心源码片段:
/** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ... // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set candidates = new LinkedHashSet<>(configCandidates); Set alreadyParsed = new HashSet<>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); ...}
首先是创建了通用的XXXConfiguration类解析器(ConfigurationClassParser)。然后循环解析所有标注了@Configuration的配置类(标注了@SpringBootConfiguration的启动类自然也被包含在内)。这里需要特别注意的一点是:如果在解析某个配置类的过程中,引入了新的配置类,则会递归解析新引入的配置类,直到所有的配置类都被解析完。其次在解析完成之后,利用ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法扫描XXXConfiguration类中的@Bean注解并完成BeanDefinition到Spring容器的注册。
再进一步深入
ConfigurationClassParser类的
parse方法,发现其解析逻辑委托给了
processConfigurationClass方法,源码如下:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass);}
核心逻辑在doProcessConfigurationClass方法,继续深入:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations ... // Process any @ComponentScan annotations ... // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations ... // Process individual @Bean methods Set beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null;}
我们把目光聚焦到下面这部分源码:
// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), true);
从这里开始,跟自动配置(@EnableAutoConfiguration)开始产生联系:
1) getImports方法获取@EnableAutoConfiguration依赖的@Import(AutoConfigurationImportSelector.class);
2) processImports方法根据Import的Class(例如:AutoConfigurationImportSelector)执行对应的导入逻辑;
进一步深入
processImports方法核心源码片段:
for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle( configClass, (DeferredImportSelector) selector); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); }}
由于AutoConfigurationImportSelector实现了DeferredImportSelector接口,我们进一步把目光聚焦到:
if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle( configClass, (DeferredImportSelector) selector);}
继续深入到
deferredImportSelectorHandler的
handle方法源码:
@Nullableprivate List deferredImportSelectors = new ArrayList<>();/** * Handle the specified {@link DeferredImportSelector}. If deferred import * selectors are being collected, this registers this instance to the list. If * they are being processed, the {@link DeferredImportSelector} is also processed * immediately according to its {@link DeferredImportSelector.Group}. * @param configClass the source configuration class * @param importSelector the selector to handle */public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) { DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder( configClass, importSelector); if (this.deferredImportSelectors == null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); handler.register(holder); handler.processGroupImports(); } else { this.deferredImportSelectors.add(holder); }}
这里需要注意:首次处理的时候,deferredImportSelectors默认是个空的List,不做任何import处理逻辑,仅把AutoConfigurationImportSelector添加进了deferredImportSelectors。那么什么时候执行真正的import逻辑呢?再回到之前ConfigurationClassParser类的parse方法源码:
public void parse(Set configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } this.deferredImportSelectorHandler.process();}
在方法的最后触发了deferredImportSelectorHandler.process():
public void process() { List deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; try { if (deferredImports != null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); deferredImports.sort(DEFERRED_IMPORT_COMPARATOR); deferredImports.forEach(handler::register); handler.processGroupImports(); } } finally { this.deferredImportSelectors = new ArrayList<>(); }}
进一步深入DeferredImportSelectorGroupingHandler的processGroupImports方法源码:
public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); }}
这里循环遍历每一个DeferredImportSelectorGrouping,然后获取其所有导入的XXXConfiguration类,并循环调用之前提到的processImports方法来递归地处理所有XXXConfiguration类。
我们重点关注grouping.getImports方法:
/** * Return the imports defined by the group. * @return each import with its associated configuration class */public Iterable getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports();}
如果我告诉你这段代码里面的group就是AutoConfigurationImportSelector.AutoConfigurationGroup,是不是就跟上面第3部分提到的@EnableAutoConfiguation利用AutoConfigurationImportSelector加载各种XXXAutoConfiguration类对上了暗号,就撸通了整个SpringBoot自动配置加载XXXAutoConfiguration类并根据@Bean注解完成Spring Bean注册的核心逻辑。
5 大串联
在了解了SpringBoot自动配置和初始化启动的源码之后,我们总体回顾一下,串联起整个SpringBoot启动的全过程:
(1) 初始化SpringApplication对象
1.1) 初始化primarySources,指定SpringBoot应用启动时加载业务相关Bean的上下文路径(通常对应声明@SpringBootApplication注解的类,即提供main方法的类)。除此之外,SpringBoot通过扫描primarySources上传递依赖的一系列注解(@SpringBootApplication、@EnableAutoConfiguration等),才能启动后续的自动配置;
1.2) 推断SpringBoot应用的应用类型(Reactive Web Application、Servlet-Based Web Application还是普通的Spring应用);
1.3) 初始化ApplicationContextInitializer和ApplicationListener;
(2) 执行run方法
2.1) 初始化应用参数(DefaultApplicationArguments);
2.2) 准备上下文环境(ConfigurableEnvironment);
2.3) 打印启动Banner(通常是Log或控制台输出Spring字符图形);
2.4) 创建应用上下文(ConfigurableApplicationContext);
2.5) 准备上下文(prepareContext),首先是BeanDefinitionLoader初始化,然后启动load(扫描指定的上下文路径,注册BeanDefinitionHolder到之前创建好的ConfigurableApplicationContext);
2.6) 刷新上下文(refreshContext),委托调用之前创建好的ConfigurableApplicationContext的refresh方法(启动了SpringCore的核心模板方法),完成BeanDefinition的扫描、初始化、依赖注入等。其中跟SpringBoot自动配置相关的主要是refresh方法中的invokeBeanFactoryPostProcessors步骤:
2.6.1) 委托ConfigurationClassPostProcessor的processConfigBeanDefinitions处理XXXConfiguration类的加载及注册;
2.6.2)
ConfigurationClassPostProcessor
委托ConfigurationClassParser类处理XXXConfiguration类的加载(
ConfigurationClassParser基于
@EnableAutoConfiguration注解的传递依赖
@Import(AutoConfigurationImportSelector.class),委托
AutoConfigurationImportSelector来实现所有
XXXAutoConfiguration类的加载);2.6.3) 递归加载所有的
XXXConfiguration类之后,
ConfigurationClassPostProcessor
创建ConfigurationClassBeanDefinitionReader对象,并委托其loadBeanDefinitions(扫描
XXXConfiguration类的
@Bean注解并注册
BeanDefinition到Spring容器);
2.7) 调用上下文中注册的Runner( ApplicationRunner和
CommandLineRunner);
以上就是SpringBoot自动配置的原理,希望对大家工作有所帮助!