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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 小白也看得懂的 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 的完整流程,可以查看之前的源码解析文章。

相关文章
|
17天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
38 2
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
3月前
|
XML Java 测试技术
spring复习01,IOC的思想和第一个spring程序helloWorld
Spring框架中IOC(控制反转)的思想和实现,通过一个简单的例子展示了如何通过IOC容器管理对象依赖,从而提高代码的灵活性和可维护性。
spring复习01,IOC的思想和第一个spring程序helloWorld
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
39 0
|
2月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
144 9
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
223 2
|
2月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
38 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
3月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
46 4
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
79 0
|
2月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
下一篇
DataWorks