2025年11月23日

[手写 MiniSpring 框架教程] 第 06 讲:Bean 作用域

📖 本讲目标

  • 理解 Bean 作用域的概念
  • 实现 Singleton(单例)作用域
  • 实现 Prototype(原型)作用域
  • 实现 @Scope 注解
  • 理解不同作用域的使用场景

🎯 Bean 作用域概述

什么是 Bean 作用域?

Bean 作用域(Scope) 定义了 Bean 实例在容器中的生命周期和可见性。不同的作用域决定了:

  1. Bean 实例的数量:容器中创建多少个实例
  2. Bean 的生命周期:实例何时创建、何时销毁
  3. Bean 的共享范围:哪些地方可以访问这个实例

Spring 框架的作用域

Spring 框架支持以下作用域:

  1. singleton:单例模式(默认)
  2. prototype:原型模式
  3. request:每个 HTTP 请求一个实例(Web 环境)
  4. session:每个 HTTP 会话一个实例(Web 环境)
  5. application:每个 ServletContext 一个实例(Web 环境)

本教程只实现 singletonprototype 两种作用域。

🏷️ @Scope 注解实现

注解定义

创建 src/main/java/com/minispring/context/annotation/Scope.java

package com.minispring.context.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Scope 注解用于指定 Bean 的作用域
 * 支持 singleton(单例)和 prototype(原型)两种作用域
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    /**
     * 作用域类型,支持以下值:
     * - "singleton":单例模式(默认),容器只创建一个实例
     * - "prototype":原型模式,每次获取 Bean 都创建新实例
     */
    String value() default "singleton";

    /**
     * 作用域常量定义
     */
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
}

使用示例

@Component
@Scope("singleton")  // 单例模式(默认)
public class UserService {
    // ...
}

@Component
@Scope("prototype")  // 原型模式
public class OrderService {
    // ...
}

🔄 Singleton 单例模式

单例模式特点

  1. 唯一实例:容器中只创建一个实例
  2. 共享使用:所有地方获取的都是同一个实例
  3. 生命周期:与容器生命周期相同
  4. 线程安全:需要自己保证线程安全

实现机制

单例 Bean 使用缓存机制实现:

/**
 * 单例 Bean 缓存池(一级缓存)
 * Key: beanName, Value: Bean 实例
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

/**
 * 获取单例 Bean(从缓存或创建)
 */
private Object getSingleton(String beanName, BeanDefinition beanDefinition) throws Exception {
    synchronized (singletonObjects) {
        // 1. 先从缓存中获取
        Object singletonObject = singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 2. 创建 Bean 实例
        singletonObject = createBean(beanName, beanDefinition);

        // 3. 放入缓存
        singletonObjects.put(beanName, singletonObject);

        System.out.println("单例 Bean 已缓存: " + beanName);

        return singletonObject;
    }
}

缓存策略

  1. 一级缓存(singletonObjects):存储完整的单例 Bean
  2. 线程安全:使用 synchronized 保证并发安全
  3. 延迟创建:首次获取时才创建,后续从缓存获取

🔄 Prototype 原型模式

原型模式特点

  1. 每次新建:每次获取 Bean 都创建新实例
  2. 独立实例:每个实例都是独立的
  3. 生命周期:由调用者管理,容器不负责销毁
  4. 无缓存:不进行缓存,每次都创建新实例

实现机制

原型 Bean 不缓存,每次都创建新实例:

@Override
public Object getBean(String beanName) throws Exception {
    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    if (beanDefinition == null) {
        throw new NoSuchBeanDefinitionException(beanName);
    }

    // 根据作用域决定返回策略
    if (beanDefinition.isSingleton()) {
        // 单例模式:从缓存获取或创建
        return getSingleton(beanName, beanDefinition);
    } else {
        // 原型模式:每次都创建新实例
        return createBean(beanName, beanDefinition);
    }
}

🏷️ @Lazy 懒加载注解

懒加载概念

懒加载(Lazy Loading) 是指 Bean 不在容器启动时创建,而是在第一次使用时才创建。

注解定义

创建 src/main/java/com/minispring/context/annotation/Lazy.java

package com.minispring.context.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Lazy 注解用于标记 Bean 为懒加载
 * 容器启动时不会立即创建该 Bean,而是在第一次使用时才创建
 */
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
    /**
     * 是否启用懒加载,默认为 true
     */
    boolean value() default true;
}

实现懒加载

ApplicationContext 中添加预实例化方法:

/**
 * 预实例化所有非懒加载的单例 Bean
 */
private void preInstantiateSingletons() {
    System.out.println("n开始预实例化单例 Bean...");

    for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
        String beanName = entry.getKey();
        BeanDefinition beanDefinition = entry.getValue();

        // 只实例化单例且非懒加载的 Bean
        if (beanDefinition.isSingleton() && !beanDefinition.isLazy()) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                throw new RuntimeException("预实例化 Bean 失败: " + beanName, e);
            }
        }
    }

    System.out.println("单例 Bean 预实例化完成n");
}

📊 作用域对比

对比表格

特性 Singleton Prototype
实例数量 1 个 多个
创建时机 容器启动时(非懒加载) 每次获取时
缓存机制 ✅ 缓存 ❌ 不缓存
生命周期 与容器相同 由调用者管理
线程安全 需要保证 每个实例独立
内存占用
适用场景 无状态服务 有状态对象

使用场景

Singleton 适用场景

  • 无状态的服务类(Service、Repository)
  • 工具类
  • 配置类

Prototype 适用场景

  • 有状态的对象
  • 需要每次创建新实例的场景
  • 线程不安全的对象

🧪 测试示例

测试类

package com.minispring.demo;

import com.minispring.context.annotation.Scope;
import com.minispring.stereotype.Component;
import com.minispring.beans.factory.config.BeanDefinition;
import com.minispring.context.ApplicationContext;

@Component
@Scope("singleton")
class SingletonService {
    private final String id = "Singleton-" + System.currentTimeMillis();

    public String getId() {
        return id;
    }
}

@Component
@Scope("prototype")
class PrototypeService {
    private final String id = "Prototype-" + System.currentTimeMillis();

    public String getId() {
        return id;
    }
}

public class ScopeTest {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ApplicationContext();

        // 注册 Bean
        BeanDefinition singletonBd = new BeanDefinition(SingletonService.class);
        singletonBd.setScope("singleton");
        context.registerBeanDefinition("singletonService", singletonBd);

        BeanDefinition prototypeBd = new BeanDefinition(PrototypeService.class);
        prototypeBd.setScope("prototype");
        context.registerBeanDefinition("prototypeService", prototypeBd);

        // 测试单例模式
        SingletonService s1 = context.getBean("singletonService", SingletonService.class);
        SingletonService s2 = context.getBean("singletonService", SingletonService.class);
        System.out.println("单例模式: s1 == s2 = " + (s1 == s2));
        System.out.println("s1 ID: " + s1.getId());
        System.out.println("s2 ID: " + s2.getId());

        // 测试原型模式
        PrototypeService p1 = context.getBean("prototypeService", PrototypeService.class);
        PrototypeService p2 = context.getBean("prototypeService", PrototypeService.class);
        System.out.println("原型模式: p1 == p2 = " + (p1 == p2));
        System.out.println("p1 ID: " + p1.getId());
        System.out.println("p2 ID: " + p2.getId());
    }
}

📝 本讲总结

本讲我们完成了:

  1. ✅ 理解了 Bean 作用域的概念
  2. ✅ 实现了 @Scope 注解
  3. ✅ 实现了 Singleton 单例模式(使用缓存)
  4. ✅ 实现了 Prototype 原型模式(每次新建)
  5. ✅ 实现了 @Lazy 懒加载注解
  6. ✅ 理解了不同作用域的使用场景

🎯 关键点回顾

  1. Singleton:使用缓存机制,容器中只有一个实例
  2. Prototype:不缓存,每次获取都创建新实例
  3. @Lazy:延迟创建,首次使用时才创建
  4. 线程安全:Singleton 需要保证线程安全

⚠️ 注意事项

  1. Singleton 线程安全:多线程环境下需要保证线程安全
  2. Prototype 内存:频繁创建可能占用较多内存
  3. 懒加载时机:懒加载的 Bean 在首次使用时创建

🚀 下一讲预告

在下一讲中,我们将实现 Bean 生命周期管理,包括:

  • Bean 创建流程
  • @PostConstruct 初始化回调
  • @PreDestroy 销毁回调
  • 初始化方法调用

准备好了吗?让我们继续 第 07 讲:Bean 生命周期管理


思考题

  1. 为什么 Singleton 模式需要缓存?
  2. Prototype 模式适合什么场景?
  3. 懒加载有什么优缺点?

“以书为舟,遨游尘世”,
最好的免费 kindle 电子书分享站:

You may also like...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


*