设计模式在Spring框架中的应用:IoC、AOP、MVC、Template

说实话,Spring框架能火这么多年,不是没有道理的。我最早接触Spring时,第一反应是「这不就是个对象容器吗?」后来踩的坑多了,才慢慢品出味道——Spring本质上是一套设计模式的集大成者。今天我就带你拆解一下,Spring到底用了哪些设计模式,以及我们在实际项目中该怎么借鉴。

IoC容器:单例模式与工厂模式的完美融合

IoC(控制反转)是Spring的基石。说白了,就是把对象的创建权交给容器,我们只管用。这里面藏着两个经典模式。

单例模式:Spring Bean的默认Scope

Spring容器管理的Bean,默认就是单例的。这意味着在整个容器生命周期内,同一个Bean只会创建一个实例。

核心要点:Spring的单例实现与传统的「饿汉式」「懒汉式」不同,它用的是注册表模式——Bean定义注册到容器,第一次请求时创建,然后缓存起来。

// Spring内部简化版单例实现
public class DefaultSingletonBeanRegistry {
    // 一级缓存:成品Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:早期暴露的Bean(解决循环依赖)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    // 三级缓存:Bean工厂(延迟创建代理对象)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    public Object getSingleton(String beanName) {
        // 先从一级缓存拿
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 再从二级缓存拿
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null) {
                // 最后从三级缓存拿,并创建Bean
                ObjectFactory<?> factory = this.singletonFactories.get(beanName);
                if (factory != null) {
                    singletonObject = factory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
        return singletonObject;
    }
}

我的经验:我曾经在一个高并发项目中,把Service层Bean设成了prototype作用域,结果每次请求都创建新对象,GC压力暴增。后来改成单例,性能提升了30%。记住:无状态Bean用单例,有状态Bean才考虑原型

工厂模式:BeanFactory与ApplicationContext

Spring的工厂模式体现在两个层面:

  • BeanFactory:最基础的工厂,延迟初始化,按需创建Bean
  • ApplicationContext:增强版工厂,预加载所有单例Bean,支持国际化、事件发布等

你想想看,这其实就是简单工厂 + 抽象工厂的变体。BeanDefinition就是产品描述,BeanFactory就是生产线,而ApplicationContext就是智能工厂。

// 工厂方法模式在Spring中的体现
public interface BeanFactory {
    Object getBean(String name) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
}

// 抽象工厂:根据不同的Bean定义创建不同类型的Bean
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport {
    protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args);
}

AOP:代理模式的教科书级应用

AOP(面向切面编程)是Spring的另一大杀器。它的底层实现,说白了就是动态代理

JDK动态代理 vs CGLIB代理

特性 JDK动态代理 CGLIB代理
目标对象要求 必须实现接口 无需接口
代理生成方式 运行时生成接口实现类 运行时生成子类
性能 创建快,调用慢 创建慢,调用快
Spring默认策略 有接口时优先使用 无接口或配置强制时使用

避坑指南:我曾经在项目中遇到一个诡异问题——事务注解不生效。排查了半天,发现是Service类没有实现接口,而Spring默认用了CGLIB代理。但CGLIB代理有个限制:final方法无法被代理。那个方法恰好被标记了final,导致事务切面没织入进去。从那以后,我写Service类都习惯性加上接口。

// Spring AOP代理的核心逻辑(简化版)
public class DefaultAopProxyFactory implements AopProxyFactory {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) {
        if (config.isOptimize() || config.isProxyTargetClass() || 
            hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass.isInterface()) {
                // 目标类是接口,用JDK动态代理
                return new JdkDynamicAopProxy(config);
            }
            // 否则用CGLIB
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
}

MVC:观察者模式与中介者模式的巧妙结合

Spring MVC的设计,其实藏着两个模式:观察者模式中介者模式

观察者模式:事件驱动机制

Spring的事件机制,就是典型的观察者模式。ApplicationEvent是事件,ApplicationListener是观察者,ApplicationEventPublisher是主题。

// 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private Long orderId;
    public OrderCreatedEvent(Object source, Long orderId) {
        super(source);
        this.orderId = orderId;
    }
}

// 事件监听器
@Component
public class OrderEventListener implements ApplicationListener<OrderCreatedEvent> {
    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        // 处理订单创建后的逻辑:发短信、更新库存等
        System.out.println("订单创建成功,订单ID:" + event.getOrderId());
    }
}

// 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void createOrder(Order order) {
        // 保存订单...
        publisher.publishEvent(new OrderCreatedEvent(this, order.getId()));
    }
}

我的建议:在微服务架构中,我习惯用Spring事件来做本地事务内的异步处理。比如订单创建后,需要发短信、更新ES索引、记录日志——这些操作如果都写在主流程里,事务会很长。用事件解耦后,主流程只做核心操作,监听器各自处理自己的事,清爽多了。

中介者模式:DispatcherServlet的核心角色

DispatcherServlet就是Spring MVC的中介者。它不直接处理业务,而是协调各个组件:HandlerMapping找处理器,HandlerAdapter执行处理器,ViewResolver解析视图。

// DispatcherServlet的核心流程(简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // 1. 通过HandlerMapping找到对应的处理器
    HandlerExecutionChain mappedHandler = getHandler(request);
    
    // 2. 通过HandlerAdapter找到适配的执行器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    // 3. 执行处理器,返回ModelAndView
    ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
    
    // 4. 通过ViewResolver解析视图
    processDispatchResult(request, response, mappedHandler, mv, exception);
}

你想想看,如果没有DispatcherServlet这个中介者,每个Controller都要自己处理请求分发、参数解析、视图渲染——那代码得多乱?中介者模式的核心价值就是降低组件间的耦合度

Template:模板方法模式的优雅实践

Spring中模板方法模式用得最典型的就是JdbcTemplateRestTemplateTransactionTemplate这些。它们把固定的流程封装好,把变化的部分留给子类或回调。

JdbcTemplate的设计思路

数据库操作其实是个固定流程:获取连接 → 创建Statement → 执行SQL → 处理结果集 → 关闭资源。JdbcTemplate把「处理结果集」这一步抽象出来,让调用者自己实现。

// JdbcTemplate的模板方法模式
public class JdbcTemplate {
    public <T> T query(String sql, ResultSetExtractor<T> rse) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 固定步骤:获取连接
            conn = getConnection();
            // 固定步骤:创建Statement
            stmt = conn.createStatement();
            // 固定步骤:执行SQL
            rs = stmt.executeQuery(sql);
            // 可变步骤:处理结果集(由回调实现)
            return rse.extractData(rs);
        } catch (SQLException e) {
            throw new DataAccessException(e);
        } finally {
            // 固定步骤:关闭资源
            closeResultSet(rs);
            closeStatement(stmt);
            closeConnection(conn);
        }
    }
}

// 使用示例
List<User> users = jdbcTemplate.query(
    "SELECT * FROM user WHERE age > ?",
    new Object[]{18},
    (rs, rowNum) -> {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        return user;
    }
);

核心思想:模板方法模式把「不变的部分」封装在父类,把「变的部分」通过回调或抽象方法暴露出去。这样既保证了流程的规范性,又保留了灵活性。

知识体系总览

下面这张图,是我梳理的Spring框架中设计模式的应用全景。你可以把它当作一个速查表。

Spring框架中的设计模式全景 IoC容器 单例模式 • 默认Scope • 三级缓存 • 循环依赖 工厂模式 • BeanFactory • ApplicationContext • BeanDefinition AOP 代理模式 • JDK动态代理 • CGLIB代理 切面织入 • @Before • @After • @Around MVC 观察者模式 • 事件驱动 • 异步解耦 中介者模式 • DispatcherServlet • HandlerMapping • ViewResolver Template 模板方法模式 • JdbcTemplate • RestTemplate • TransactionTemplate 回调机制 • RowMapper • ResultSetExtractor 核心思想:将设计模式融入框架设计,让开发者专注于业务,而非基础设施

说实话,Spring框架之所以能成为Java生态的标杆,就是因为它把设计模式用到了极致。我们平时写代码,不用刻意去套用设计模式,但理解这些模式在Spring中的应用,能帮我们写出更优雅、更可维护的代码。

最后分享一个我的习惯:每当我需要设计一个公共组件时,我会先问自己三个问题:

  1. 这个组件的「不变流程」是什么?(模板方法模式)
  2. 哪些部分需要让调用者自定义?(策略模式/回调)
  3. 如何降低组件间的耦合?(中介者模式/观察者模式)

想清楚这三点,设计出来的组件基本不会太差。


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