第一章:走进 IOC 容器——设计思想与整体架构

第一章:走进 IOC 容器——设计思想与整体架构

本章将带你理解 IOC 容器的核心思想,建立对 Jfire 框架的整体认知,并通过源码分析揭示一个轻量级 IOC 容器的设计精髓。本专栏的项目地址在:https://github.com/linbin-eric/jfire

1.1 什么是 IOC 与 DI

在传统的 Java 开发中,当一个对象需要依赖另一个对象时,我们通常会这样写:

public class UserService {
    private UserDao userDao = new UserDaoImpl();  // 主动创建依赖

    public void saveUser(User user) {
        userDao.save(user);
    }
}

这种方式存在几个明显的问题:

  1. 耦合度高UserServiceUserDaoImpl 紧密耦合,如果要更换实现类,必须修改源代码
  2. 难以测试:无法轻松替换为 Mock 对象进行单元测试
  3. 生命周期管理困难:对象的创建、销毁完全由使用方控制,难以实现单例等复用模式

1.1.1 控制反转(IoC)

控制反转(Inversion of Control) 是一种设计思想,它将对象的创建和依赖关系的管理从应用代码中转移到外部容器中。

所谓"反转",指的是:

对比项 传统方式 IoC 方式
对象创建 应用代码主动 new 容器负责创建
依赖获取 主动查找或创建 容器注入
控制权 在应用代码中 在容器中

1.1.2 依赖注入(DI)

依赖注入(Dependency Injection) 是实现 IoC 的一种具体方式。容器在创建对象时,自动将其依赖的对象注入进去。

public class UserService {
    @Resource  // 声明需要注入
    private UserDao userDao;  // 不再主动创建,由容器注入

    public void saveUser(User user) {
        userDao.save(user);
    }
}

依赖注入的三种常见方式:

  1. 字段注入:通过反射直接设置字段值(Jfire 采用的方式)
  2. 构造器注入:通过构造函数传入依赖
  3. Setter 注入:通过 setter 方法传入依赖

1.1.3 IoC 与 DI 的关系

简单来说:

  • IoC 是一种设计思想:强调控制权的转移
  • DI 是 IoC 的实现手段:通过注入的方式实现控制反转

IoC 容器 = IoC 思想 + DI 实现 + Bean 生命周期管理

1.2 为什么要手写 IOC

你可能会问:Spring 已经那么成熟了,为什么还要手写 IOC 容器?

1.2.1 理解框架本质

使用 Spring 多年,你是否真正理解过:

  • Bean 是如何被创建的?
  • 依赖注入是如何实现的?
  • AOP 代理是怎么生成的?
  • 事务是如何被管理的?

手写 IOC 容器,能让你从"会用"变成"懂原理",这是架构师与普通开发者的分水岭。

1.2.2 面试加分项

大厂面试常见问题:

  • "请说说 Spring IoC 的实现原理"
  • "循环依赖是如何解决的?"
  • "AOP 动态代理有几种方式?"

只有深入源码,才能给出有深度的回答。

1.2.3 定制化需求

在某些场景下,Spring 显得过于庞大:

  • 嵌入式设备:资源受限
  • 快速启动:Serverless 场景
  • 特殊定制:需要深度改造容器行为

Jfire 作为一个轻量级框架,非常适合作为学习和定制的基础。

1.2.4 学习设计模式

一个 IOC 容器是设计模式的集大成者:

  • 工厂模式、策略模式、责任链模式
  • 模板方法、观察者模式、装饰器模式
  • 单例模式、代理模式...

通过手写 IOC,你将深刻理解这些模式的实际应用。

1.3 Jfire 整体架构设计

Jfire 是一个轻量级的 Java IOC/AOP 容器,其设计目标是:

  • 轻量:核心代码精简,依赖少
  • 高效:启动快,运行时开销小
  • 易学:代码结构清晰,适合学习

1.3.1 项目基本信息

pom.xml 可以看出 Jfire 的基本配置:

<groupId>cc.jfire</groupId>
<artifactId>jfire</artifactId>
<version>1.0.1-SNAPSHOT</version>

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

核心依赖

依赖 版本 作用
baseutil 1.0 基础工具库,包含 @Resource 注解、反射工具、字节码生成
jfireEL 1.0 表达式语言,用于缓存键计算、条件表达式
lombok 1.18.30 代码简化

1.3.2 核心模块划分

Jfire 的源码结构如下:

src/main/java/cc/jfire/jfire/
├── core/                           # 核心模块
│   ├── ApplicationContext.java     # 容器接口
│   ├── DefaultApplicationContext.java # 默认实现
│   ├── AwareContextInited.java     # 初始化感知接口
│   ├── aop/                        # AOP 增强模块
│   │   ├── EnhanceManager.java     # 增强管理器接口
│   │   ├── ProceedPoint.java       # 连接点接口
│   │   ├── impl/                   # 增强器实现
│   │   └── notated/                # AOP 注解
│   ├── bean/                       # Bean 定义模块
│   │   ├── BeanDefinition.java     # Bean 定义接口
│   │   ├── BeanRegisterInfo.java   # Bean 注册信息接口
│   │   └── impl/                   # 具体实现
│   ├── beanfactory/                # Bean 工厂模块
│   │   ├── BeanFactory.java        # 工厂接口
│   │   └── impl/                   # 工厂实现
│   ├── inject/                     # 依赖注入模块
│   │   ├── InjectHandler.java      # 注入处理器接口
│   │   └── impl/                   # 注入实现
│   └── prepare/                    # 容器初始化模块
│       ├── ContextPrepare.java     # 准备器接口
│       ├── processor/              # 处理器实现
│       └── annotation/             # 配置注解
├── exception/                      # 异常定义
└── util/                           # 工具类

1.3.3 模块职责说明

让我们逐一理解每个模块的职责:

1. 容器核心(ApplicationContext)

容器的入口和门面,负责:

  • 启动和初始化容器
  • 提供 Bean 获取接口
  • 管理 Bean 注册信息

2. Bean 定义模块(bean)

管理 Bean 的元信息和实例化逻辑:

  • BeanDefinition:定义如何获取 Bean 实例
  • BeanRegisterInfo:存储 Bean 的注册信息和增强器

3. Bean 工厂模块(beanfactory)

负责创建 Bean 的原始实例:

  • ClassBeanFactory:通过构造器创建
  • MethodBeanFactory:通过 @Bean 方法创建

4. 依赖注入模块(inject)

负责处理 Bean 之间的依赖关系:

  • 字段注入、集合注入、延迟注入等多种策略

5. AOP 增强模块(aop)

负责 Bean 的动态增强:

  • 前置、后置、环绕等增强方式
  • 事务增强、缓存增强

6. 容器初始化模块(prepare)

负责容器启动时的各种准备工作:

  • 组件扫描、配置加载、条件判断等

1.3.4 模块依赖关系

                    ┌─────────────────────────────────┐
                    │      ApplicationContext         │
                    │         (容器入口)               │
                    └─────────────────────────────────┘
                                   │
           ┌───────────────────────┼───────────────────────┐
           │                       │                       │
           ▼                       ▼                       ▼
    ┌─────────────┐         ┌─────────────┐         ┌─────────────┐
    │   prepare   │         │    bean     │         │     aop     │
    │  (初始化)    │         │  (Bean管理)  │         │   (增强)    │
    └─────────────┘         └─────────────┘         └─────────────┘
                                   │                       │
                                   ▼                       │
                            ┌─────────────┐               │
                            │ beanfactory │◄──────────────┘
                            │  (对象创建)  │
                            └─────────────┘
                                   │
                                   ▼
                            ┌─────────────┐
                            │   inject    │
                            │  (依赖注入)  │
                            └─────────────┘

1.4 核心设计模式概览

Jfire 中运用了多种经典设计模式,这些模式的巧妙运用是框架优雅的关键。

1.4.1 工厂模式(Factory Pattern)

应用场景:Bean 的创建

// BeanFactory 接口定义
public interface BeanFactory {
    <E> E getUnEnhanceyInstance(BeanDefinition beanDefinition);
}

不同的工厂实现负责不同的创建方式:

  • ClassBeanFactory:通过反射调用构造器
  • MethodBeanFactory:通过调用 @Bean 方法

好处:将对象的创建逻辑封装起来,调用方无需关心具体创建过程。

1.4.2 策略模式(Strategy Pattern)

应用场景:依赖注入

// InjectHandler 接口定义注入策略
public interface InjectHandler {
    void init(Field field, ApplicationContext context);
    void inject(Object instance);
}

根据字段类型选择不同的注入策略:

  • 普通类型 → InstacenInject
  • 接口类型 → AbstractInject
  • 集合类型 → CollectionInject
  • Map 类型 → MapInject
  • BeanHolder → BeanHolderInject

好处:策略可以独立变化,新增注入方式不影响现有代码。

1.4.3 责任链模式(Chain of Responsibility)

应用场景:增强器链、准备器链

// EnhanceManager 增强管理器
public interface EnhanceManager {
    int order();  // 定义执行顺序
    void enhance(ClassModel classModel, Class<?> type,
                ApplicationContext context, String hostFieldName);
}

多个增强器按 order() 排序后依次执行:

TransactionEnhanceManager (order=10)
        ↓
CacheEnhanceManager (order=30)
        ↓
AopEnhanceManager (order=100)

好处:增强器之间解耦,可以灵活组合。

1.4.4 模板方法模式(Template Method)

应用场景:Bean 实例构建流程

PrototypeBeanDefinition 中定义了构建 Bean 的模板流程:

1. 检测循环依赖
2. 创建原始实例(子类可扩展)
3. 执行依赖注入
4. 调用初始化方法
5. 执行 AOP 增强

好处:固定算法骨架,具体步骤可定制。

1.4.5 观察者模式(Observer Pattern)

应用场景:容器初始化通知

// AwareContextInited 接口
public interface AwareContextInited {
    void aware(ApplicationContext applicationContext);
    default int order() { return 0; }
}

实现该接口的 Bean 会在容器初始化完成后收到通知。

好处:解耦容器初始化和业务逻辑。

1.4.6 装饰器模式(Decorator Pattern)

应用场景:AOP 增强

通过字节码生成技术,为原始类动态生成增强子类:

原始类: UserService
        ↓
增强类: UserService$$Jfire$$1 extends UserService
        - 包含拦截逻辑
        - 调用原始方法

好处:不修改原始类代码,动态添加功能。

1.4.7 单例模式(Singleton Pattern)

应用场景:单例 Bean、工厂实例

// ClassBeanFactory 使用单例
public class ClassBeanFactory implements BeanFactory {
    public static final ClassBeanFactory INSTANCE = new ClassBeanFactory();
    private ClassBeanFactory() {}
}

好处:节省资源,保证全局唯一。

1.4.8 设计模式总结

设计模式 应用位置 核心作用
工厂模式 BeanFactory 封装对象创建
策略模式 InjectHandler 多种注入方式
责任链模式 EnhanceManager、ContextPrepare 链式处理
模板方法 BeanDefinition 固定构建流程
观察者模式 AwareContextInited 事件通知
装饰器模式 AOP 增强 动态增强
单例模式 SingletonBeanDefinition 对象复用

1.5 快速上手:第一个 Jfire 应用

理论讲得再多,不如动手实践。让我们通过源码来理解 Jfire 的启动过程。

1.5.1 启动方式

Jfire 提供了两种启动方式,定义在 ApplicationContext 接口中:

// 源码:ApplicationContext.java

/**
 * 启动应用上下文
 * @param bootClass 启动类(必须标注 @Configuration)
 * @return 应用上下文实例
 */
static ApplicationContext boot(Class<?> bootClass) {
    return new DefaultApplicationContext(bootClass);
}

/**
 * 启动应用上下文(无启动类,编程式)
 * @return 应用上下文实例
 */
static ApplicationContext boot() {
    return new DefaultApplicationContext();
}

1.5.2 配置类启动

这是最常用的启动方式:

@Configuration  // 标记为配置类
@ComponentScan("com.example.app")  // 扫描组件
@PropertyPath("application.yml")   // 加载配置
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

// 启动容器
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = ApplicationContext.boot(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.doSomething();
    }
}

1.5.3 启动流程源码解析

让我们深入 DefaultApplicationContext 的构造函数:

// 源码:DefaultApplicationContext.java

public DefaultApplicationContext(Class<?> bootStarpClass) {
    // 1. 校验启动类必须有 @Configuration 注解
    if (AnnotationContext.isAnnotationPresent(Configuration.class, bootStarpClass) == false) {
        throw new IllegalArgumentException("启动的配置类,一定要有Configuration注解");
    }
    // 2. 注册启动类
    register(bootStarpClass);
}

当调用 getBean() 时,会触发容器的完整初始化:

// 源码:DefaultApplicationContext.java

@Override
public <E> E getBean(Class<E> ckass) {
    makeAvailable();  // 确保容器已初始化
    // ... 获取 Bean 逻辑
}

@Override
public void makeAvailable() {
    if (!freshed) {
        refresh();  // 执行容器刷新
    }
}

1.5.4 容器刷新流程(核心!)

refresh() 方法是整个容器的核心,让我们逐行分析:

// 源码:DefaultApplicationContext.java

private void refresh() {
    freshed = true;

    // 步骤1:注册内部类(框架自身的组件)
    registerInternalClass();

    // 步骤2:处理 @Import 注解,导入其他配置类
    processImports();

    // 步骤3:执行准备器链(可能循环执行)
    if (processContextPrepare() == FoundNewContextPrepare.YES) {
        log.debug("执行ContextPrepare接口,发现需要刷新容器");
        refresh();  // 递归刷新
        return;
    }

    // 步骤4:执行 AOP 增强扫描
    log.debug("准备获取所有的EnhanceManager,执行增强扫描");
    enhanceScan();

    // 步骤5:完成所有 Bean 定义
    beanRegisterInfoMap.values().stream()
        .filter(info -> info instanceof DefaultBeanRegisterInfo)
        .forEach(info -> ((DefaultBeanRegisterInfo) info).complete());

    // 步骤6:触发容器初始化完成回调
    log.debug("准备获取所有的AwareContextInited接口实现,执行aware方法");
    awareContextInit();
}

让我们用流程图来展示:

                        ┌─────────────────┐
                        │    refresh()    │
                        └────────┬────────┘
                                 │
                                 ▼
                    ┌────────────────────────┐
                    │  registerInternalClass │
                    │  注册框架内部组件        │
                    └────────────┬───────────┘
                                 │
                                 ▼
                    ┌────────────────────────┐
                    │    processImports      │
                    │    处理 @Import 导入    │
                    └────────────┬───────────┘
                                 │
                                 ▼
                    ┌────────────────────────┐
                    │  processContextPrepare │◄──────┐
                    │    执行准备器链          │       │
                    └────────────┬───────────┘       │
                                 │                   │
                          发现新准备器?              │
                           ┌────┴────┐              │
                           │         │              │
                          YES       NO              │
                           │         │              │
                           │         ▼              │
                           │  ┌──────────────┐      │
                           │  │ enhanceScan  │      │
                           │  │ AOP 增强扫描  │      │
                           │  └──────┬───────┘      │
                           │         │              │
                           │         ▼              │
                           │  ┌──────────────┐      │
                           │  │  complete()  │      │
                           │  │ 完成Bean定义  │      │
                           │  └──────┬───────┘      │
                           │         │              │
                           │         ▼              │
                           │  ┌──────────────┐      │
                           │  │awareContextInit│    │
                           │  │ 初始化回调    │      │
                           │  └──────┬───────┘      │
                           │         │              │
                           └─────────┼──────────────┘
                                     │
                                     ▼
                              ┌─────────────┐
                              │   完成启动   │
                              └─────────────┘

1.5.5 内部组件注册

registerInternalClass() 注册了框架必需的内部组件:

// 源码:DefaultApplicationContext.java

private void registerInternalClass() {
    // 1. 注册容器自身(方便其他 Bean 注入 ApplicationContext)
    registerBeanRegisterInfo(new OutterBeanRegisterInfo(this, "applicationContext"));

    // 2. 注册三个内置增强管理器
    register(AopEnhanceManager.class);        // 普通 AOP
    register(TransactionEnhanceManager.class); // 事务增强
    register(CacheEnhanceManager.class);       // 缓存增强

    // 3. 注册配置处理器
    register(ConfigurationProcessor.class);
}

1.5.6 获取 Bean 的方式

Jfire 提供了多种获取 Bean 的方式:

// 源码:ApplicationContext.java

// 1. 按类型获取(最常用)
<E> E getBean(Class<E> ckass);

// 2. 按名称获取
<E> E getBean(String beanName);

// 3. 获取所有匹配的 Bean
<E> Collection<E> getBeans(Class<E> ckass);

实现逻辑:

// 源码:DefaultApplicationContext.java

@Override
public <E> E getBean(Class<E> ckass) {
    makeAvailable();  // 确保容器初始化

    // 从注册表中查找匹配的 Bean
    Optional<BeanDefinition> any = beanRegisterInfoMap.values().stream()
        .filter(info -> ckass.isAssignableFrom(info.getType()))
        .map(info -> info.get())
        .findAny();

    if (any.isPresent()) {
        return (E) any.get().getBean();
    } else {
        throw new BeanDefinitionCanNotFindException(ckass);
    }
}

1.5.7 完整示例

下面是一个完整的 Jfire 应用示例:

// 1. 定义服务接口
public interface UserService {
    void saveUser(String name);
}

// 2. 实现服务
@Resource  // 标记为 Bean
public class UserServiceImpl implements UserService {

    @Resource  // 注入依赖
    private UserDao userDao;

    @Override
    public void saveUser(String name) {
        userDao.save(new User(name));
    }
}

// 3. 定义 DAO
@Resource
public class UserDaoImpl implements UserDao {
    @Override
    public void save(User user) {
        System.out.println("保存用户: " + user.getName());
    }
}

// 4. 配置类
@Configuration
@ComponentScan("com.example")
public class AppConfig {
}

// 5. 启动应用
public class Application {
    public static void main(String[] args) {
        // 启动容器
        ApplicationContext context = ApplicationContext.boot(AppConfig.class);

        // 获取 Bean
        UserService userService = context.getBean(UserService.class);

        // 使用
        userService.saveUser("张三");
    }
}

运行结果:

保存用户: 张三

1.6 Jfire vs Spring:设计理念对比

对比维度 Jfire Spring
定位 轻量级学习框架 企业级生产框架
代码量 数千行 数十万行
启动速度 毫秒级 秒级
依赖数量 3个 数十个
Bean 作用域 单例、原型 单例、原型、Request、Session 等
AOP 实现 字节码生成 JDK 动态代理/CGLIB
配置方式 注解为主 注解 + XML + JavaConfig
生态系统 有限 极其丰富

Jfire 的优势

  1. 代码精简:核心原理一目了然
  2. 学习成本低:适合入门学习 IOC/AOP
  3. 启动极快:适合轻量级场景

Jfire 的局限

  1. 功能有限:不支持复杂的企业级特性
  2. 生态缺乏:没有成熟的周边框架
  3. 社区小:遇到问题难以找到解决方案

1.7 本章小结

本章我们学习了:

  1. IOC 与 DI 的概念

    • IOC 是一种设计思想,强调控制权的转移
    • DI 是实现 IOC 的具体方式
  2. Jfire 的整体架构

    • 容器核心、Bean 管理、工厂、注入、AOP、准备器六大模块
    • 模块之间职责清晰、依赖合理
  3. 核心设计模式

    • 工厂、策略、责任链、模板方法、观察者、装饰器、单例
  4. 容器启动流程

    • refresh() 方法的六个步骤
    • 从注册到初始化的完整链路
  5. 快速上手实践

    • 配置类、组件扫描、Bean 获取

下一章预告:我们将深入 Bean 定义体系,探索 BeanDefinitionBeanRegisterInfo 的设计,理解单例与原型作用域的实现原理,以及 Jfire 是如何检测和处理循环依赖的。

思考题

  1. 为什么 refresh() 方法中 processContextPrepare() 可能会导致递归调用?这样设计的目的是什么?

  2. registerInternalClass() 为什么要先注册 ApplicationContext 自身?

  3. 如果让你设计一个 IOC 容器,你会选择哪些核心接口?

核心源码清单

文件 路径 核心内容
ApplicationContext.java core/ 容器接口定义
DefaultApplicationContext.java core/ 容器默认实现、refresh() 流程
BeanDefinition.java core/bean/ Bean 定义接口
BeanRegisterInfo.java core/bean/ Bean 注册信息接口
BeanFactory.java core/beanfactory/ Bean 工厂接口
InjectHandler.java core/inject/ 注入处理器接口
EnhanceManager.java core/aop/ 增强管理器接口
ContextPrepare.java core/prepare/ 准备器接口
AwareContextInited.java core/ 初始化感知接口
Configuration.java core/prepare/annotation/ 配置类注解
Java 进阶提升,手写系列 文章被收录于专栏

专栏以轻量级 Java 框架 Jfire 为蓝本,带你从零手写一个完整的 IOC 容器。专栏共 10 章,涵盖 IOC 容器核心原理:Bean 定义与生命周期、Bean 工厂设计、五种依赖注入策略、循环依赖解决方案;深入 AOP 实现:五种增强方式、字节码动态生成技术;以及企业级特性:声明式事务管理(四种传播级别)、声明式缓存框架、条件注解与自动配置机制。

全部评论

相关推荐

11-13 20:16
已编辑
厦门理工学院 软件测试
专业嗎喽:硕佬,把学校背景放后面几段,学校背景双非还学院,让人看了就不想往下看。 把实习经历和个人奖项放前面,用数字化简述自己实习的成果和掌握的技能,比如负责项目一次通过率90%,曾4次发现项目潜在问题风险为公司减少损失等等
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务