506. Java 反射 - 在 BeanFactory 中实现单例模式

506. Java 反射 - 在 BeanFactory 中实现单例模式

1. 为什么要用单例 (Singleton)?

在企业级应用中,有些对象是全局唯一的,例如:

  • 数据库连接服务 (DBService)
  • 远程调用客户端 (REST Client、MQ 客户端)
  • 配置中心服务

👉 如果每次都 new 一个,会浪费资源、产生性能问题,甚至引发错误。
👉 使用单例,确保只会创建一个实例,所有地方复用它。


2. 自定义 @Singleton 注解

我们通过注解来标记哪些类应该是单例的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Singleton {}

用法示例:

@Singleton
public class DBService {
}

这样,BeanFactory 知道:DBService 需要单例管理。


3. 改造后的 BeanFactory

核心思想:

  1. 使用 ConcurrentMap 注册表 保存单例对象。
  2. 第一次请求时创建对象,并放入注册表。
  3. 以后再请求时,直接返回已存在的对象。
  4. 利用 ConcurrentMap.putIfAbsent() 保证线程安全。
public enum BeanFactory {
    INSTANCE;

    private final ConcurrentMap<Class<?>, Object> registry = new ConcurrentHashMap<>();

    public <T> T getInstanceOf(Class<T> beanClass, Object... arguments) {
        try {
            if (beanClass.isAnnotationPresent(Singleton.class)) {
                // 已经有实例 → 直接返回
                if (registry.containsKey(beanClass)) {
                    return (T) registry.get(beanClass);
                }

                // 没有 → 创建实例
                T bean = instantiateBeanClass(beanClass, arguments);

                // 放入注册表(原子操作)
                registry.putIfAbsent(beanClass, bean);

                // 返回最终存入的实例(可能不是自己创建的)
                return (T) registry.get(beanClass);
            } else {
                // 普通类 → 每次创建新对象
                return instantiateBeanClass(beanClass, arguments);
            }
        } catch (NoSuchMethodException | InvocationTargetException |
                 InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private <T> T instantiateBeanClass(Class<T> beanClass, Object[] arguments)
            throws NoSuchMethodException, InstantiationException,
            IllegalAccessException, InvocationTargetException {
        Class<?>[] argumentClasses =
                Arrays.stream(arguments).map(Object::getClass).toArray(Class<?>[]::new);
        Constructor<T> beanConstructor = beanClass.getConstructor(argumentClasses);
        return beanConstructor.newInstance(arguments);
    }
}

4. 使用示例

void main() {
    BeanFactory beanFactory = BeanFactory.INSTANCE;

    // 普通类 → 每次创建新对象
    Message message = beanFactory.getInstanceOf(Message.class, "Hello");
    System.out.println("Message = " + message);

    // 单例类 → 所有调用返回同一个实例
    DBService dbService1 = beanFactory.getInstanceOf(DBService.class);
    DBService dbService2 = beanFactory.getInstanceOf(DBService.class);
    System.out.println("Instances of DBService are the same? " + (dbService1 == dbService2));
}

输出结果:

Message = Message[message=Hello]
Instances of DBService are the same? true

5.要点 🎓

  • 线程安全:为什么要用 ConcurrentMap + putIfAbsent()
    👉 防止并发情况下创建多个实例。
  • 为什么最后用 registry.get() 返回?
    👉 因为可能有别的线程赢得了竞争,它的实例才是最终被放进去的。
  • 对比 Spring:Spring 的 @Scope("singleton") 本质上就是类似的机制,只是功能更丰富。
  • 延伸思考
    • 如果想要 “懒加载单例”,可以结合 Supplier<T> 或者 computeIfAbsent()
    • 如果 Bean 构造参数不同,是否还应该共享同一个实例?(讨论点)
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容