Skip to content

Spring进阶- Spring IOC实现原理详解之IOC初始化流程 #31

Description

@cyjxiaozhi

本节我们主要研究的是:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中

Spring IOC实现原理详解之IOC初始化流程

Spring的IOC(控制反转)容器通过管理应用中的对象(即Bean)的生命周期和依赖关系,实现了松耦合、模块化的设计。在Spring中,IOC容器负责创建对象、管理对象之间的依赖关系以及执行生命周期的各个阶段。本文将详细解析Spring IOC的初始化流程,帮助大家深入理解Spring容器的工作原理。

一、Spring IOC容器初始化流程概述

Spring IOC容器初始化过程可简要总结为以下几个步骤:
1. 容器创建与加载配置
2. 读取Bean定义(注册Bean)
3. 实例化Bean对象
4. 填充Bean的依赖(依赖注入)
5. 执行Bean初始化方法
6. 容器准备就绪,提供服务
7. 容器关闭,销毁Bean(如果需要)

二、Spring IOC容器初始化流程详解

  1. 容器创建与加载配置

Spring容器通过 ApplicationContext 或 BeanFactory 接口来实现,最常见的是 ApplicationContext。创建容器时,容器会从配置文件或注解类中加载Bean定义。
• XML配置方式:通过解析XML文件(如applicationContext.xml),获取Bean的定义。
• 注解方式:通过注解(如@configuration@componentscan)扫描并注册Bean。

容器的常见实现类:
• ClassPathXmlApplicationContext:用于加载XML配置文件。
• AnnotationConfigApplicationContext:用于Java配置类(通过注解)来配置Spring容器。

例如,使用XML配置的初始化方式如下:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

或者使用Java配置类:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

  1. 读取Bean定义(注册Bean)

Spring会读取配置文件或注解扫描的类,并解析其中的Bean定义。每个Bean定义会被注册到BeanDefinitionRegistry中。一个BeanDefinition包括Bean的类名、构造函数、依赖关系、作用域等信息。

例如,XML中的Bean定义:

这段XML表示容器中有一个id为myBean,类型为com.example.MyBean的Bean,并且设置了一个属性someProperty的值为someValue。

容器通过 BeanDefinitionReader 类来读取XML配置并将这些Bean定义注册到BeanDefinition容器中。

  1. 实例化Bean对象

在Spring容器初始化过程中,容器会根据BeanDefinition来实例化Bean。Spring支持懒加载(lazy loading)和即时加载(eager loading)两种方式:
• 懒加载:只有在第一次访问时,才实例化Bean。
• 即时加载:容器启动时即实例化所有单例Bean。

在实例化Bean时,Spring容器会使用反射机制调用无参构造方法来创建对象。对于需要依赖注入的Bean,Spring会在此阶段记录Bean的依赖关系。

MyBean myBean = new MyBean();

  1. 填充Bean的依赖(依赖注入)

在Bean实例化之后,Spring容器会进行依赖注入(DI,Dependency Injection)。Spring支持以下几种注入方式:
• 构造器注入:通过构造函数将依赖项注入Bean。
• Setter方法注入:通过调用Setter方法来注入依赖。
• 字段注入:通过反射直接设置字段。

Spring会根据BeanDefinition中的依赖关系(如、或注解标记)来确定如何注入Bean的依赖。

例如,XML配置的Setter注入:

构造器注入:

public class MyBean {
private Service service;

public MyBean(Service service) {
    this.service = service;
}

}

依赖注入是通过调用Bean的Setter方法或构造方法来实现的。Spring会使用反射机制将依赖注入到Bean实例中。

  1. 执行Bean的初始化方法

当所有的依赖注入完成后,Spring会执行Bean的初始化方法。这些初始化方法可以通过以下方式指定:
@PostConstruct注解:在Bean实例化和依赖注入之后,自动调用初始化方法。
• init-method属性:在XML配置中指定初始化方法。
• InitializingBean接口:实现afterPropertiesSet()方法来自定义初始化逻辑。

例如,使用@PostConstruct注解:

public class MyBean {

@PostConstruct
public void init() {
    System.out.println("Bean has been initialized");
}

}

或者在XML配置中指定:

init-method属性指定的初始化方法将在依赖注入完成之后执行。

  1. 容器准备就绪,提供服务

在所有的Bean实例化、依赖注入和初始化方法执行完毕后,Spring容器就已经准备好提供服务了。此时可以从容器中获取Bean,并开始使用它们。

MyBean myBean = context.getBean("myBean", MyBean.class);

在这个阶段,Spring容器管理的所有Bean都可以通过容器进行访问,并且可以开始参与业务逻辑的处理。

  1. 容器关闭,销毁Bean(如果需要)

如果是单例Bean,当容器关闭时,Spring会销毁这些Bean,并执行销毁方法。销毁方法的定义方式有:
• @PreDestroy注解:标注销毁方法。
• destroy-method属性:在XML配置中指定销毁方法。
• DisposableBean接口:实现destroy()方法来自定义销毁逻辑。

例如,使用@PreDestroy注解:

public class MyBean {

@PreDestroy
public void destroy() {
    System.out.println("Bean is being destroyed");
}

}

或者在XML配置中指定销毁方法:

容器关闭时,会根据这些配置调用销毁方法,释放相关资源。

三、总结

Spring IOC初始化流程是一个复杂且精细的过程,涉及了多个阶段的操作,从容器的创建、配置加载、Bean定义的注册、Bean实例化到依赖注入、初始化、以及容器关闭时的销毁过程。每个阶段都离不开Spring容器内部的机制和设计思想,如反射、依赖注入、Bean生命周期管理等。

理解这些初始化流程和背后的实现原理,对于开发者在实际开发中使用Spring框架至关重要。掌握这些细节,能够帮助我们更好地利用Spring容器进行高效的开发和调试,并能够解决更复杂的开发场景中的问题。

四、补充重要知识点
1.首先在初始化容器的时候,调用父类容器AbstractApplicationContext的构造方法(super(parent)方法)为容器设置好Bean资源加载器
通过AbstractApplicationContext默认构造函数初始化容器id, name, 状态 以及 资源解析器
通过AbstractApplicationContext的setParent(parent)方法将父容器的Environment合并到当前容器
在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContet执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位

2.初始化的主题流程是从refresh方法开始的,refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器
在refresh方法中,有很多细节,首先 synchronized (this.startupShutdownMonitor)
• 使用 synchronized 锁定 startupShutdownMonitor 对象,以确保在容器的刷新过程中不会有并发调用。
• startupShutdownMonitor 是一个同步对象,用于控制在 Spring 容器的启动和关闭过程中线程的安全性。

这里面有对应的模版方法和钩子方法:
模板方法 是指在父类中定义一个算法的框架,具体的实现由子类来完成。它允许子类在不改变算法结构的情况下实现具体的步骤。

在 Spring 中,refresh() 方法本身就是一个 模板方法,它定义了容器初始化的整体流程,但其中的部分步骤是由子类实现的。refresh() 方法中的调用顺序固定,而一些具体的实现留给子类来执行。

在这段代码中,refresh() 方法按照固定的步骤进行容器的初始化,但一些具体的步骤是由子类(如 AnnotationConfigApplicationContext 或其他具体的 ApplicationContext 实现)来定义和实现。

钩子方法(Hook Method)

钩子方法 是指在父类中定义的方法,允许子类选择性地重写或执行。钩子方法通常是空的或提供默认行为,但子类可以根据需要覆盖它们来扩展父类的功能。

在这段代码中,有几个方法是典型的钩子方法,它们允许子类根据具体需求进行扩展或修改行为:
• onRefresh():
这是一个钩子方法,它在容器刷新时被调用,但默认实现为空。具体的实现逻辑通常由子类提供,用来扩展容器刷新过程中的行为。例如,AnnotationConfigApplicationContext 就会覆盖这个方法来执行特定的初始化逻辑。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions