SpringBoot启动流程分析(四):IoC容器的初始化过程
2020-12-13 03:34
标签:crete fresh cal rem eof ram throw message one SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: 笔者注释版Spring Framework与SpringBoot源码git传送门:请不要吝啬小星星 第五步:刷新应用上下文 在前面的博客中谈到IoC容器的初始化过程,主要分下面三步: 在上一篇文章介绍了prepareContext()方法,在准备刷新阶段做了什么工作。本文我们主要从refresh()方法中总结IoC容器的初始化过程。 从以上代码中我们可以看到,refresh()方法中所作的工作也挺多,我们没办法面面俱到,主要根据IoC容器的初始化步骤和IoC依赖注入的过程进行分析,围绕以上两个过程,我们主要介绍重要的方法,其他的请看注释。 在启动流程的第三步:初始化应用上下文。中我们创建了应用的上下文,并触发了GenericApplicationContext类的构造方法如下所示,创建了beanFactory,也就是创建了DefaultListableBeanFactory类。 关于obtainFreshBeanFactory()方法,其实就是拿到我们之前创建的beanFactory。 从上面代码可知,在该方法中主要做了三个工作,刷新beanFactory,获取beanFactory,返回beanFactory。 首先看一下refreshBeanFactory()方法,跟下去来到GenericApplicationContext类的refreshBeanFactory()发现也没做什么。 从字面意思上可以看出准备BeanFactory。 看代码,具体看看做了哪些准备工作。这个方法不是重点,看注释吧。 postProcessBeanFactory()方法向上下文中添加了一系列的Bean的后置处理器。后置处理器工作的时机是在所有的beanDenifition加载完成之后,bean实例化之前执行。简单来说Bean的后置处理器可以修改BeanDefinition的属性信息。 关于这个方法就先这样吧,有兴趣的可以直接百度该方法。篇幅有限,对该方法不做过多介绍。 上面说过,IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化过程的三个步骤。 1,第一步:Resource定位 在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的定位加载。 常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了) 2,第二步:BeanDefinition的载入 在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:classpath*:org/springframework/boot/demo/**/*.class这样的形式,然后一个叫做PathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过程就是这样的了。 3、第三个过程:注册BeanDefinition 这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。 OK,总结完了,接下来我们通过代码看看具体是怎么实现的。 一路跟踪调用栈,来到ConfigurationClassParser类的parse()方法。 看上面的注释,在前面的prepareContext()方法中,我们详细介绍了我们的主类是如何一步步的封装成AnnotatedGenericBeanDefinition,并注册进IoC容器的beanDefinitionMap中的。 继续沿着parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());方法跟下去 看doProcessConfigurationClass()方法。(SpringBoot的包扫描的入口方法,重点哦) 我们先大致说一下这个方法里面都干了什么,然后稍后再阅读源码分析。 上面代码的第29行 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(... 获取主类上的@PropertySource注解(关于该注解是怎么用的请自行百度),解析该注解并将该注解指定的properties配置文件中的值存储到Spring的 Environment中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。 42行 Set componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); 解析主类上的@ComponentScan注解,呃,怎么说呢,42行后面的代码将会解析该注解并进行包扫描。 68行 processImports(configClass, sourceClass, getImports(sourceClass), true); 解析主类上的@Import注解,并加载该注解指定的配置类。 TIPS: 在spring中好多注解都是一层一层封装的,比如@EnableXXX,是对@Import注解的二次封装。@SpringBootApplication注解=@ComponentScan+@EnableAutoConfiguration+@Import+@Configuration+@Component。@Controller,@Service等等是对@Component的二次封装。。。 从上面的42行往下看,来到第49行 Set 进入该方法 发现有两行重要的代码 为了验证代码中的注释,debug,看一下declaringClass,如下图所示确实是我们的主类的全路径名。 跳过这一行,继续debug,查看basePackages,该set集合中只有一个,就是主类所在的路径。 到这里呢IoC容器初始化三个步骤的第一步,Resource定位就完成了,成功定位到了主类所在的包。 接着往下看 return scanner.doScan(StringUtils.toStringArray(basePackages)); Spring是如何进行类扫描的。进入doScan()方法。 这个方法中有两个比较重要的方法,第7行 Set 跟踪调用栈 在第13行将basePackage拼接成classpath*:org/springframework/boot/demo/**/*.class,在第16行的getResources(packageSearchPath);方法中扫描到了该路径下的所有的类。然后遍历这些Resources,在第27行判断该类是不是 @Component 注解标注的类,并且不是需要排除掉的类。在第29行将扫描到的类,解析成ScannedGenericBeanDefinition,该类是BeanDefinition接口的实现类。OK,IoC容器的BeanDefinition载入到这里就结束了。 回到前面的doScan()方法,debug看一下结果(截图中所示的就是我定位的需要交给Spring容器管理的类)。 查看registerBeanDefinition()方法。是不是有点眼熟,在前面介绍prepareContext()方法时,我们详细介绍了主类的BeanDefinition是怎么一步一步的注册进DefaultListableBeanFactory的beanDefinitionMap中的。在此呢我们就省略1w字吧。完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableFactory中已经建立了
一、前言
1 BeanDefinition的Resource定位
2 BeanDefinition的载入
3 向IoC容器注册BeanDefinition
从run方法的,refreshContext()方法一路跟下去,最终来到AbstractApplicationContext类的refresh()方法。 1 @Override
2 public void refresh() throws BeansException, IllegalStateException {
3 synchronized (this.startupShutdownMonitor) {
4 // Prepare this context for refreshing.
5 //刷新上下文环境
6 prepareRefresh();
7 // Tell the subclass to refresh the internal bean factory.
8 //这里是在子类中启动 refreshBeanFactory() 的地方
9 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
10 // Prepare the bean factory for use in this context.
11 //准备bean工厂,以便在此上下文中使用
12 prepareBeanFactory(beanFactory);
13 try {
14 // Allows post-processing of the bean factory in context subclasses.
15 //设置 beanFactory 的后置处理
16 postProcessBeanFactory(beanFactory);
17 // Invoke factory processors registered as beans in the context.
18 //调用 BeanFactory 的后处理器,这些处理器是在Bean 定义中向容器注册的
19 invokeBeanFactoryPostProcessors(beanFactory);
20 // Register bean processors that intercept bean creation.
21 //注册Bean的后处理器,在Bean创建过程中调用
22 registerBeanPostProcessors(beanFactory);
23 // Initialize message source for this context.
24 //对上下文中的消息源进行初始化
25 initMessageSource();
26 // Initialize event multicaster for this context.
27 //初始化上下文中的事件机制
28 initApplicationEventMulticaster();
29 // Initialize other special beans in specific context subclasses.
30 //初始化其他特殊的Bean
31 onRefresh();
32 // Check for listener beans and register them.
33 //检查监听Bean并且将这些监听Bean向容器注册
34 registerListeners();
35 // Instantiate all remaining (non-lazy-init) singletons.
36 //实例化所有的(non-lazy-init)单件
37 finishBeanFactoryInitialization(beanFactory);
38 // Last step: publish corresponding event.
39 //发布容器事件,结束Refresh过程
40 finishRefresh();
41 } catch (BeansException ex) {
42 if (logger.isWarnEnabled()) {
43 logger.warn("Exception encountered during context initialization - " +
44 "cancelling refresh attempt: " + ex);
45 }
46 // Destroy already created singletons to avoid dangling resources.
47 destroyBeans();
48 // Reset ‘active‘ flag.
49 cancelRefresh(ex);
50 // Propagate exception to caller.
51 throw ex;
52 } finally {
53 // Reset common introspection caches in Spring‘s core, since we
54 // might not ever need metadata for singleton beans anymore...
55 resetCommonCaches();
56 }
57 }
58 }
二、obtainFreshBeanFactory();
1 public GenericApplicationContext() {
2 this.beanFactory = new DefaultListableBeanFactory();
3 }
1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
2 //刷新BeanFactory
3 refreshBeanFactory();
4 //获取beanFactory
5 ConfigurableListableBeanFactory beanFactory = getBeanFactory();
6 if (logger.isDebugEnabled()) {
7 logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
8 }
9 return beanFactory;
10 }
1 @Override
2 protected final void refreshBeanFactory() throws IllegalStateException {
3 if (!this.refreshed.compareAndSet(false, true)) {
4 throw new IllegalStateException(
5 "GenericApplicationContext does not support multiple refresh attempts: just call ‘refresh‘ once");
6 }
7 this.beanFactory.setSerializationId(getId());
8 }
TIPS:
1,AbstractApplicationContext类有两个子类实现了refreshBeanFactory(),但是在前面第三步初始化上下文的时候,
实例化了GenericApplicationContext类,所以没有进入AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
2,this.refreshed.compareAndSet(false, true)
这行代码在这里表示:GenericApplicationContext只允许刷新一次
这行代码,很重要,不是在Spring中很重要,而是这行代码本身。首先看一下this.refreshed属性:
private final AtomicBoolean refreshed = new AtomicBoolean();
java J.U.C并发包中很重要的一个原子类AtomicBoolean。通过该类的compareAndSet()方法可以实现一段代码绝对只实现一次的功能。
感兴趣的自行百度吧。
三、prepareBeanFactory(beanFactory);
1 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
2 // Tell the internal bean factory to use the context‘s class loader etc.
3 // 配置类加载器:默认使用当前上下文的类加载器
4 beanFactory.setBeanClassLoader(getClassLoader());
5 // 配置EL表达式:在Bean初始化完成,填充属性的时候会用到
6 beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
7 // 添加属性编辑器 PropertyEditor
8 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
9
10 // Configure the bean factory with context callbacks.
11 // 添加Bean的后置处理器
12 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
13 // 忽略装配以下指定的类
14 beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
15 beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
16 beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
17 beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
18 beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
19 beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
20
21 // BeanFactory interface not registered as resolvable type in a plain factory.
22 // MessageSource registered (and found for autowiring) as a bean.
23 // 将以下类注册到 beanFactory(DefaultListableBeanFactory) 的resolvableDependencies属性中
24 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
25 beanFactory.registerResolvableDependency(ResourceLoader.class, this);
26 beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
27 beanFactory.registerResolvableDependency(ApplicationContext.class, this);
28
29 // Register early post-processor for detecting inner beans as ApplicationListeners.
30 // 将早期后处理器注册为application监听器,用于检测内部bean
31 beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
32
33 // Detect a LoadTimeWeaver and prepare for weaving, if found.
34 //如果当前BeanFactory包含loadTimeWeaver Bean,说明存在类加载期织入AspectJ,
35 // 则把当前BeanFactory交给类加载期BeanPostProcessor实现类LoadTimeWeaverAwareProcessor来处理,
36 // 从而实现类加载期织入AspectJ的目的。
37 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
38 beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
39 // Set a temporary ClassLoader for type matching.
40 beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
41 }
42
43 // Register default environment beans.
44 // 将当前环境变量(environment) 注册为单例bean
45 if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
46 beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
47 }
48 // 将当前系统配置(systemProperties) 注册为单例Bean
49 if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
50 beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
51 }
52 // 将当前系统环境 (systemEnvironment) 注册为单例Bean
53 if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
54 beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
55 }
56 }
四、postProcessBeanFactory(beanFactory);
五、invokeBeanFactoryPostProcessors(beanFactory);(重点)
TIPS:
@Configuration,@Controller,@Service等注解底层都是@Component注解,只不过包装了一层罢了。
1 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
2 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
3 ...
4 }
5 // PostProcessorRegistrationDelegate类
6 public static void invokeBeanFactoryPostProcessors(
7 ConfigurableListableBeanFactory beanFactory, List
1 // ConfigurationClassParser类
2 public void parse(Set
TIPS:
至于processDeferredImportSelectors();方法,后面我们分析SpringBoot的自动装配的时候会详细讲解,各种starter是如何一步步的实现自动装配的。SpringBoot启动流程分析(五):SpringBoot自动装配原理实现>
1 // ConfigurationClassParser类
2 protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
3 processConfigurationClass(new ConfigurationClass(metadata, beanName));
4 }
5 // ConfigurationClassParser类
6 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
7 ...
8 // Recursively process the configuration class and its superclass hierarchy.
9 //递归地处理配置类及其父类层次结构。
10 SourceClass sourceClass = asSourceClass(configClass);
11 do {
12 //递归处理Bean,如果有父类,递归处理,直到顶层父类
13 sourceClass = doProcessConfigurationClass(configClass, sourceClass);
14 }
15 while (sourceClass != null);
16
17 this.configurationClasses.put(configClass, configClass);
18 }
19 // ConfigurationClassParser类
20 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
21 throws IOException {
22
23 // Recursively process any member (nested) classes first
24 //首先递归处理内部类,(SpringBoot项目的主类一般没有内部类)
25 processMemberClasses(configClass, sourceClass);
26
27 // Process any @PropertySource annotations
28 // 针对 @PropertySource 注解的属性配置处理
29 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
30 sourceClass.getMetadata(), PropertySources.class,
31 org.springframework.context.annotation.PropertySource.class)) {
32 if (this.environment instanceof ConfigurableEnvironment) {
33 processPropertySource(propertySource);
34 } else {
35 logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
36 "]. Reason: Environment must implement ConfigurableEnvironment");
37 }
38 }
39
40 // Process any @ComponentScan annotations
41 // 根据 @ComponentScan 注解,扫描项目中的Bean(SpringBoot 启动类上有该注解)
42 Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
43 sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
44 if (!componentScans.isEmpty() &&
45 !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
46 for (AnnotationAttributes componentScan : componentScans) {
47 // The config class is annotated with @ComponentScan -> perform the scan immediately
48 // 立即执行扫描,(SpringBoot项目为什么是从主类所在的包扫描,这就是关键了)
49 Set
TIPS:
在以上代码的第60行parse(bdCand.getBeanClassName(), holder.getBeanName());会进行递归调用,
因为当Spring扫描到需要加载的类会进一步判断每一个类是否满足是@Component/@Configuration注解的类,
如果满足会递归调用parse()方法,查找其相关的类。
同样的第68行processImports(configClass, sourceClass, getImports(sourceClass), true);
通过@Import注解查找到的类同样也会递归查找其相关的类。
两个递归在debug的时候会很乱,用文字叙述起来更让人难以理解,所以,我们只关注对主类的解析,及其类的扫描过程。
5.1、看看42-64行干了啥
1 // ComponentScanAnnotationParser类
2 public Set
TIPS:
为什么只有一个还要用一个集合呢,因为我们也可以用@ComponentScan注解指定扫描路径。
1 // ComponentScanAnnotationParser类
2 protected Set
5.1.1、findCandidateComponents(basePackage);
1 // ClassPathScanningCandidateComponentProvider类
2 public Set
5.1.2、registerBeanDefinition(definitionHolder, this.registry);
下一篇:PHP (20140513)
文章标题:SpringBoot启动流程分析(四):IoC容器的初始化过程
文章链接:http://soscw.com/essay/27839.html