盒子
盒子
文章目录
  1. 前言
  2. Spring 启动
    1. 原文
    2. 正文
  3. Spring IOC
    1. 原文
    2. 正文
    3. 容器启动
    4. getBean

Spring 启动流程

前言

基于 spring version = 5.2.0.BUILD-SNAPSHOT

请求从浏览器到 Web 服务器,不提,具体查看 Java Web 启动流程…

抠字眼地提一下,说起来 Spring 是一个 简化 Java 开发的框架,不应该直接放到服务端开发来讨论.所以这里讨论 Spring 的启动流程,应当是 Spring 用于服务端开发时的启动流程.而 Spring 用于服务端开发,自然离不开 Spring MVC.

Spring MVC 是基于 Spring 的 Web 框架,其作用相当于对 servlet 做一层封装,在 Web 服务器和 servlet 间利用 Spring IOC 来管理 Bean 的生命周期与依赖关系.

事实上,Spring 启动流程,其实就是 Spring 环境(上下文之类的) + Spring IOC 容器的启动.

以下讨论 Web 服务器以 Tomcat 为例…

Spring 启动

原文

基本 copy 自这

https://blog.csdn.net/lovejj1994/article/details/79164317

正文

在 web.xml 中提供一个 contextLoaderListener,当 Web 容器启动,触发容器初始化事件,contextLoaderListener 会监听到这个事件,其 contextLnitialized() 方法被调用,在这个方法中,Spring 初始化一个 根上下文 WebApplicationContext.这是一个接口类,其实际实现类是 XmlWebApplicationContext.

这个就是 Spring 的 IOC 容器.其对应的 Bean 定义的配置由 web.xml 中的 context-param 标签指定.在这个 IOC 容器初始化完毕后,Spring 以 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 为 属性 key,将其存储到 ServletContext 中,以便获取.

contextLoaderListener 监听器初始化完毕后,开始初始化 web.xml 中配置的 Servlet,这里是 DispatcherServlet….这属于 Spring MVC 部分了…

  1. Web 容器提供上下文环境.
  2. Spring IOC 容器初始化.

Web 容器(Tomcat)在启动时加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象.此 ServletContext 对象中可以存放共享数据,可以看作一个 Web 应用的服务器端组建的共享内存.也称为 一个 全局的上下文环境,为后面的 Spring IOC 容器提供宿主环境.

不严谨地说,有一个监听者监听着 ServletContext 的创建,一旦发现 ServletContext 创建了,就开始创建 Spring 容器.

这个过程是怎么实现的呢?

在 Spring MVC 项目中,其 web.xml 定义了一个 contextLoaderListener.

1
2
3
4
5
...
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...

ContextLoaderListener(spring 中的类)继承自 ContextLoader(spring 中的类),并实现ServletContextListener(servlet 中的接口),ServletContextListener是容器里的类,如果用的是 tomcat,这个类就在 tomcat 的 lib 里.

注意!!!这里的 ServletContextListener 便是 ServletContext 的监听者,监视着 ServletContext 的创建.ServletContext 一旦创建,就调用该接口的回调方法来启动 Spring 容器.

(真正的实现是在 spring 里的 ContextLoaderListener)

查看一下 ServletContextListener.

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ServletContextListener extends EventListener {
/**
* 通知在web程序初始化的时候开始,所有的ServletContextListeners都会在
* web应用中任何的filter和servlet初始化之前接收到context初始化的时候通知
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
public void contextInitialized(ServletContextEvent sce);

public void contextDestroyed(ServletContextEvent sce);
}

启动 Spring 容器便是通过回调 contextLoaderListener 的 contextInitialized() 方法.

再查看一下 contextInitialized() 的实现..

1
2
3
4
5
6
7
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

能看到,这里传入了 ServletContext 对象.

我们继续深入…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 为给定的 ServletContext 初始化 Spring 容器
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// ...
try {
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}

//...
}

删除了一些不影响解释 spring 启动的代码.

能看到,这个方式试图 基于 ServletContext, 创建一个 ApplicationContext.

前面提到 ServletContext 是 全局上下文 ,这里的 ApplicationContext 便是 Spring 的上下文.(根上下文)

代码中的 WebApplicationContext 是一个接口类,其实际实现是 XmlWebApplicationContext.

Context 被称为上下文,其实也可称为 容器.

这个 ApplicationContext 便是 Spring 的较高级的 IOC 容器.

Spring IOC 容器 : BeanFactory, ApplicationContext

IOC 具体的加载与初始化这里暂且不提…

简要提一下

ApplicationContext 就是 Spring 的 IOC 容器,其对应的 Bean 定义的配置由 web.xml 中的 context-param 标签指定.在这个 IOC 容器初始化完毕后,Spring 以 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 为 属性 key,将其存储到 ServletContext 中,以便获取.

contextLoaderListener 监听器初始化完毕后,开始初始化 web.xml 中配置的 Servlet,这里是 DispatcherServlet….这属于 Spring MVC 部分了…

Spring IOC

原文

源码解释等基本 copy 自这篇文章 : Spring IOC 容器源码分析

正文

IOC 容器 : BeanFactory,ApplicationContext

[Spring 揭秘]

  • BeanFactory

    基础类型 IOC 容器,提供完整的 IOC 服务支持.默认采用 lazy-load(懒加载),当客户端对象需要访问容器中某个受管理的对象时,才对该受管理的对象进行初始化以及依赖注入操作.

    也因此,该容器启动初期速度很快,所需资源有限.适合于资源有限,功能要求不严格的场景.

  • ApplicationContext

    ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory 的所有支持外,还提供了其他高级特性.如 时间发布,国际化支持…

    如何支持的呢?实际上是通过实现多个接口.

    BeanFactory –> 管理,装配 bean

    RousourcePatternResolver –> 加载资源文件

    MessageSource –> 能够实现国际化等功能

    ApplicationEventPublisher –> 注册监听器,实现监听机制

    ApplicationContext 管理的对象在该类型容器启动后,默认全部初始化并绑定完成.

    因此,需要更多资源,比起 BeanFactory 启动时间较长.

Spring IOC 的启动分两步 :

  1. 容器启动
  2. Bean 实例化

首先通过 某种途径 加载 Configuration MetaData(通常也就是 xml 格式的配置信息).

除了代码方式比较直接,大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的 Configuration MetaData 进行解析和分析,并将分析后的信息编组为相应的 BeanDefinition,最后把这些保存了 Bean 定义信息的 BeanDefinition,注册到相应的 BeanDefinitionRegistry.

这样,容器的启动工作就完成了.

当某个请求方通过容器的 getBean 方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用 getBean 方法时,就会触发 Bean 的实例化.

容器首先检查所请求的对象之前是否已经初始化,如没有,则根据注册的 BeanDefinition 所提供的信息实例化请求对象,并为其注入依赖.如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它.当该对象装配完毕,容器立即将其返回给请求方使用.

容器启动

在前面提到过,Spring 是一个简化 Java 开发的框架,不过如果只是拿来用,一般都是直接上手 SSM 或者 Spring Boot.在这些更加简化的框架里,最简单的依赖注入甚至只需要一个注解…现在先忘掉这些简化后的东西.

经过 Spring 启动的步骤后,我们已经能够获得 Spring IOC 容器了,然后通过容器提供的方法做一些事.

举例:

1
2
3
4
ApplicationContext appCt = new ClassPathXmlApplicationContext("app.spring.xml"); 
// ...
String[] xmlCfg = new String[] { "classpath:base.spring.xml","app.spring.xml"};
ApplicationContext appCt = new ClassPathXmlApplicationContext(xmlCfg);

能看出,获取 ApplicationContext 容器调用的是 ClassPathXmlApplicationContext 这个类的构造方法.

那么我们就看看 ClassPathXmlApplicationContext 这个类吧.

这个类在 org.springframework.context.support 包下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
// 将配置文件作为资源都存放到这个数组中
private Resource[] configResources;

// 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
...
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

super(parent);
// 解析配置文件列表,放置到上面说的那个 configResources 数组中
setConfigLocations(configLocations);
if (refresh) {
refresh(); // 核心方法
}
}
...
}

这里的 refresh() 就是执行各种初始化的方法啦.

命名为 refresh() 而不是 init() 是因为 ApplicationContext 建立后,可以通过 refresh() 方法销毁原来的 ApplicationContext 并重新执行一次初始化操作.

refresh() 实际上是在父类 AbstractXmlApplicationContext 里实现的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// AbstractXmlApplicationContext.class
@Override
public void refresh() throws BeansException, IllegalStateException {
// 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
synchronized (this.startupShutdownMonitor) {

// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();

// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
// 这块待会会展开说
prepareBeanFactory(beanFactory);

try {
// 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
// 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】

// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);

// 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
// 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
// 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
registerBeanPostProcessors(beanFactory);

// 初始化当前 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);
}

// Destroy already created singletons to avoid dangling resources.
// 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// 把异常往外抛
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

这里提及一点.

看源码,能发现 AbstractXmlApplicationContext 类实际维护了一个 BeanFactory 对象.

1
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

查看 obtainFreshBeanFactory() 返回的是 ConfigurableListableBeanFactory 的实例.

1
2
3
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//...
}

不过,ConfigurableListableBeanFactory 是一个接口,而它的实现类只有一个,DefaultListableBeanFactory.

所以我们可以说,这里维护的是 DefaultListableBeanFactory 的实例.

后面所有关于 BeanFactory 相关的操作其实是由这个实例来处理的.

refresh() 的内容就不具体讲了.

我们再深入一下 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); .

还是回到 obtainFreshBeanFactory()

1
2
3
4
5
6
// AbstractApplicationContext.java
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等等
refreshBeanFactory();
return getBeanFactory();
}

再深入查看 refreshBeanFactory()

1
2
// AbstractApplicationContext.java
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

抽象方法啊…

在子类中实现…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果 ApplicationContext 中已经加载过 BeanFactory 了,销毁所有 Bean,关闭 BeanFactory
// 注意,应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前
// ApplicationContext 是否有 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 初始化一个 DefaultListableBeanFactory,为什么用这个,我们马上说。
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 用于 BeanFactory 的序列化,我想不部分人应该都用不到
beanFactory.setSerializationId(getId());

// 下面这两个方法很重要,别跟丢了,具体细节之后说
// 设置 BeanFactory 的两个配置属性:是否允许 Bean 覆盖、是否允许循环引用
customizeBeanFactory(beanFactory);

// 加载 Bean 到 BeanFactory 中
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
}

能看到,有个 loadBeanDefinitions(beanFactory); .

这个方法又在 AbstractXmlApplicationContext.java 中实现…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AbstractXmlApplicationContext.java
/** 我们可以看到,此方法将通过一个 XmlBeanDefinitionReader 实例来加载各个 Bean。*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 给这个 BeanFactory 实例化一个 XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// 初始化 BeanDefinitionReader,其实这个是提供给子类覆写的,
// 我看了一下,没有类覆写这个方法,我们姑且当做不重要吧
initBeanDefinitionReader(beanDefinitionReader);

// 重点来了,继续往下
loadBeanDefinitions(beanDefinitionReader);
}

啊,总算到这啦!!

重点在 loadBeanDefinitions(beanDefinitionReader); 这行代码内.

执行了加载 Bean,解析为 BeanDefinition,以及注册到相应的 BeanDefinitionRegistry.

先到这吧!!!这里就不继续深入了………………..

需要了解的话,看别人的分析吧………….

比如我上面的链接………………

这里就是上文提到的 :

大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的 Configuration MetaData 进行解析和分析,并将分析后的信息编组为相应的 BeanDefinition,最后把这些保存了 Bean 定义信息的 BeanDefinition,注册到相应的 BeanDefinitionRegistry.

Spring 会把开发者定义的 Bean 转换为 BeanDefinition 储存于 Spring 的 BeanFactory.

然后了解一下 getBean 是怎么做的…

getBean

前面提到,ApplicationContext 维护一个 BeanFactory 实例 DefaultListableBeanFactory .实际操作其实是靠 DefaultListableBeanFactory.

getBean() 方法也定义在 BeanFactory 中…

1
2
3
4
5
public interface BeanFactory {
// ...
Object getBean(String name) throws BeansException;
// ...
}

DefaultListableBeanFactory 中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory{
// ...
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
return getBean(requiredType, (Object[]) null);
}

@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
if (resolved == null) {
throw new NoSuchBeanDefinitionException(requiredType);
}
return (T) resolved;
}
// ...
}

不深入了先…

PS : Spring version=5.2.0.BUILD-SNAPSHOT 这里的实现有所改动,与上文链接中不一致了.

先到此.