.spring5源码 -- ioc加载的整体流程

本文深入分析了Spring5中IOC容器的加载流程,从main方法入口开始,详细介绍了BeanFactory如何加载、AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner的作用,以及refresh()方法在bean实例化过程中的关键步骤,包括invokeBeanFactoryPostProcessors和finishBeanFactoryInitialization。

之前我们知道了spring ioc的加载过程, 具体如下图. 下面我们就来对照下图, 看看ioc加载的源代码.

 下面在用装修类比, 看看个个组件都是怎么工作的.

 

接下来是源码分析的整体结构图. 对照上面的思路梳理出来的

 

一、源码分析的入口 

通常,我们的入口都是从main方法进入. 这里我们也来定义一个main方法

复制代码

public class MainStarter {
    public static void main(String[] args) {
        // 第一步: 通过AnnotationConfigApplicationContext读取一个配置类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainStarter.class);
        context.scan("package name");
        Car car = (Car) context.getBean("car");
        System.out.println(car.getName());
        context.close();
    }
}

复制代码

 

顺便再来看看还有哪些相关的类

复制代码

/**
 * 这是一个配置类,
 * 在配置类里面定义了扫描的包路径com.lxl.www.iocbeanlifecicle
 * 这是会将这个包下配置了注解的类扫描到ioc容器里面,成为一个成熟的bean
 */
@Configuration
@ComponentScan(basePackages = {"com.lxl.www.iocbeanlifecicle"})
public class MainConfig {
}

复制代码

这个类有一个注解@Configuration, 这样这个类会被扫描成bean

还有一个注解@ComponentScan(backPackage = {"com.lxl.www.iocbeanlifecicle"}) 他表示, 请扫描com.lxl.www.iocbeanlifecicle包下所有的类.

com.lxl.www.iocbeanlifecicle 这个包下还有哪些类呢? 我们来看看项目结构

 

 

 这是这个包下完整的项目结构. 

下面会逐渐说明, 每个类的用途

 

二. 最重要的类BeanFactory

我们知道在将一个class加载为bean的过程中BeanFactory是最最重要的, 那么他是何时被加载的呢?

我们来跟踪一下带有一个参数的构造方法AnnotationConfigApplicationContext

复制代码

    public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
        // 进入构造函数, 首先调用自身的构造方法this();
        // 调用自身的构造方法之前, 要先调用父类的构造方法
        this();
        // retister配置注册类
        register(componentClasses);
        // ioc容器shua新接口--非常重要
        refresh();
    }

复制代码

这就是AnnotationConfigApplicationContext初始化的时候做的三件事

第一件事:  this();  //调用自身的无参构造方法. 同时调用父类的构造方法

第二件事: register(componentClasses); // 调用注册器, 这里会加载两个BeanDefinitionReader和BeanDefinitionScanner. 这两位的角色是什么呢? 可以回忆一下之前的框架图

第三件事: refresh();  // 这是ioc容器刷新, 非常重要. 无论是spring boot还是spring mvc都有这个方法. 这个方法包含了整个spring ioc加载的全生命流程. 也是我们要重点学习的方法

 

下面来看看BeanFactory是何时被加载进来的呢?

在初始化方法的时候调用了自身的无参构造函数, 在调用自身无参构造函数的时候, 同时会调用父类的无参构造函数. 

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
  
  
  ......
} 

父类是GenericApplicationContext, 其无参构造函数就做了一件事

复制代码

   public GenericApplicationContext() {
        // 构造了一个BeanFactory.
        // 在调用GenericApplicationContext父类构造函数, 为ApplicationContext spring上下文对象初始化beanFactory
        // 为什么初始化的是DefaultListableBeanFactory呢?
        // 我们在看BeanFactory接口的时候发现DefaultListableBeanFactory是最底层的实现, 功能是最全的.
        // 查看
        this.beanFactory = new DefaultListableBeanFactory();
    }    

复制代码

初始化DefaultListableBeanFactory. 

问题: BeanFactory有很多, 为什么初始化的时候选择DefaultListableBeanFactory呢?

我们来看看DefaultListableBeanFactory的结构. 快捷键option + command + u --> Java Class Diagrams

 

 

 通过观察, 我们发现, DefaultListableBeanFactory实现了各种各样的BeanFactory接口, 同时还是先了BeanDefinitionRegistry接口. 

也就是说, DefaultListableBeanFactory不仅仅有BeanFactory的能力, 同时还有BeanDefinitionRegistry的能力. 它的功能是最全的.

所以, 我们使用的是一个功能非常强大的类Bean工厂类. 

 

AnnotationConfigApplicationContext继承了GenericApplicationContext,

而 GenericApplicationContext 实现了AnnotationConfigRegistry接口.

所以AnnotationConfigApplicationContext有AnnotationConfigRegistry的能力.

 

三. bean定义读取器AnnotatedBeanDefinitionReader

接着上面, 第一步调用的是this(). 也就是AnnotationConfigApplicationContext的无参构造函数. 在这个无参构造函数里一共做了两件事情

复制代码

    public AnnotationConfigApplicationContext() {
        /**
         * 创建了一个Bean定义的读取器.
         * 完成了spring内部BeanDefinition的注册(主要是后置处理器)
         * 读取了很多spring自定义的配置(主要是后置处理器). 这些类都是spring 的原始类.
         */
        this.reader = new AnnotatedBeanDefinitionReader(this);

        /**
         * 创建BeanDefinition扫描器
         * 可以用来扫描包或者类, 进而转换为bd
         *
         * Spring默认的扫描包不是这个scanner对象
         * 而是自己new的一个ClassPathBeanDefinitionScanner
         * Spring在执行工程后置处理器ConfigurationClassPostProcessor时, 去扫描包时会new一个ClassPathBeanDefinitionScanner
         *
         * 这里的scanner仅仅是为了程序员可以手动调用AnnotationConfigApplicationContext对象的scan方法
         * 通过调用context.scan("package name");扫描处理配置类
         * 扫描
         */
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

复制代码

1. 初始化AnnotatedBeanDefinitionReader.

2. 初始化ClassPathBeanDefinitionScanner

我们先来看看AnnotatedBeanDefinitionReader

 

 

 在这里的描述中, 我们知道BeanDefinitionReader是要去扫描配置或者注解, 如果理解为销售的话, 就是扫描楼盘. 这里面就有我们的潜在用户. 也就是我们需要将其转换为bean的对象. 

那么初始化的时候,AnnotatedBeanDefinitionReader做了什么呢?

重点看这句

复制代码

    public AnnotatedBeanDefinitionReader(BeanDefinitio
<think>嗯,用户需要了解Kettle 7.1.0.0.13升级XStream和Spring Core后的兼容性测试案例。从引用内容看,用户之前可能遇到过Kettle启动问题、数据库连接问题,特别是引用[4]提到Linux下需要安装xulrunner的依赖问题,说明用户对运行环境问题比较敏感。 用户这次关注的是特定版本升级的兼容性测试,这属于比较专业的运维场景。考虑到Kettle作为ETL工具在企业数据流程中的关键作用,用户可能是系统管理员或数据工程师,负责维护Kettle的升级部署。需要特别注意引用[2]提到的JVM参数问题,升级后内存管理可能有变化。 关于XStream的升级测试点,重点要覆盖Kettle的元数据序列化功能。引用[3]提到Kettle连接Oracle时需要驱动包,说明数据库连接配置的序列化很重要。测试案例应该包括作业和转换的保存/加载,特别是包含数据库连接的复杂对象。 Spring Core升级跨度较大(4.3.2到5.x),需要关注IoC容器行为变化。引用[5]提到源码结构,可能涉及插件开发场景,所以测试案例要包含插件加载机制。另外引用[1]强调Java环境配置,Spring 5要求至少Java 8,这点必须验证。 用户没有明确测试环境要求,但引用[4]显示用户可能在Linux环境工作,所以测试案例要区分操作系统。考虑到Kettle常与Hadoop生态集成(引用[2]提到"大数据分析"),Hadoop相关插件也要纳入测试范围。 测试策略上,应该分三层:基础功能(核心文件操作)、扩展功能(数据库/NoSQL连接)、高级特性(集群执行)。特别要设计包含特殊字符的转换文件,验证XStream的XML安全机制是否正常。</think>以下是为 Kettle 7.1.0.0.13 升级 **XStream 1.4.9 → 1.4.20** 和 **Spring Core 4.3.2 → 5.x** 设计的兼容性测试案例,涵盖关键功能点: --- ### **一、核心功能兼容性测试** 1. **元数据序列化/反序列化** - 测试步骤: - 在旧版本(7.1.0.0.12)创建含复杂参数的作业/转换(如数据库连接、变量、循环) - 在新版本加载并执行,验证对象解析完整性 - 预期结果: - 所有作业/转换可正确加载,无 `XStream` 反序列化错误(如 `com.thoughtworks.xstream.security.ForbiddenClassException`) - 变量传递、依赖关系保持正确 2. **插件加载机制** - 测试步骤: - 安装常用插件(如 `Big Data` 插件、`JSON Input` 等) - 验证插件初始化日志,检查 `Spring` 上下文注入是否正常 - 预期结果: - 插件无 `ClassNotFoundException` 或 `NoSuchMethodError`(常见于 `Spring` 版本不兼容) - 插件配置界面可正常打开 --- ### **二、数据库连接与操作测试** 1. **连接池管理** - 测试步骤: - 配置 Oracle/MySQL 连接池(引用[3]) - 执行高频查询(`SELECT`)与写入(`INSERT/UPDATE`) - 预期结果: - 连接池无 `Spring` 事务管理错误(如 `TransactionSuspensionNotSupportedException`) - 无 `XStream` 导致的数据库元数据序列化失败 2. **跨数据库事务** - 测试步骤: - 创建跨 Oracle + MySQL 的事务作业 - 模拟事务回滚场景 - 预期结果: - `Spring` 事务注解(如 `@Transactional`)生效,回滚逻辑正确执行 --- ### **三、安全增强验证** 1. **XStream 安全策略** - 测试步骤: - 加载含潜在危险类型的转换文件(如 `java.io.File`) - 预期结果: - 触发 `XStream 1.4.20` 安全限制(`ForbiddenClassException`),阻断恶意代码注入 2. **XML 外部实体(XXE)防护** - 测试步骤: - 在转换中注入恶意 XML 实体(如 `<!ENTITY xxe SYSTEM "file:///etc/passwd">`) - 预期结果: - `XStream` 默认禁用外部实体解析,无敏感信息泄露 --- ### **四、性能与稳定性测试** 1. **高负载场景** - 测试步骤: - 执行 10K+ 记录的 `表输入→转换→表输出` 流水线 - 监控 JVM 内存(引用[2]的 JVM 参数兼容性) - 预期结果: - 无 `OutOfMemoryError` 或 `Spring` 线程池崩溃 - CPU/内存占用符合预期 2. **集群执行** - 测试步骤: - 在 Carte 集群中分发作业 - 模拟节点故障转移 - 预期结果: - `Spring` 管理的远程调用(RMI)保持稳定 - 故障节点任务自动重分配 --- ### **五、回归测试重点** | 功能模块 | 测试用例 | 关联风险点 | |-------------------|------------------------------|----------------------------| | **文件读写** | 读写 CSV/Excel/XML 文件 | XStream 解析异常 | | **日志系统** | 检查 `Log4j` 日志输出 | Spring 日志桥接冲突 | | **REST API 调用** | 使用 `HTTP Client` 步骤 | Spring 5 的 WebClient 兼容性 | | **变量传递** | 全局/局部变量跨作业传递 | Spring 作用域机制变更影响 | --- ### **已知风险与规避建议** 1. **XStream 别名兼容性** - 风险:旧版定义的 `xstream-alias` 可能失效 - 规避:在 `kettle.properties` 中显式配置别名: ```ini org.pentaho.xstream.aliases=com.example.MyClass ``` 2. **Spring 包路径变更** - 风险:`spring-beans-4.3.x.jar` → `spring-beans-5.x.jar` 类路径变化 - 验证:使用 `-verbose:class` 启动参数检查类加载顺序 > **引用说明**: > - XStream 安全策略升级见 [CVE-2020-26217](https://nvd.nist.gov/vuln/detail/CVE-2020-26217)[^1] > - Spring 5 迁移指南参考 [官方文档](https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/migration.html)[^2] --- ### 相关问题 1. Kettle 升级后如何验证插件兼容性? 2. Spring 5 在 Kettle 中的主要变化有哪些? 3. 如何解决 XStream 反序列化时的 `ForbiddenClassException` 错误? [^1]: XStream 安全公告 (CVE-2020-26217) [^2]: Spring Framework 5.x 迁移指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值