设计模式与依赖注入:工厂模式实现DI、单例模式管理Bean、代理模式实现AOP

说实话,很多朋友学设计模式时,总觉得它们是孤立的。工厂就是创建对象,单例就是全局唯一,代理就是加个中间层。但在我十几年的项目经验里,这些模式往往是一起出现的。尤其是当你开始写一个轻量级的框架时——嗯,你会发现它们天然就是一家人。

今天我们就来聊聊,怎么用工厂模式实现依赖注入(DI),用单例模式管理Bean,用代理模式实现AOP。这三者组合起来,其实就是Spring IoC容器的核心雏形。我自己在早期做项目时,就手写过这样一个微型容器,效果出奇地好。

核心思想: 设计模式不是孤立存在的。工厂负责创建和装配,单例负责生命周期管理,代理负责横切逻辑增强。三者协作,构成了依赖注入容器的骨架。

1. 工厂模式实现依赖注入

依赖注入,说白了就是「别自己new,让别人给你」。谁来做这个「别人」?工厂模式最合适。

我习惯把工厂分为两种:一种是简单工厂,根据类型直接创建;另一种是IoC工厂,它会扫描配置,递归地创建并注入依赖。

来看一个最简的IoC工厂实现:

// 一个极简的依赖注入容器
public class SimpleIoCContainer {
    private Map<String, Object> beans = new HashMap<>();
    private Map<String, BeanDefinition> definitions = new HashMap<>();

    public void registerBean(String name, BeanDefinition def) {
        definitions.put(name, def);
    }

    public Object getBean(String name) {
        if (beans.containsKey(name)) {
            return beans.get(name);
        }
        // 工厂模式:创建并注入依赖
        BeanDefinition def = definitions.get(name);
        Object bean = createInstance(def.getClassName());
        // 注入依赖属性
        injectDependencies(bean, def.getProperties());
        beans.put(name, bean);
        return bean;
    }

    private void injectDependencies(Object bean, Map<String, String> props) {
        for (Map.Entry<String, String> entry : props.entrySet()) {
            String fieldName = entry.getKey();
            String refBeanName = entry.getValue();
            Object refBean = getBean(refBeanName); // 递归注入
            setField(bean, fieldName, refBean);
        }
    }
}

你看,这里的getBean方法就是一个典型的工厂方法。它不直接new,而是根据配置创建对象,并且自动把依赖的Bean注入进去。我在一个电商项目中就用过类似的思路,把几十个Service的依赖关系全部交给这个工厂管理,再也不用担心构造顺序了。

我的经验: 工厂模式实现DI时,要注意循环依赖问题。我一般用「正在创建中」的标记来检测,如果发现A依赖B,B又依赖A,就直接抛异常,避免栈溢出。

2. 单例模式管理Bean

容器里的Bean,大部分应该是单例的。为什么?因为无状态的Service、DAO,创建一次就够了,反复创建浪费内存,也浪费GC。

单例模式在这里的应用,其实比教科书上的「懒汉式」「饿汉式」要灵活得多。我们不是在类内部控制单例,而是在容器层面控制——同一个Bean名称,只创建一个实例。

// 容器级别的单例管理
public class SingletonBeanRegistry {
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    public Object getSingleton(String beanName) {
        return singletonObjects.get(beanName);
    }

    public void registerSingleton(String beanName, Object bean) {
        Object old = singletonObjects.putIfAbsent(beanName, bean);
        if (old != null) {
            throw new IllegalStateException("Bean already exists: " + beanName);
        }
    }

    public boolean containsSingleton(String beanName) {
        return singletonObjects.containsKey(beanName);
    }
}

这里有个细节:我用的是putIfAbsent而不是put。为什么?因为并发环境下,两个线程可能同时注册同一个Bean。我曾经在生产环境遇到过这个问题——一个定时任务框架因为并发注册导致Bean被覆盖,结果某些任务执行了两次。嗯,从那以后我对并发注册格外小心。

避坑指南: 单例Bean如果有状态(比如成员变量),一定要考虑线程安全。我见过有人把用户信息存在单例Service的字段里,结果A用户看到了B用户的数据。单例只保证唯一,不保证线程安全。

3. 代理模式实现AOP

AOP(面向切面编程)的核心,就是代理模式。你想在方法执行前后加日志、加事务、加权限检查,但又不想改原有代码——那就用代理把它包一层。

Java里实现代理有两种方式:JDK动态代理和CGLIB。JDK代理要求目标类实现接口,CGLIB可以直接代理类。我一般优先用JDK代理,因为它是原生支持的,性能也更好。

// 一个简单的AOP代理工厂
public class JdkDynamicAopProxy implements InvocationHandler {
    private Object target;
    private List<MethodInterceptor> interceptors;

    public Object createProxy(Object target, List<MethodInterceptor> interceptors) {
        this.target = target;
        this.interceptors = interceptors;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        for (MethodInterceptor interceptor : interceptors) {
            interceptor.before(method, args);
        }
        // 调用目标方法
        Object result = method.invoke(target, args);
        // 后置增强
        for (MethodInterceptor interceptor : interceptors) {
            interceptor.after(method, args, result);
        }
        return result;
    }
}

你想想看,这个代理工厂和前面的IoC容器结合起来,会发生什么?容器创建Bean时,如果检测到它有AOP配置,就不直接返回原始Bean,而是返回代理对象。调用方拿到代理对象,完全感知不到底层有增强逻辑——这就是AOP的魅力。

三者协作流程:
1. 容器启动,读取配置
2. 工厂模式创建Bean实例
3. 单例注册表管理Bean生命周期
4. 如果Bean需要AOP,代理工厂生成代理对象
5. 最终返回给调用方的是代理后的Bean

4. 三者整合:一个微型IoC容器

下面我把这三者整合起来,形成一个完整的微型容器。这个容器虽然简单,但核心思想跟Spring IoC是一致的。

// 微型IoC容器
public class MiniApplicationContext {
    private SingletonBeanRegistry singletonRegistry = new SingletonBeanRegistry();
    private SimpleIoCContainer iocContainer = new SimpleIoCContainer();
    private JdkDynamicAopProxy aopProxy = new JdkDynamicAopProxy();

    public void refresh() {
        // 1. 扫描配置,注册Bean定义
        loadBeanDefinitions();

        // 2. 创建单例Bean(非懒加载的)
        for (String beanName : getSingletonBeanNames()) {
            getBean(beanName);
        }
    }

    public Object getBean(String name) {
        // 先从单例缓存取
        Object bean = singletonRegistry.getSingleton(name);
        if (bean != null) {
            return bean;
        }

        // 工厂创建(含依赖注入)
        bean = iocContainer.getBean(name);

        // 如果需要AOP,生成代理
        if (hasAopConfig(name)) {
            List<MethodInterceptor> interceptors = getInterceptors(name);
            bean = aopProxy.createProxy(bean, interceptors);
        }

        // 注册为单例
        singletonRegistry.registerSingleton(name, bean);
        return bean;
    }
}

这段代码虽然只有几十行,但它把工厂模式、单例模式、代理模式完美地融合在了一起。我在做一个内部工具平台时,就用这个思路写了一个不到500行的容器,支撑了十几个微服务的Bean管理。说实话,效果超出预期。

个人建议: 如果你想深入理解Spring,不妨自己手写一个这样的微型容器。不用考虑太多边界情况,先把主流程跑通。你会发现,原来那些高大上的框架,底层就是这些设计模式的组合。

5. 知识体系总览

为了让你更直观地理解这三者的关系,我画了一张图:

设计模式与依赖注入:三者协作关系图 工厂模式 创建Bean实例 注入依赖属性 管理Bean定义 单例模式 Bean唯一实例 生命周期管理 并发安全控制 代理模式 JDK/CGLIB代理 方法增强拦截 AOP横切逻辑 创建后注册 需要AOP时 微型IoC容器(MiniApplicationContext) 1. 工厂模式:根据配置创建Bean,自动注入依赖 2. 单例模式:缓存已创建的Bean,保证全局唯一 3. 代理模式:对需要增强的Bean生成代理对象

从这张图可以看得很清楚:工厂模式是「生产者」,单例模式是「仓库」,代理模式是「包装车间」。三者各司其职,又紧密协作。你写框架时,这个结构可以直接复用。

6. 总结与思考

聊了这么多,我想表达的核心观点其实就一句话:设计模式不是背出来的,是用出来的。工厂、单例、代理这三个模式,单独看都很简单,但把它们组合起来,就能构建出像Spring这样强大的框架。

我在带团队时,经常让新人做一个小练习:用这三大模式手写一个简易的IoC容器,支持XML配置、单例管理、AOP日志增强。做完这个练习,对Spring的理解会上一个台阶。你不妨也试试。

关键收获:
✅ 工厂模式:负责对象的创建和依赖装配
✅ 单例模式:负责Bean的缓存和生命周期管理
✅ 代理模式:负责横切逻辑的织入和增强
✅ 三者组合:构成了依赖注入容器的核心骨架

最后说一句:别怕手写框架。你写一次,胜过读十遍源码。嗯,今天就到这里。


公众号:蓝海资料掘金营,微信deep3321