Spring核心流程梳理

Easter79
• 阅读 564

1. 简介

之前其实有写过Spring Bean的生命周期:Spring 容器Bean与生命周期

当时太过于关注细节的实现,而进入了Spring庞大的体系之中,对于还不太了解Spring的朋友不太友好,也不方便记忆。

所以,这一次以应用为目的来重新梳理一下Spring核心流程。

因为对于实用主义来说,暂时并不想关心那么多Spring的细节,现在只希望能做2件事情:

  1. 当遇到问题了能快速知道大致那个步骤出现了问题,知道debug入口,以便于快速定位问题
  2. 知道一些Spring的重要扩展点,以便于更好的根据自己的需求完成自定义

2. 开始

想想我们使用Spring最常用的操作是什么?

无非就是我们添加一些xml、properties、注解配置。

然后,Spring解析这些配置帮我们创建相应的对象,并且用一个容器(上下文,BeanFactory,ApplicationContext)统一来管理。

如果,我们想要知道Spring怎样根据我们的配置,创建出我们需要的对象,那就得对Spring怎样创建上下文的流程有所了解。

所以,下面我们来简单看一下Spring创建上下文的核心流程。

注意:不要太过于关注每一个分支的细节,需要的时候再细看,否则就会没完没了的陷入一个又一个的分支细节之中。

不关心细节,只关心流程。

所以我们这里先不管是使用xml配置的ClassPathXmlApplicationContext,还是使用注解的AnnotationConfigApplicationContext,亦或是其他的Context。

我们这里只关注一个抽象类:AbstractApplicationContext,并且只关注它的refresh方法。

3. AbstractApplicationContext的refresh

这是一个典型的模板方法模式,在refresh方法中定义了各种Context的创建流程,具体怎么实现,就看个人。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            prepareRefresh();
            // 告诉子类该刷新BeanFactory了
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);

            try {
                // 告诉子类BeanFactory准备完成了
                postProcessBeanFactory(beanFactory);

                // 统计信息
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // 调用BeanFactoryPostProcessor来进一步定义BeanFactory
                invokeBeanFactoryPostProcessors(beanFactory);

                // 创建注册BeanPostProcessor
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // 国际化设置
                initMessageSource();

                // 初始化事件多播
                initApplicationEventMulticaster();

                // 告诉子类可以实例化其他一些特殊Bean了
                onRefresh();

                // 检查注册事件监听器
                registerListeners();

                // 实例化所有剩下的非懒加载单例bean
                finishBeanFactoryInitialization(beanFactory);

                // 发布相应的事件
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // 消耗已经创建的bean
                destroyBeans();

                // 重置 'active' 标识
                cancelRefresh(ex);
                throw ex;
            }
            finally {
                // 重置哪些可能已经不需要的公共缓存
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

4. refresh方法说明

顺序

调用方法

说明

1

prepareRefresh

处理properties的占位符,验证上下文环境中所有的properties是否可以解析,设置早期事件监听器

2

obtainFreshBeanFacotry

获取的BeanFactory,主要是为了得到BeanDefinition这么一个玩意儿,解析创建BeanDefinition不一定发生在这一步

3

prepareBeanFactory

BeanFactory定制化工作

4

postProcessBeanFactory

BeanFactory准备好了,如果还想在对BeanFactory进行定制可以在这里完成

5

invokeBeanFactoryPostProcessors

BeanFctoryPostProcessor在这个地方被执行,可以处理一些特殊的bean,如EventListenerMethodProcessor的EventListenerFactory

6

registerBeanPostProcessors

实例化和注册BeanPostProcessor,并处理它们的执行顺序

7

initMessageSource

国际化设置

8

initApplicationEventMulticaster

初始化事件多播器

9

onRefresh

用于初始化一些特定Bean,如SpringBoot重写该方法启动Tomcat容器

10

registerListeners

检查容器中所有的事件监听器,并注册

11

finishBeanFactoryInitialization

实例化剩下所有非懒加载的单例

12

finishRefresh

发布容器刷新完成事件

13

resetCommonCaches

清除公共缓存数据

AbstractApplicationContext为prepareBeanFactory提供了默认实现,主要完成下面的工作:

  1. 设置类加载器

  2. 设置SpEL解析器和属性注册解析器

  3. 利用BeanPostProcessor的特性给各种Aware接口的实现类注入ApplicationContext中对应的属性

  4. 设置各种Aware接口的实现类为忽略自动装配

  5. 手动注册BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext

  6. 探测LoadTimeWeaver,并完成动态织入

  7. 注册其他工具bean:ConfigurableEnvironment、系统属性、系统环境变量、ApplicationStartup(容器启动监控)

    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { // 设置类加载器 beanFactory.setBeanClassLoader(getClassLoader()); if (!shouldIgnoreSpel) {//设置SpEL解析器 beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader())); } // 属性注册解析器 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); // 配置BeanFactory的回调类 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); // 配置忽略自动装配的接口 beanFactory.ignoreDependencyInterface(EnvironmentAware.class); beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); beanFactory.ignoreDependencyInterface(ApplicationStartup.class); // BeanFactory中手动注册下面这些类 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); // 注册一个早期的BeanPostProcessor用来探测beans是不是一个ApplicationListeners beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); // 探测LoadTimeWeaver,如果是准备织入 if (!IN_NATIVE_IMAGE && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); // 设置临时类加载器 beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } // 注册默认的environment beans ConfigurableEnvironment if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); } // 注册系统属性bean if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); } //注册系统环境变量bean if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); } // 注册启动指标监测bean if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) { beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup()); } }

TIPS:

  1. PropertySource,主要处理properties文件中的键值对和系统变量
  2. PropertySource会注册到上下文环境中,我们可以通过Environment这个类直接获取
  3. systemProperties与systemEnvironment有啥区别?就是System.getProperties()与System.getenv(),更具体就是systemProperties是user.dir这些,systemEnvironment是PATH、OS这些

5. 核心流程梳理

前面我们已经大致从代码层面简单的介绍了Spring上下文启动的流程,下面用一个图做一个简单的梳理。

Spring核心流程梳理

6. Bean生命周期

前面在介绍Spring上下文启动的流程,我们为了尽量聚焦于核心的流程,并没有细化进入介绍和Bean生命周期的流程。

其实,对于定制化来说,我们更多的是针对Bean的定制化,你可以通过【】这篇文章了解Bean的生命周期。

当然,你也可以通过下面两个类来详细了解:

AnnotationConfigApplicationContext:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

Spring核心流程梳理

ClassPathXmlApplicationContext:

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

Spring核心流程梳理

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
spring源码
SpringIOC容器源码解析系列,建议大家按顺序阅读,欢迎讨论(_spring源码均为4.1.6.RELEASE版本_)1.Spring源码IOC容器(一)构建简单IOC容器(https://my.oschina.net/u/2377110/blog/902073)2.Spring源码IOC容器(二)Bean的定位解析注
Easter79 Easter79
3年前
spring注解
随着越来越多地使用Springboot敏捷开发,更多地使用注解配置Spring,而不是Spring的applicationContext.xml文件。Configuration注解:Spring解析为配置类,相当于spring配置文件Bean注解:容器注册Bean组件,默认id为方法名@Configurat
Easter79 Easter79
3年前
springcloud eureka.instance
1.在springcloud中服务的 InstanceID默认值是:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance\_id:${server.port}},也就是:主机名:应用名:应用端口。如图1
Easter79 Easter79
3年前
Spring中管理Bean依赖注入之后和Bean销毁之前的行为
    对于Singleton作用域的Bean,Spring容器将会跟踪它们的生命周期,容器知道何时实例化结束、何时销毁。Spring可以管理Bean在实例化结束之后和Bean销毁之前的行为。Bean依赖关系注入之后的行为:    Spring提供了两种方式在Bean全部属性设置成功后执行特定的行为:在Spring配置文件
Stella981 Stella981
3年前
Spring Bean生命周期
1\.简介在【Spring核心流程梳理】我们介绍了Spring容器的refresh过程,但是我们并没有进入到SpringBean的创建等生命周期等内容。这里,就来梳理一下SpringBean的生命周期。我们还是关注流程,重点关注核心的入口,忽略具体实现的细节。目标也很简单:1.知道SpringBean相关的扩展点
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k