spring容器的启动流程是一个面试中比较难答的题目。这块内容比较复杂,回答的时候如果想到什么回答什么,很容易把面试官绕晕。因此比较好的回答方式就是,先理清一个大致的启动流程,再根据面试官的问题细说小点。
这里我们从AnnotationConfigApplicationContext着手分析源码:
说明:这里通过AnnotationConfigApplicationContext扫描com.test包下所有被注解修饰的bean对象,创建了一个applicationContext对象(spring容器)。
这里的类A被@Component注解标识了,因此会被放进applicationContext上下文中。再通过getBean方法,就可以获取A的实例对象。
注意:关于spring容器的启动有很多种方式,比如说ClassPathApplicationContext,通过XML的方式启动一个spring容器。但没有annotation这种注解式开发更方便。
1 构造方法
跟进AnnotationConfigApplicationContext的构造方法可以看到:
1.1 this 方法
跟进这个this方法:
说明:
-
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
这行代码主要用于启动一个名为"spring.context.annotated-bean-reader.create"的启动步骤,它是用于计算和监控Spring应用的启动时间,主要用于性能的跟踪监控。 -
this.reader = new AnnotatedBeanDefinitionReader(this);
这行代码创建了一个AnnotatedBeanDefinitionReader
实例,并将其赋值给AnnotationConfigApplicationContext
的reader
成员变量。AnnotatedBeanDefinitionReader
类主要用于从注解化的类中读取Bean定义信息,它是Spring处理注解主要的类。 -
createAnnotatedBeanDefReader.end();
这行代码结束了"spring.context.annotated-bean-reader.create"的启动步骤,主要用于性能的跟踪监控。 -
this.scanner = new ClassPathBeanDefinitionScanner(this);
这行代码创建了一个ClassPathBeanDefinitionScanner
实例,并将其赋值给AnnotationConfigApplicationContext
的scanner
成员变量。ClassPathBeanDefinitionScanner
类主要用于从classpath下的类文件中扫描Bean定义信息。
1.2 scan 方法
详细解释如下:
-
Assert.notEmpty(basePackages, "At least one base package must be specified");
这行代码用于判断方法参数basePackages
是否为空。如果为空,它将抛出一个IllegalArgumentException
异常,消息为"At least one base package must be specified"。 -
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> {return Arrays.toString(basePackages);});
这行代码启动一个名为"spring.context.base-packages.scan"的启动步骤,并标记了"packages"标签(其值为传入的basePackages
)。这也是用于计算和监控Spring应用启动时间、性能跟踪。 -
this.scanner.scan(basePackages);
这行代码使用之前在构造函数里面初始化的scanner
(一个ClassPathBeanDefinitionScanner
实例)来扫描指定的包basePackages
。在这个过程中,scanner
将查找所有包及其子包下的类,如果这个类符合Spring Bean的定义(比如被@Component, @Service, @Repository, @Controller等注解标注的类),那么scanner
就会将这个类作为一个Bean定义并注册到Spring上下文。 -
scanPackages.end();
这行代码结束了之前启动的"spring.context.base-packages.scan"步骤。同样地,这个步骤也主要用于性能跟踪。
简单来说,scan方法就是:用于扫描指定的基础包,以查找并注册Bean定义。
1.3 refresh 方法(重难点)
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工厂的初始化,主要作用是初始化所有剩下的单例Beanthis.finishBeanFactoryInitialization(beanFactory);// 完成刷新,发布容器刷新完成的事件this.finishRefresh();} //以下是容器启动失败的场景,可以不过多深究catch (BeansException var10) {// 如果在上下文初始化过程中遇到异常,记录警告日志并取消刷新尝试if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);}// 销毁所有Beanthis.destroyBeans();// 取消刷新this.cancelRefresh(var10);// 抛出异常throw var10;} finally {// 重置公共缓存this.resetCommonCaches();// 结束刷新步骤记录contextRefresh.end();}}}