小白也看得懂的 Spring IoC 核心流程介绍

简介: 小白也看得懂的 Spring本文将用最通俗易懂的文字介绍 Spring IoC 中的核心流程,主要用于帮助初学者快速了解 IoC 的核心流程,也可以用作之前源码分析文章的总结。本着简单的初衷,本文会省略掉大量流程,只介绍最重要的步骤。 IoC 核心流程介绍

前言


本文将用最通俗易懂的文字介绍 Spring IoC中的核心流程,主要用于帮助初学者快速了解 IoC 的核心流程,也可以用作之前源码分析文章的总结。本着简单的初衷,本文会省略掉大量流程,只介绍最重要的步骤。

 

基础概念


1IoC DI


IoC Inversion of Control),即控制反转。这不是一种新的技术,而是 Spring 的一种设计思想。


在传统的程序设计,我们直接在对象内部通过 new 来创建对象,是程序主动去创建依赖对象;而在Spring 中有专门的一个容器来创建和管理这些对象,并将对象依赖的其他对象注入到该对象中,这个容器我们一般称为 IoC 容器。


所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 Spring 控制,所以这叫控制反转。

 

DIDependency Injection),即依赖注入,由 Martin Fowler 提出。可以认为IoC DI 其实是同一个概念的不同角度描述。


依赖注入是指组件之间的依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。


通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

 

2bean


官方概念:在Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 beanbean 是一个由 Spring IoC 容器实例化,组装和管理的对象。


大白话:bean可以认为是那些我们想注入到 Spring IoC 容器的 Java 对象实例的抽象。

我们经常会在Service 上使用 @Service 注解,然后在要使用该 Service 的类中通过 @Autowire 注解来注入,这个 Service 就是一个 bean。在这个地方,@Service 注解相当于告诉 IoC 容器:这个类你需要帮我创建和管理;而 @Autowire 注解相当于告诉 IoC 容器:我需要依赖这个类,你需要帮我注入进来。

 

3BeanDefinition


理解了 beanBeanDefinition 就好理解了。BeanDefinition bean 的定义,用来存储 bean 的所有属性方法定义。

 

4BeanFactory ApplicationContext


BeanFactory:基础类型 IoC 容器,提供完整的 IoC 服务支持。

ApplicationContextBeanFactory 的子接口,在BeanFactory 的基础上构建,是相对比较高级的 IoC 容器实现。包含 BeanFactory 的所有功能,还提供了其他高级的特性,比如:事件发布、国际化信息支持、统一资源加载策略等。正常情况下,我们都是使用的 ApplicationContext

 

以电话来举例:

我们家里使用的座机就类似于 BeanFactory,可以进行电话通讯,满足了最基本的需求。

而现在非常普及的智能手机,iPhone、小米等,就类似于 ApplicationContext,除了能进行电话通讯,还有其他很多功能:拍照、地图导航、听歌等。

 

5FactoryBean


一般情况下,我们将 bean 的创建和管理都交给 Spring IoC 容器,Spring 会利用 bean class 属性指定的类来实例化 bean


但是如果我们想自己实现 bean 的创建操作,可以实现吗?答案是可以的,FactoryBean 就可以实现这个需求。


FactoryBean 是一种特殊的 bean,它是个工厂 bean,可以自己创建 bean 实例,如果一个类实现了 FactoryBean 接口,则该类可以自己定义创建实例对象的方法,只需要实现它的 getObject()方法即可。


FactoryBean 可能对于普通开发来说基本用不到也没去注意过,但是它其实应用的非常广,特别是在中间件中,如果你看过一些中间件的源码,一定会看到FactoryBean 的身影。

 

介绍了几个基础的类后,接下来将介绍 Spring IoC 的核心流程。

 

核心流程


一、容器构建启动入口


容器构建启动的入口有多种多样,这边以常用的 web.xml 配置的方式来说。

首先,我们会在web.xml 中配置 ContextLoaderListener 监听器,当 Tomcat 启动时,会触发 ContextLoaderListener contextInitialized 方法,从而开始 IoC 的构建流程。

另一个常用的参数是 contextConfigLocation,用于指定 Spring 配置文件的路径。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>open-joonwhee-service WAR</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:config/spring/appcontext-*.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

 

二、ApplicationContext 刷新前配置


在正式进入容器的刷新前,会进行一些前置操作。

1、确认要使用的容器,通常使用的是:XmlWebApplicationContext,如果是用 Spring Boot,一般是AnnotationConfigApplicationContext,但其实都差别不大,最终都会继承AbstractApplicationContext,核心逻辑也都是在 AbstractApplicationContext 中实现。

2、提供一个给开发者初始化 ApplicationContext 的机会,具体的使用如下。

 

例子:ApplicationContextInitializer 扩展使用

1)创建一个 ApplicationContextInitializer 接口的实现类,例如下面的 SpringApplicationContextInitializer,并在initialize 方法中进行自己的逻辑操作,例如:添加监听器、添加 BeanFactoryPostProcessor

package com.joonwhee.open.spring;
import com.joonwhee.open.listener.EarlyListener;
import com.joonwhee.open.processor.MyBeanFactoryPostProcessor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
 * @author joonwhee
 * @date 2019/1/19
 */
public class SpringApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 自己的逻辑实现
        // 例子1:通过硬编码的方式添加监听器
        EarlyListener earlyListener = new EarlyListener();
        applicationContext.addApplicationListener(earlyListener);
        // 例子2:通过硬编码的方式添加BeanFactoryPostProcessor
        MyBeanFactoryPostProcessor myBeanFactoryPostProcessor = new MyBeanFactoryPostProcessor();
        applicationContext.addBeanFactoryPostProcessor(myBeanFactoryPostProcessor);
    }
}

2)在web.xml中,定义 contextInitializerClasses globalInitializerClasses 参数,参数值为SpringApplicationContextInitializer 的全路径。

image.png


三、初始化 BeanFactory、加载 Bean 定义


1、创建一个新的 BeanFactory,默认为 DefaultListableBeanFactory

2、根据 web.xml contextConfigLocation 配置的路径,读取 Spring 配置文件,并封装成 Resource

3、根据 Resource 加载 XML 配置文件,并解析成Document 对象

4、从根节点开始,遍历解析 Document 中的节点。

4.1、对于默认命名空间的节点:先将 bean 节点内容解析封装成 BeanDefinition,然后将 beanNameBeanDefinition 放到 BeanFactory 的缓存中,用于后续创建 bean 实例时使用。


默认命名空间:http://www.springframework.org/schema/beans,可能存在的节点如下:

image.png

 

4.2、对于自定义命名空间的节点:会拿到自定义命名空间对应的解析器,对节点进行解析处理。


例如: ,该节点对应的解析器会扫描 base-package 指定路径下的所有类,将使用了 @Component@Controller@Service@Repository)注解的类封装成 BeanDefinition,然后将 beanNameBeanDefinition 放到 BeanFactory 的缓存中,用于后续创建 Bean 实例时使用。

 

四、触发 BeanFactoryPostProcessor


实例化和调用所有 BeanFactoryPostProcessor,包括其子类BeanDefinitionRegistryPostProcessor


BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它。


BeanDefinitionRegistryPostProcessor 继承自BeanFactoryPostProcessor,比 BeanFactoryPostProcessor 具有更高的优先级,主要用来在常规的 BeanFactoryPostProcessor 激活之前注册一些 bean定义。特别是,你可以通过 BeanDefinitionRegistryPostProcessor 来注册一些常规的 BeanFactoryPostProcessor,因为此时所有常规的 BeanFactoryPostProcessor 都还没开始被处理。 


注:这边的常规 BeanFactoryPostProcessor” 主要用来跟BeanDefinitionRegistryPostProcessor 区分。

 

例子:BeanFactoryPostProcessor 扩展使用


1)创建一个 BeanFactoryPostProcessor 接口的实现类,例如下面的MyBeanFactoryPostProcessor,并在 postProcessBeanFactory 方法中进行自己的逻辑操作。例如:扫描某个包路径,将该包路径下使用了某个注解的类全部注册到 Spring 中。


2)将该实现类注册到 Spring 容器中,例如使用 @Component 注解


package com.joonwhee.open.demo.spring;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
/**
 * @author joonwhee
 * @date 2019/2/18
 */
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor#postProcessBeanFactory");
        // 自己的逻辑处理
    }
}

另外,Mybatis 中的MapperScannerConfigurer 是一个典型的BeanDefinitionRegistryPostProcessor 的扩展使用,有兴趣的可以看看这个类的源码。

 

五、注册 BeanPostProcessor


注册所有的BeanPostProcessor,将所有实现了 BeanPostProcessor 接口的类加载到 BeanFactory 中。


BeanPostProcessor 接口是 Spring 初始化 bean时对外暴露的扩展点,Spring IoC 容器允许 BeanPostProcessor 在容器初始化 bean 的前后,添加自己的逻辑处理。在这边只是注册到 BeanFactory 中,具体调用是在 bean 初始化的时候。

 

例子:BeanPostProcessor 扩展使用


1)创建一个 BeanPostProcessor 接口的实现类,例如下面的MyBeanPostProcessor,并在方法中进行自己的逻辑操作。

2)将该实现类注册到 Spring 容器中,例如使用 @Component 注解。

package com.joonwhee.open.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
 * @author joonwhee
 * @date 2019/2/23
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor#postProcessBeforeInitialization");
        // 自己的逻辑
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor#postProcessAfterInitialization");
        // 自己的逻辑
        return bean;
    }
}

 

六、实例化所有剩余的非懒加载单例 bean


1、遍历所有被加载到缓存中的 beanName,触发所有剩余的非懒加载单例 bean 的实例化。


2、首先通过 beanName 尝试从缓存中获取,如果存在则跳过实例化过程;否则,进行 bean 的实例化。


3、根据 BeanDefinition,使用构造函数创建 bean 实例。


4、根据 BeanDefinition,进行 bean 实例属性填充。


5、执行 bean 实例的初始化。


5.1、触发 Aware 方法。


5.2、触发 BeanPostProcessor postProcessBeforeInitialization 方法。


5.3、如果 bean 实现了 InitializingBean 接口,则触发afterPropertiesSet() 方法。


5.4、如果 bean 设置了 init-method 属性,则触发 init-method 指定的方法。


5.5、触发 BeanPostProcessor postProcessAfterInitialization方法。


6、将创建好的 bean 实例放到缓存中,用于之后使用。


 

七、完成上下文的刷新


使用应用事件广播器推送上下文刷新完毕事件(ContextRefreshedEvent )到相应的监听器。

 

例子:监听器扩展使用


1)创建一个自定义监听器,实现 ApplicationListener 接口,监听 ContextRefreshedEvent(上下文刷新完毕事件)。

2)将该监听器注册到 Spring IoC 容器即可。

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
 * @author joonwhee
 * @date 2019/6/22
 */
@Component
public class MyRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 自己的逻辑处理
    }
}

 

至此,整个 IoC 的核心流程介绍完毕。

 

总结


本文只是以简单的介绍了 IoC 中的重要步骤,如果想详细了解 IoC 的完整流程,可以查看之前的源码解析文章。

相关文章
|
14天前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
22 0
|
5天前
|
Java Spring 容器
深入理解Spring Boot启动流程及其实战应用
【5月更文挑战第9天】本文详细解析了Spring Boot启动流程的概念和关键步骤,并结合实战示例,展示了如何在实际开发中运用这些知识。
17 2
|
14天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
22 0
|
14天前
|
XML Java 程序员
什么是Spring的IoC容器?
【4月更文挑战第30天】什么是Spring的IoC容器?
20 0
|
17天前
|
Java Spring 容器
【Spring系列笔记】IOC与DI
IoC 和 DI 是面向对象编程中的两个相关概念,它们主要用于解决程序中的依赖管理和解耦问题。 控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入和依赖查找。
34 2
|
17天前
|
Java 测试技术 数据库连接
Spring中ioc的优点
总之,Spring中的IoC提供了一种更加灵活、可维护、可测试和可扩展的方式来管理组件之间的依赖关系,从而提高了应用程序的质量和可维护性。这使得开发人员能够更专注于业务逻辑而不是底层的技术细节。
32 1
|
26天前
|
Java 应用服务中间件 Spring
|
27天前
|
XML Java 数据格式
Spring IOC的源码解析
【4月更文挑战第17天】Spring IOC(控制反转)的核心功能是通过依赖注入(DI)来管理对象的创建和它们之间的依赖关系。要深入理解Spring IOC的工作原理,我们可以从其源码分析入手,特别是关注如何创建和管理Bean以及依赖注入的实现
21 1
|
1月前
|
XML Java 数据格式
Spring IOC—基于XML配置和管理Bean 万字详解(通俗易懂)
Spring 第二节 IOC—基于XML配置和管理Bean 万字详解!。
94 5
|
1月前
|
XML Java 数据格式
Spring(一)IOC小案例
Spring(一)IOC小案例