本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。
记录日期:2022.1.4
大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。
文章目录
框架原理 - Spring(二)之Spring IOC 源码引入refresh()
IOC容器
本文所有Spring 版本 都为 5.3.9。
引入
写一个最基本的启动Spring容器的例子:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationfile.xml");
}
以上代码就可以利用配置文件来启动一个 Spring 容器了,在maven的pom文件中直接在 dependencies 中加上以下依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来,如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
Spring 是渐进式的工具,并不具有很强的侵入性,它的模块也划分得很合理,即使你的应用不是 web 应用,或者之前完全没有使用到 Spring,而你就想用 Spring 的依赖注入这个功能,其实完全是可以的,它的引入不会对其他的组件产生冲突。
简而言之,它并不是和web应用开发强绑定的框架。
ApplicationContext context = new ClassPathXmlApplicationContext(…)
其实很好理解,从名字上就能看出,就是在 ClassPath
中寻找 xml 配置文件
,根据 xml 配置文件
内容来构建 ApplicationContext
。当然,除了 ClassPathXmlApplicationContext
以外,我们也还有其他构建 ApplicationContext
的方案可供选择,我们可以来看看的继承结构如下:
因为 Spring 为了适应各种使用场景,提供的各个接口都可能有很多的实现类。对于我们来说,就是梳理一个完整的分支看完。
我们可以看到,ClassPathXmlApplicationContext
是 ApplicationContext
接口非常下面的实现;同样的,和它并级的还有FileSystemXmlApplicationContext
。
FileSystemXmlApplicationContext
的构造函数需要一个 xml 配置文件在系统中的路径,其他和 ClassPathXmlApplicationContext 基本上一样。AnnotationConfigApplicationContext
是基于注解来使用的,它不需要配置文件,采用 java 配置类和各种注解来配置,是比较简单的方式,未来是大势所趋。
这里应该主要针对ClassPathXmlApplicationContext
来进行说明。
我们先来编写示例代码:
// message 接口
public interface MessageService {
String getMessage();
}
// message 接口实现类
public class MessageServiceImpl implements MessageService {
public String getMessage() {
return "hello world";
}
}
接下来我们要在resources 目录新建一个配置文件,文件名随意,通常叫 application.xml 或 application-xxx.xml。
public class App {
public static void main(String[] args) {
// 用我们的配置文件来启动一个 ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
System.out.println("ApplicationContext 启动成功");
// 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式
MessageService messageService = context.getBean(MessageService.class);
// 这句将输出: hello world
System.out.println(messageService.getMessage());
}
}
怎么样通过配置文件来启动 Spring 的 ApplicationContext
?也就是本文要分析的 IOC 的核心内容。
ApplicationContext
启动过程中,会负责创建实例 Bean,往各个 Bean 中注入依赖等。
源码分析开始
BeanFactory介绍
前面说的 ApplicationContext
其实就是一个 BeanFactory
。我们来看下和 BeanFactory
接口相关的主要的继承结构:
需要关注的几个父类和接口如下:
ApplicationContext
继承了ListableBeanFactory
,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。ApplicationContext
继承了HierarchicalBeanFactory
,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。AutowireCapableBeanFactory
这个名字中的Autowire
大家都非常熟悉,它就是用来自动装配Bean
用的,但是仔细看上图,ApplicationContext
并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到ApplicationContext
接口定义中的最后一个方法getAutowireCapableBeanFactory()
就知道了。ConfigurableListableBeanFactory
也是一个特殊的接口,看图,特殊之处在于它继承了第二层所有的三个接口,而ApplicationContext
没有。这点之后会用到。
上面五个框出来的类可以去源码中自己先阅读一遍。
IOC容器初始化入口
首先从 ClassPathXmlApplicationContext
的构造方法开始看:
// org.springframework.context.support.ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
// 将配置文件作为资源都存放到这个数组中
@Nullable
private Resource[] configResources;
public ClassPathXmlApplicationContext() {
}
// 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
...
// 最终调用的构造方法
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
// 解析配置文件列表,放置到上面说的那个 configResources 数组中
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
// 核心方法
refresh();
}
...
}
接下来,就是 refresh()
方法,这里说明一下为什么是 refresh()
,而不是 init()
这种名字的方法。因为 ApplicationContext
建立起来以后,其实我们是可以通过调用 refresh()
这个方法重建的,这样会将原来的 ApplicationContext
销毁,然后再重新执行一次初始化操作。
AbstractApplicationContext#refresh()
我们再来看一下refresh()
方法的主要内容:
@Override
public void refresh() throws BeansException, IllegalStateException {
// 添加同步锁,startupShutdownMonitor = new Object()
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();
// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
prepareBeanFactory(beanFactory);
try {
// 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
// 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】
// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
// 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
// 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
initMessageSource();
// 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
initApplicationEventMulticaster();
// 从方法名就可以知道,典型的模板方法(钩子方法),
// 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
onRefresh();
// 注册事件***,***需要实现 ApplicationListener 接口。这里不是重点
registerListeners();
// 重点,重点,重点
// 初始化所有的 singleton beans
//(lazy-init 的除外)
finishBeanFactoryInitialization(beanFactory);
// 最后,广播事件,ApplicationContext 初始化完成
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
突然发现这段
refresh()
比之前的refresh()
多了一个applicationStartup
参数调用,默认是一个无实现的DefaultApplicationStartup
,它的start
和end
都是空实现。它的作用是在应用程序启动期间标记步骤,并收集有关执行上下文或其处理时间的数据。这个不是重点。