spring容器的启动流程

spring容器的启动流程是一个面试中比较难答的题目。这块内容比较复杂,回答的时候如果想到什么回答什么,很容易把面试官绕晕。因此比较好的回答方式就是,先理清一个大致的启动流程,再根据面试官的问题细说小点。

这里我们从AnnotationConfigApplicationContext着手分析源码:

说明:这里通过AnnotationConfigApplicationContext扫描com.test包下所有被注解修饰的bean对象,创建了一个applicationContext对象(spring容器)。

这里的类A被@Component注解标识了,因此会被放进applicationContext上下文中。再通过getBean方法,就可以获取A的实例对象。

注意:关于spring容器的启动有很多种方式,比如说ClassPathApplicationContext,通过XML的方式启动一个spring容器。但没有annotation这种注解式开发更方便。


1 构造方法

跟进AnnotationConfigApplicationContext的构造方法可以看到:

1.1 this 方法

跟进这个this方法:

说明:

  1. StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create"); 这行代码主要用于启动一个名为"spring.context.annotated-bean-reader.create"的启动步骤,它是用于计算和监控Spring应用的启动时间,主要用于性能的跟踪监控。

  2. this.reader = new AnnotatedBeanDefinitionReader(this); 这行代码创建了一个AnnotatedBeanDefinitionReader实例,并将其赋值给AnnotationConfigApplicationContextreader成员变量。AnnotatedBeanDefinitionReader类主要用于从注解化的类中读取Bean定义信息,它是Spring处理注解主要的类。

  3. createAnnotatedBeanDefReader.end(); 这行代码结束了"spring.context.annotated-bean-reader.create"的启动步骤,主要用于性能的跟踪监控。

  4. this.scanner = new ClassPathBeanDefinitionScanner(this); 这行代码创建了一个ClassPathBeanDefinitionScanner实例,并将其赋值给AnnotationConfigApplicationContextscanner成员变量。ClassPathBeanDefinitionScanner类主要用于从classpath下的类文件中扫描Bean定义信息


1.2 scan 方法

详细解释如下:

  1. Assert.notEmpty(basePackages, "At least one base package must be specified"); 这行代码用于判断方法参数basePackages是否为空。如果为空,它将抛出一个IllegalArgumentException异常,消息为"At least one base package must be specified"。

  2. StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> {return Arrays.toString(basePackages);}); 这行代码启动一个名为"spring.context.base-packages.scan"的启动步骤,并标记了"packages"标签(其值为传入的basePackages)。这也是用于计算和监控Spring应用启动时间、性能跟踪。

  3. this.scanner.scan(basePackages); 这行代码使用之前在构造函数里面初始化的scanner(一个ClassPathBeanDefinitionScanner实例)来扫描指定的包basePackages。在这个过程中,scanner将查找所有包及其子包下的类,如果这个类符合Spring Bean的定义(比如被@Component, @Service, @Repository, @Controller等注解标注的类),那么scanner就会将这个类作为一个Bean定义并注册到Spring上下文。

  4. scanPackages.end(); 这行代码结束了之前启动的"spring.context.base-packages.scan"步骤。同样地,这个步骤也主要用于性能跟踪。

简单来说,scan方法就是:用于扫描指定的基础包,以查找并注册Bean定义。


1.3 refresh 方法(重难点)

java 复制代码
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            // 开启一个启动步骤记录器,用于记录开始刷新上下文
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            // 刷新前预处理,准备刷新上下文
            this.prepareRefresh();
            // 获取在容器初始化时创建的BeanFactory(我们后面取类都通过这个BeanFactory中加载出来)
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // 准备Bean工厂,BeanFactory的预处理工作,回向容器中添加一些组件
            this.prepareBeanFactory(beanFactory);

            try {
                // 处理Bean工厂(子类重写该方法,可以实现在BeanFactory创建并预处理完成后做进一步的设置)
                this.postProcessBeanFactory(beanFactory);
                // 开启一个启动步骤记录器,用于记录开始处理Bean后置处理器
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // 在BeanFactory初始化之后执行BeanFactory的后置处理器
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 注册Bean后置处理器。它的主要作用就是干预Spring初始化Bean的流程,完成代理、自动注入、循环依赖等功能。
                this.registerBeanPostProcessors(beanFactory);
                // 结束Bean后置处理器步骤记录
                beanPostProcess.end();
                // 初始化Messagesource组件,主要是用于国际化
                this.initMessageSource();
                // 初始化应用事件广播器
                this.initApplicationEventMulticaster();
                // 执行刷新回调方法(留给子类重写的方法,在容器刷新时可以自定义一些逻辑)
                this.onRefresh();
                // 注册监听器(有了监听器之后,就可以接收上面事件广播器广播的消息了)
                this.registerListeners();
                // 完成Bean工厂的初始化,主要作用是初始化所有剩下的单例Bean
                this.finishBeanFactoryInitialization(beanFactory);
                // 完成刷新,发布容器刷新完成的事件
                this.finishRefresh();
            } 


                //以下是容器启动失败的场景,可以不过多深究
                catch (BeansException var10) {
                // 如果在上下文初始化过程中遇到异常,记录警告日志并取消刷新尝试
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                }

                // 销毁所有Bean
                this.destroyBeans();
                // 取消刷新
                this.cancelRefresh(var10);
                // 抛出异常
                throw var10;
            } finally {
                // 重置公共缓存
                this.resetCommonCaches();
                // 结束刷新步骤记录
                contextRefresh.end();
            }

        }
    }
相关推荐
Adolf_19938 分钟前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
Jarlen8 分钟前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽14 分钟前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
叫我:松哥20 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Reese_Cool21 分钟前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
海里真的有鱼22 分钟前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺33 分钟前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
严文文-Chris1 小时前
【设计模式-享元】
android·java·设计模式
Flying_Fish_roe1 小时前
浏览器的内存回收机制&监控内存泄漏
java·前端·ecmascript·es6