设计模式与Spring框架:IoC容器中的单例与工厂、AOP中的代理、MVC中的适配器

说实话,很多Java开发者天天用Spring,但未必清楚它背后到底用了哪些设计模式。我经常在面试中问这个问题,能说清楚的人真不多。今天咱们就聊聊Spring框架里最核心的三个设计模式:单例、工厂、代理和适配器。这些不是纸上谈兵,而是你每天写代码时都在用的东西。

一、IoC容器中的单例模式

Spring的IoC容器默认管理的Bean都是单例的。什么意思呢?就是同一个Bean定义,在整个容器中只会创建一个实例。我个人习惯把Spring容器想象成一个“对象仓库”,你每次要某个Bean,它都给你同一个对象。

核心要点:Spring的单例Bean默认是scope="singleton",这也是默认值。如果你需要每次获取新实例,可以改成scope="prototype"

我在项目中遇到过一个问题:一个无状态的Service Bean,因为不小心在类里放了一个成员变量,结果在高并发下数据全乱了。嗯,这就是单例模式带来的“副作用”——所有线程共享同一个实例,成员变量就成了共享资源。

// Spring默认单例
@Component
public class UserService {
    // 注意:单例模式下,这个list会被所有请求共享
    private List<String> tempList = new ArrayList<>();
    
    public void process(String data) {
        tempList.add(data);  // 并发问题!
        // 业务逻辑...
    }
}

避坑指南:我曾经在一个项目中,因为单例Bean里放了可变的成员变量,导致线上偶发数据错乱。排查了两天才找到原因。记住:单例Bean里只能放无状态的逻辑,或者用ThreadLocal来处理线程隔离的数据。

二、IoC容器中的工厂模式

Spring的BeanFactory和ApplicationContext,说白了就是工厂模式的经典实现。你不需要自己new对象,告诉Spring“我要什么”,它帮你创建好。这其实就是工厂模式的核心思想——把对象的创建和使用分离。

你想想看,如果没有工厂模式,你的代码会变成什么样?到处都是new XXX(),耦合度极高。Spring用工厂模式帮你管理对象的生命周期,你只需要关注业务逻辑。

// 传统方式:硬编码创建对象
UserService userService = new UserServiceImpl();
userService.doSomething();

// Spring方式:工厂模式创建
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.doSomething();

我个人觉得,Spring的工厂模式最妙的地方在于:它不只是简单的创建对象,还能处理对象之间的依赖关系。比如A依赖B,Spring在创建A的时候会自动注入B的实例。这在大型项目中简直是救命稻草。

三、AOP中的代理模式

AOP(面向切面编程)是Spring的另一个核心功能,它的底层实现就是代理模式。说白了,就是给目标对象创建一个“替身”,在调用目标方法前后插入额外的逻辑。

Spring AOP支持两种代理方式:

代理方式 适用场景 实现原理
JDK动态代理 目标类实现了接口 通过反射生成代理类
CGLIB代理 目标类没有实现接口 通过字节码增强生成子类
// 假设有一个事务切面
@Aspect
@Component
public class TransactionAspect {
    
    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前置逻辑:开启事务
        System.out.println("开启事务");
        
        Object result = joinPoint.proceed();  // 调用目标方法
        
        // 后置逻辑:提交事务
        System.out.println("提交事务");
        
        return result;
    }
}

经验之谈:我在项目中遇到过一个问题:某个Service类没有实现接口,结果用JDK动态代理报错了。后来改成CGLIB代理才解决。所以如果你的类没有实现接口,记得在配置里加上proxy-target-class="true"

四、MVC中的适配器模式

Spring MVC里的适配器模式,主要体现在HandlerAdapter上。你想想看,Controller的写法五花八门:有的返回ModelAndView,有的返回String,有的用@ResponseBody返回JSON。Spring怎么统一处理这些不同的Controller呢?答案就是适配器模式。

每个Controller都会被包装成一个适配器,适配器知道怎么调用这个Controller,并把结果转换成统一的格式。这样Spring MVC的核心代码就不用关心Controller的具体实现了。

// HandlerAdapter接口
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

// 针对不同Controller的适配器实现
public class RequestMappingHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }
    
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 调用Controller方法,处理返回值
        // ...
    }
}

说白了,适配器模式就是“让不兼容的接口能够一起工作”。Spring MVC通过适配器,把各种风格的Controller统一成了标准的处理流程。我在项目中曾经自己写过一个自定义的Controller类型,就是通过实现HandlerAdapter来让它被Spring MVC识别的。

五、知识体系总览

为了让你更直观地理解这些设计模式在Spring中的位置,我画了一张图:

Spring框架中的设计模式 IoC容器 单例模式 - 默认scope=singleton - 对象共享,注意线程安全 工厂模式 - BeanFactory/ApplicationContext - 对象创建与使用分离 - 依赖注入自动管理 AOP 代理模式 - JDK动态代理(有接口) - CGLIB代理(无接口) - 切面逻辑织入 应用场景 - 事务管理 - 日志记录 - 权限校验 MVC 适配器模式 - HandlerAdapter接口 - 统一不同Controller - 返回值统一处理 常见适配器 - RequestMappingHandlerAdapter - SimpleControllerHandlerAdapter - HttpRequestHandlerAdapter 核心思想:通过设计模式实现松耦合、高内聚、易扩展的架构

六、总结与思考

Spring框架之所以强大,很大程度上是因为它巧妙地运用了多种设计模式。单例模式保证了对象的唯一性和资源的高效利用;工厂模式解耦了对象的创建和使用;代理模式让AOP切面编程成为可能;适配器模式让MVC架构能够灵活处理各种Controller。

我个人觉得,学习设计模式最好的方式就是去读优秀框架的源码。Spring就是一个绝佳的教材。你不需要一下子全看懂,但每次看都能有新的收获。比如我当年看Spring的BeanFactory源码时,才真正理解了工厂模式在大型项目中的威力。

一句话总结:设计模式不是空中楼阁,它就藏在你的日常代码里。Spring框架就是设计模式最好的“活教材”。


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