本文共 29239 字,大约阅读时间需要 97 分钟。
springboot run 的总体流程如下:
(1)加载各种配置文件以及环境参数
主要根据main方法和自动配置文件META-INF/spring.factories 中的相关配置类进行自动化化加载初始化等操作。
注册监事件听器。
初始化容器上下文Context。
解析@ComponentScan扫描Bean定义生成BeanDefinition、BeanDefinitionRegistry 把BeanDefinition注册到BeanFactory。
目录:
辅助阅读:
应用:
启动入口
本文是springboot启动流程的第一篇,涉及的内容是SpringApplication这个对象的实例化过程。为什么从SpringApplication这个对象说起呢?我们先看一段很熟悉的代码片段
@SpringBootApplicationpublic class SpringBootLearnApplication { public static void main(String[] args) { SpringApplication.run(SpringBootLearnApplication.class, args); }}
springboot项目从一个main方法开始,main方法将会调用SpringApplication的run方法开始springboot的启动流程。所以,本文即从构造SpringApplication对象开始。
我们跟进SpringApplication的run方法
public static ConfigurableApplicationContext run(Class primarySource, String... args) { return run(new Class [] { primarySource }, args);}
这是一个静态方法,入参有两个:
1)main方法所在的类,该类后续将被作为主要的资源来使用,比如通过该类获取到basePackage;
2)main方法的命令行参数,命令行参数可以通过main传入,也就意味着可以在springboot启动的时候设置对应的参数,比如当前是dev环境、还是production环境等。
第2行代码,run方法将调用另外一个内部run方法,并返回一个ConfigurableApplicationContext,预示着spring容器将在后续过程中创建。
跟进另一个run方法
public static ConfigurableApplicationContext run(Class [] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
run方法中先是构造了一个SpringApplication实例对象,而后调用了SpringApplication的成员方法run,这个run方法将包含springboot启动流程的核心逻辑。本文只讨论SpringApplication的实例化过程。
构造函数
跟进SpringApplication的构造函数中
public SpringApplication(Class ... primarySources) { this(null, primarySources);}
构造函数调用了另外一个构造函数,继续跟进
public SpringApplication(ResourceLoader resourceLoader, Class ... primarySources) { // 设置资源加载器 this.resourceLoader = resourceLoader; // 设置主要资源类 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 推断当前应用的类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 设置ApplicationContext的初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 设置Application监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推断并设置主类 this.mainApplicationClass = deduceMainApplicationClass();}
构造过程主要包含:
1)推断当前应用类型
2)设置ApplicationContext初始化器、Application监听器
3)根据堆栈来推断当前main方法所在的主类
推断当前应用类型
WebApplicationType是一个枚举对象,枚举了可能的应用类型
public enum WebApplicationType { /** * 非web应用类型,不启动web容器 */ NONE, /** * 基于Servlet的web应用,将启动Servlet的嵌入式web容器 */ SERVLET, /** * 基于reactive的web应用,将启动reactive的嵌入式web容器 */ REACTIVE; // 省略...}
deduceFromClasspath方法将会推断出当前应用属于以上三个枚举实例的哪一个,跟进方法
static WebApplicationType deduceFromClasspath() { // 类路径中是否包含DispatcherHandler,且不包含DispatcherServlet,也不包含ServletContainer,那么是reactive应用 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } // 如果Servlet或者ConfigurableWebApplicationContext不存在,那么就是非web应用 for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } // 否则都是Servlet的web应用 return WebApplicationType.SERVLET;}
推断过程将根据类路径中是否有指示性的类来判断
设置ApplicationContext初始化器、Application监听器
getSpringFactoriesInstances(ApplicationContextInitializer.class)
这个方法调用将会从META-INF/spring.factories配置文件中找到所有ApplicationContextInitializer接口对应的实现类配置,然后通过反射机制构造出对应的实例对象。
getSpringFactoriesInstances(ApplicationListener.class)
这个方法也是一样的做法,将会获取ApplicationListener接口的所有配置实例对象
有关于如何从spring.factories配置文件中获取配置并构造出实例对象请看:
根据堆栈来推断当前main方法所在的主类
构造SpringApplication还有最后一步,推断出main方法所在的主类。我们跟进deduceMainApplicationClass方法
private Class deduceMainApplicationClass() { try { // 获取堆栈链路 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); // 遍历每一个栈帧信息 for (StackTraceElement stackTraceElement : stackTrace) { // 如果该栈帧对应的方法名等于main if ("main".equals(stackTraceElement.getMethodName())) { // 获取该类的class对象 return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null;}
该方法采用遍历栈帧的方式来判断最终main方法落在哪个栈帧上,并通过forName来获取该类
总结
到这里本文就结束了,核心点就在于SpringApplication的实例化,可以看出最主要的就是做了应用类型的推断,后面的Application创建、Environment创建也会基于该类型。
SpringApplication.run方法逻辑
在中,我们看到SpringApplication的静态方法最终是去构造了一个SpringApplication实例对象,并调用了SpringApplication的成员方法run
public static ConfigurableApplicationContext run(Class [] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
本文将梳理一下run方法的代码主要的逻辑,为后面其它内容做一个铺垫
跟进run方法,这个方法的代码有点长我们将抛弃掉一些比较次要的内容
public ConfigurableApplicationContext run(String... args) { // 声明一个Context容器 ConfigurableApplicationContext context = null; // 获取监听器 SpringApplicationRunListeners listeners = getRunListeners(args); // 调用监听器的启动 listeners.starting(); try { // 创建并配置Environment(这个过程会加载application配置文件) ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 根据应用类型创建对应的Context容器 context = createApplicationContext(); // 刷新Context容器之前的准备 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新Context容器 refreshContext(context); // 刷新Context容器之后处理 afterRefresh(context, applicationArguments); // Context容器refresh完毕发布 listeners.started(context); // 触发Context容器refresh完以后的执行 callRunners(context, applicationArguments); } catch (Throwable ex) {} try { // Context启动完毕,Runner运行完毕发布 listeners.running(context); } catch (Throwable ex) {} return context;}
简化后的代码看起来就比较清晰了,我们再整理一下逻辑
1)首先会从spring.factories配置文件中获取SpringApplicationRunListener监听器并启动监听器;
2)而后就会去创建Environment
3)紧接着创建ApplicationContext
4)ApplicationContext的refresh的事前准备
5)ApplicationContext的refresh
6)ApplicationContext的refresh之后
7)发布ApplicationContext的refresh完毕的事件
8)触发runner
9)最后发布refresh完毕、runner执行完毕的事件
run方法描述了SpringApplication这个类的职责,包含了不少步骤,但简单的看其实就是为了创建并配置好一个ApplicationContext。
总结
我们忽略各种细节以后就会发现,SpringApplication的run方法主要就是为了构建出一个ApplicationContext,后续文章也将围绕着构建ApplicationContext相关的内容展开。
Environment
我们简单了解了一下SpringApplication的run方法的代码逻辑。其中的prepareEnvironment方法正如它的方法名表示的意思一样,为当前应用准备一个Environment对象,也就是运行环境。在阅读prepareEnvironment代码之前,我们先了解一下Environment。
组成
首先,Environment是Spring3.1才提供的一个接口。它是对当前运行的应用程序的环境的抽象,下面我们了解一下它的组成。
Environment由两部分组成
1)profiles
profile中文直译是"概述"、"简介"、"轮廓"的意思,但在使用spring开发应用程序的时候,我们对profile的认识更亲切的是用在划分多环境的时候。
通常,我们会将profile划分成如:开发、测试、预生产、生产环境。每个环境会有有些bean不同、配置不同等。每个profile将有相应的bean和配置与之匹配,那么当我们切换profile的时候自然也就切换了相应的bean和配置文件,从而达到在不同环境中快速切换避免不断修改的问题。
这也就是spring的java doc里面描述的"logical group"的意思。
2)properties
properties的概念想必我们已经非常熟悉了,在java中properties代表着key-value的键值对象集合。Environment内部设计了key-value结构的对象来存储相应的键值。
综上所述,Environment中包含着用于切换环境的profile,还包含着存储键值对的properties。
核心uml类图
上面的内容中,我们了解了Environment的组成部分包括profile和properties。spring在对Environment进行设计的时候也把这两个部分进行了隔离。
如上图所示,PropertyResolver包含了properties相关的操作,如:getProperty(String key),Environment继承于PropertyResolver同时也就将properties的相关能力给组合了进来。
Environment的则包含了profile的相关操作,如:getActiveProfiles()。
如果查看PropertyResolver和Environment接口的方法,我们就会发现这两个接口都只是包含了如getter方法的获取操作,并没有setter样子的操作。这或许也意味着spring希望在程序的开发运行过程中,Environment尽量是维持稳定的,而不是不断地被修改、变化。
那么在程序启动过程中势必要对Environment进行配置,因此我们会看到多个继承自Environment和PropertyResolver接口地子接口,如:ConfigurableEnvironment和ConfigurablePropertyResolver。
再往下看,AbstractEnvironment显然包含了Environment设计地大部分实现,而从StandardEnvironment再往下走了两个分支,也就是针对reactive和Servlet的Environment实现。
到这里,我们基本了解了Environment主要的相关接口设计,设计路线也比较简单。
profile和properties的数据结构
前面的两个部分,我们了解了Environment包含profile和properties。也知道了Environment相关接口也主要是根据profile和properties来设计的。但是我们并不知道具体的实现里面profile和properties的数据结构是怎么样的。
从uml类图中,我们清晰地看到Environment的具体实现是在AbstractEnvironment这个抽象类中。我们可以直接打开这个类
profile数据结构
AbstractEnvironment类中包含着profile的成员变量
private final SetactiveProfiles = new LinkedHashSet<>();private final Set defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
profile的存储结构看起来相对简单,就是两个set集合,每个profile就是单纯的一个String类型的字符串表示而已。
activeProfiles表示的是当前应用中"激活"的profile集合,比如我当profile=test的时候表示当前环境是测试环境。
而defaultProfiles则表示的是默认的profile集合,也就是说如果没有任何指定的profile,那么就会采用默认的。
properties数据结构
我们再看看AbstractEnvironment中properties的数据结构
private final MutablePropertySources propertySources = new MutablePropertySources();
前面我们一直提到,properties是一种key-value的键值对存储的集合。那么也就是说MutablePropertySources这个类实现了这个概念。
我们先看看MutablePropertySources的继承结构是怎么样的
看起来很简单的设计路线,Iterable接口表明MutablePropertySources像集合一样是可以迭代的,我们可以大胆猜测其内部就是组成了一个集合。Iterable往下,就是PropertySources,这个接口表示的是PropertySource类的集合,也就是说被迭代的元素就是PropertySource。MutablePropertySources则直接继承于PropertySources。
那么,我们基本可以想得到PropertySource这个类就是properties概念得设计,是我们主要得关注对象。
现在让我们打开MutablePropertySources看看PropertySource的具体结构
public class MutablePropertySources implements PropertySources { private final List> propertySourceList = new CopyOnWriteArrayList<>(); // 省略}
跟我们想象得差不多,就是一个PropertySource类的集合作为成员组合在MutablePropertySources中。
我们继续跟进PropertySource这个类,更多得了解一下
public abstract class PropertySource{ protected final String name; protected final T source; // 省略}
看起来就是一个key-value的数据结构是吗?这里请注意!跟我们想象的稍微有点不同,举例说明
我们创建了一个config.properties文件,内容如
username=testpassword=a123456
那么当config.properties这个文件被加载到内存中,并作为一个PropertySource存在的时候,name=config而非name=username或者password。也就是说,加载config.properties这样的资源,泛型T将会是一个Map集合,而Map集合包含着config.properties文件中所有的键值对。
另外,我们注意到PropertySource是一个抽象类。spring将会针对资源的不同来源而使用不同的实现,例如上例中的config.properties加载到内存作为Properties对象添加的,就是PropertySource的其中一个实现类PropertiesPropertySource。
还有诸如
1)来自命令行的配置:CommandLinePropertySource
2) 来自Servlet的配置:ServletConfigPropertySource、ServletContextPropertySource
等
下面是一张PropertySource的层级图
prepareEnvironment创建Environment
上部分的内容包括了不少介绍的内容,下面我们简单看看SpringApplication的run方法中包含的prepareEnvironment方法,跟进方法
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments ) { // 创建一个Environment对象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置Environment对象 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 触发监听器(主要是触发ConfigFileApplicationListener,这个监听器将会加载如application.properties/yml这样的配置文件) listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment;}
该方法核心内容包括三部分
1)创建一个Environment对象
2)配置Environment对象
3)触发ConfigFileApplicationListener监听器(加载application.properties/yml将再后续文章中说明)
getOrCreateEnvironment
我们跟进getOrCreateEnvironment方法看看创建过程
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); }}
在文章中,我们提到SpringApplication在deduceFromClassPath方法中会推断出WebApplicationType具体的枚举实例,代表了当前应用的类型。
getOrCreateEnvironment方法中根据WebApplicationType类型选择具体的Environment类型,也就是我们提到过的Servlet类型、Reative类型或者非Web应用类型。
configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // 添加初始的properties(注意:当前并未加载如application.properties/yml的properties) configurePropertySources(environment, args); // 添加初始的profile(注意:当前并未加载如application.properties/yml配置profile) configureProfiles(environment, args);}
总结
本文,我们大体地讲解了Environment的接口设计、profile和properties的数据结构设计。再从prepareEnvironment方法中看到了Environment是根据webApplicationType匹配后创建的。到这里,Environment相关的内容简单介绍就结束了,我们也初步地为spring地Context创建了一个Environment的对象。
refreshContext刷新(上)
run方法中,prepareContext和afterRefresh之间的refreshContext方法正是ioc容器刷新的入口方法。
ApplicationContext和BeanFactory
但是在阅读refreshContext方法之前,我们得先区分一下ApplicationContext和BeanFactory两者之间的关系。在之前的文章中,我们并没有把二者进行区分。比如,我们总是说"把Bean注册到ApplicationContext容器"。
在我们的理解中,容器应该是一个空间的概念,用于存放事物的东西。在spring中,存放的是Bean。而BeanFactory提供了这么一个空间用于存放Bean,所以BeanFactory才是Bean所在的主要容器,而不是我们一直说的ApplicationContext。
既然ApplicationContext不是容器,那它又是啥呢?我们称之为"上下文"。"上下文"的概念我们也许不见得那么熟,但是"场景","场所"这样的概念我们应该就比较熟悉了。比如说"拍摄场景","交易场所"等。它们的共同点都是事件发生的地方。所以ApplicationContext正是spring定义的应用程序的事件发生场所,也就是所谓的应用上下文。
上面,我了解了BeanFactory作为Bean容器,而ApplicationContext作为上下文。那么Bean容器和上下文之间是什么关系呢?我们可以看一个代码片段
// 通用的应用上下文实现public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { // 默认BeanFactory的实现 private final DefaultListableBeanFactory beanFactory; // 省略}
我们看到BeanFactory是被组合在ApplicationContext当中的,所以它们的其中一种关系就是组合关系。也就是说应用上下文中包含着Bean工厂。
接着,我们分别看看ApplicationContext的类图
我们看到ApplicationContext和BeanFactory还存在着继承关系,这意味着ApplicationContext可以对外被当做BeanFactory来使用,这也是为什么我们总是把ApplicationContext当做容器来看的主要原因,因为对外来看两者是一体的。
结合上面的组合关系,我们可以知道对内的话ApplicationContext的BeanFactory相关实现会由内部组合的BeanFactory的实现类来完成具体工作。
到这里,我们基本就明白了ApplicationContext和BeanFactory之间的关系有两种:组合、继承。
后面我们将称呼ApplicationContext为上下文,而BeanFactory为Bean容器,进行区分。
上下文组合Bean工厂
那么BeanFactory是什么时候被组合到ApplicationContext当中的呢?我们先看看ApplicationContext的实现类的类图
注意!springboot默认的servlet项目的ApplicationContext实现类是AnnotationCofnigServletWebServerApplicationContext,所以我们会以它作为实现类来看
我们自下而上,顺着继承链找到GenericApplicationContext我们就会看到之前出现过的代码片段
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory; public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); } // 省略}
这里,在GenericApplicationContext的构造方法当中构建了一个DefaultListableBeanFactory的实例对象。DefaultListableBeanFactory也就是BeanFactory的默认实现,那么也就是说在构造ApplicationContext实例对象的时候创建并组合了一个BeanFactory的实例。
我们顺便也看看DefaultListableBeanFactory的继承关系吧
这个类图只保留了BeanFactory的东西,设计路线自BeanFactory到DefaultListableBeanFactory也很清晰。
refreshContext刷新过程
下面,我们将正式进行refreshContext方法的阅读。打开refreshContext方法
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } }}
跟进refresh方法
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh();}
我们看到,这里调用的是AbstractApplicationContext的refresh方法,顺序AnnotationConfigServletWebServerApplicationContext的继承链向上可以找到AbstractApplicationContext。
我们继续跟进AbstractApplicationContext的refresh方法,refresh方法有点长
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 刷新前准备,设置flag、时间,初始化properties等 prepareRefresh(); // 获取ApplicationContext中组合的BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 设置类加载器,添加后置处理器等准备 prepareBeanFactory(beanFactory); try { // 供子类实现的后置处理 postProcessBeanFactory(beanFactory); // 调用Bean工厂的后置处理器 invokeBeanFactoryPostProcessors(beanFactory); // 注册Bean的后置处理器 registerBeanPostProcessors(beanFactory); // 初始化消息源 initMessageSource(); // 初始化事件广播 initApplicationEventMulticaster(); // 供之类实现的,初始化特殊的Bean onRefresh(); // 注册监听器 registerListeners(); // 实例化所有的(non-lazy-init)单例Bean finishBeanFactoryInitialization(beanFactory); // 发布刷新完毕事件 finishRefresh(); } catch (BeansException ex) { // } finally { // } }}
在中,我们提到了这么一个初始化过程:
annotation或者xml中Bean的配置 --> 内存中的BeanDefinition --> Bean
也就是实际的配置,转化成内存中的配置对象,再根据配置对象转化成具体的实例对象。说白了就是从元数据到实例的一个转化过程。
为什么会提及这么一个转化过程呢?因为我们的refresh过程主要包含的就是其中的一步,也就是从annotation或者xml的Bean配置 --> 内存中的BeanDefinition的过程。这个过程的实现在调用Bean工厂的后置处理器的时候完成,也就是invokeBeanFactoryPostProcessors方法
我们跟进invokeBeanFactoryPostProcessors方法
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // }
这里先获取了所有后置处理器,然后调用处理。再跟进PostProcessorRegistrationDelegate的invokeBeanFactoryFactoryPostProcessors方法
该方法很长,我们删减掉大部分内容
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, ListbeanFactoryPostProcessors ) { // if (beanFactory instanceof BeanDefinitionRegistry) { // while (reiterate) { // 调用BeanDefinition注册的后置处理器 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); // } // } else { // } //}
可以看到,调用后置处理器的时候会调用到注册BeanDefinition的后置处理器。也就是从这里开始作为BeanDefinition的注册入口
跟进invokeBeanDefinitionRegistryPostProcessors
private static void invokeBeanDefinitionRegistryPostProcessors( Collection postProcessors, BeanDefinitionRegistry registry ) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}
这里我们需要断点一下,看看有哪些后置处理器处理BeanDefinition注册
我们看到了ConfigurationClassPostProcessor也就是它完成BeanDefinition注册这项工作的
我们跟进ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { // 省略 processConfigBeanDefinitions(registry);}
继续跟进,我们看到processConfigBeanDefinitions方法挺长的,进行了大量的缩减
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ListconfigCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { // } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 默认仅有主类被添加 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // // 解析被 @Configuration 注解的类 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set candidates = new LinkedHashSet<>(configCandidates); // do { // 解析的核心方法 parser.parse(candidates); parser.validate(); // candidates.clear(); // } while (!candidates.isEmpty()); // }
中,也就是在prepareContext方法的核心逻辑里,main方法所在的主类将会被作为BeanDefinition加载到BeanFactory当中。而在这里,该主类将被作为一个配置类被解析,解析器即ConfigurationClassParser。
我们跟进parse方法看看
public void parse(SetconfigCandidates) { 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) {} catch (Throwable ex) {} } this.deferredImportSelectorHandler.process();}
继续跟进parse方法
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName));}
可以看到主类作为配置类的解析过程将从processConfigurationClass这里开始
总结
到这里,ioc容器的refresh过程先做一个小结。我们知道了上下文和Bean容器是继承关系又是组合关系。refreshContext的核心就是为了加载BeanDefinition,而加载BeanDefinition将从main方法所在的主类开始,主类作为一个配置类将由ConfigurationClassParser解析器来完成解析的职责。下一篇文章,我们将会看到从主类中解析出BeanDefinition的主要逻辑。
refreashContext(下)
,我们知道了解析过程将从解析main方法所在的主类开始。在文章的最后我们稍微看了一下ConfigurationClassParser这个解析器的parse方法
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName));}
本文将从这个parse方法继续下去,看看解析main方法所在的主类这个过程主要发生了什么。
跟进processConfigurationClass方法
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // // 由main方法所在的主类开始,向超类逐层向上递归解析 SourceClass sourceClass = asSourceClass(configClass); do { // 这里包含了解析单个配置类的核心逻辑 sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); // }
我们注意到,doProcessConfigurationClass方法将会完成解析的主要工作,但是又会返回一个新的sourceClass用于解析。而这个新的sourceClass会是当前上一个sourceClass的父类。所在解析过程是一个递归过程,由主类开始,向超类逐层向上递归解析处理。
继续跟进doProcessConfigurationClass方法,我们看看这个核心的解析逻辑。代码量对较多,我们只关注两个点
1)@ComponentScan注解解析,扫描并注册BeanDefinition
2)获取超类向上递归
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // // 处理@ComponentScan注解 SetcomponentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { // 遍历@ComponentScan的属性值 for (AnnotationAttributes componentScan : componentScans) { // 解析扫描 Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // } } // // 判断是否有超类 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // 返回待解析的超类 return sourceClass.getSuperClass(); } } // 没有超类,则解析完毕 return null;}
首先我们main方法所在的主类是被@SpringbootApplication注解所标注的,而@SpringbootApplication组合了@ComponentScan。所谓解析主类的时候将会处理@ComponentScan注解。解析@ComponentScan的主要工作的实现由ComponentScanAnnotationParser这个解析器来完成。通常这个解析器完成之后,被扫描到的BeanDefinition将会被注册到BeanFactory当中。
doProcessConfigurationClass方法的最后一部分是从当前被解析的类元数据中获取超类,如果超类存在且需要被解析那么就当做返回值返回回去,从而被外层的方法给递归处理。
@ComponentScan注解解析
下面,我们跟进ComponentScanAnnotationParser这个解析器的parse方法,看看@ComponentScan的处理过程
public Setparse( AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); // Set basePackages = new LinkedHashSet<>(); // 从basePackages配置获取扫描路径 String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } // 从basePackageClasses获取扫描路径 for (Class clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { // 默认添加当前被解析类的路径作为根路径 basePackages.add(ClassUtils.getPackageName(declaringClass)); } // // 扫描目标路径 return scanner.doScan(StringUtils.toStringArray(basePackages));}
这里获取了一个扫描器,然后找到了待扫描的路径,最后利用扫描器去扫描路径。
跟进doScan方法
protected SetdoScan(String... basePackages) { Set beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 扫描获取BeanDefinition Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { // if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册BeanDefinition到BeanFactory registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions;}
我们看到,findCandidateComponents方法将会根据扫描路径获取BeanDefinition,而扫描出来的BeanDefinition将会进入注册方法registerBeanDefinition。
我们先跟进findCandidateComponents方法看看如何扫描获取
public SetfindCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // 进入 return scanCandidateComponents(basePackage); }}
再跟进scanCandidateComponents方法
private SetscanCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet<>(); try { // 拼接出搜索路径,例如:classpath*:cn/lay/springbootlearn/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 获取搜索路径下待处理资源 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // for (Resource resource : resources) { // if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { // 转化成BeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); // } else { // } } catch (Throwable ex) { // } } else { // } } } catch (IOException ex) { // } return candidates;}
basePackage将会被拼接成搜索路径,如:classpath*:cn/lay/springbootlearn/**/*.class。而getResources方法将会从搜索路径中获取相应的资源对象,这些资源对象并最终被读取并转化为BeanDefinition。
到这里,@ComponentScan扫描的Bean就已经成为了BeanDefinition,但是还有一步就是将BeanDefinition注册到BeanFactory当中。
我们回到doScan方法,并跟进registerBeanDefinition方法,看看注册过程
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}
继续跟进registerBeanDefinition
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 省略}
我们看到这里直接注册到了BeanDefinitionRegistry中去了,其实就是注册到BeanFactory当中。BeanFactory的默认实现类DefaultListableBeanFactory实现了BeanDefinitionRegistry,所以DefaultListableBeanFactory即是BeanDefinition的注册位置。
跟进DefaultListableBeanFactory的registerBeanDefinition方法
private final MapbeanDefinitionMap = new ConcurrentHashMap<>(256);public void registerBeanDefinition( String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { // if (existingDefinition != null) { // } else { if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); // } } else { // } // } // }
最终,也就是将BeanDefinition添加到一个key-value的集合当中,这样就完成了注册工作。
总结
到这里,ioc容器refresh过程部分就结束了。我们略过不少东西,将解析主类、解析@ComponentScan扫描Bean定义、注册到BeanFactory这个主要的流程过了一遍。当然,在这里可能还存在一个比较困惑的点。前面的文章中,我们提过几次:配置 -> BeanDefinition -> Bean这样一个过程。ioc的refresh过程却只有从配置 -> BeanDefinition这样一个过程,那么BeanDefinition -> Bean这个过程又在哪里呢?后面ioc容器注入部分将说明这部分内容。
转载地址:https://liuhuiyao.blog.csdn.net/article/details/106962769 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!