本文里的设计模式分两类:经典老干部(GoF原版)和Spring潮人(框架演化出的新物种)。建议搭配咖啡阅读,因为内容有点上头。


一、经典老干部:策略模式(Strategy Pattern)

1.1 这老头是谁

策略模式,GoF 23将之一,入行必拜的码头。核心思想一句话:定义一族算法,分别封装起来,让它们可以互相替换,算法的变化独立于使用它的客户

说人话:你有一个if-else链长得像港珠澳大桥,策略模式就是来拆桥的。

1.2 没有策略模式的惨痛人生

// 怨种写法:每加一种支付方式就改这里
public void pay(String type, BigDecimal amount) {
    if ("ALIPAY".equals(type)) {
        System.out.println("支付宝到账 " + amount);
    } else if ("WECHAT".equals(type)) {
        System.out.println("微信支付 " + amount);
    } else if ("UNION_PAY".equals(type)) {
        System.out.println("银联云闪付 " + amount);
    } else if ("CREDIT_CARD".equals(type)) {
        System.out.println("信用卡支付 " + amount);
    }
    // ... 三个月后这里已经100行了
}

老板说要加"数字人民币",你在第78行加了一个else if,结果把原来"花呗分期"的逻辑挤坏了。这就是开闭原则的眼泪——对扩展开放?不,对改崩开放。

1.3 策略模式拯救人生

// 策略接口:支付方式抽象
public interface PaymentStrategy {
    void pay(BigDecimal amount);
    String getType(); // 返回策略标识
}

// 具体策略:支付宝
@Component  // 注意这里!Spring来托管了
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("[支付宝] 扫码支付 " + amount);
    }
    @Override
    public String getType() { return "ALIPAY"; }
}

// 具体策略:微信支付
@Component
public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("[微信支付] 指纹支付 " + amount);
    }
    @Override
    public String getType() { return "WECHAT"; }
}

// 具体策略:银联
@Component
public class UnionPayStrategy implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("[银联] 云闪付挥卡 " + amount);
    }
    @Override
    public String getType() { return "UNION_PAY"; }
}

1.4 上下文持有策略

@Component
public class PaymentContext {
    
    // Spring自动把所有PaymentStrategy实现类塞进来,key是beanName
    @Autowired
    private Map<String, PaymentStrategy> strategyMap;
    
    // 或者按类型注入List
    @Autowired
    private List<PaymentStrategy> strategies;
    
    public void executePay(String type, BigDecimal amount) {
        PaymentStrategy strategy = strategies.stream()
            .filter(s -> s.getType().equals(type))
            .findFirst()
            .orElseThrow(() -> new RuntimeException("不支持支付方式: " + type));
        strategy.pay(amount);
    }
}

1.5 效果对比

维度 if-else写法 策略模式
新增支付方式 改原有代码,容易引入bug 新建类即可,零侵入
单元测试 一个方法测所有分支,爆炸 每个策略独立测试,清爽
运行时替换 不可能 结合Spring可动态切换
代码行数 线性增长,永无止境 每个策略独立,可控

二、Spring潮人登场:6大原创设计模式

以下模式并非GoF经典,而是Spring框架在进化过程中自然演化出的设计哲学。我给它们取了名字、画了UML、编了段子。这些模式在Spring源码中真实存在,只是没有人从设计模式的视角正经讲过。


模式一:容器托管模式(Container Delegation Pattern)

概念

对象不自己创建依赖,而是向容器"托管"生命周期。IoC的本质不是"控制反转"四个字能说完的——它是一种全新的造物方式。

传统世界的悲剧
// 一个类自己new依赖,就像自己给自己做手术
public class OrderService {
    // 紧耦合:OrderService和UserService焊死了
    private UserService userService = new UserService();
    private LogService logService = new LogService();
    private EmailService emailService = new EmailService();
    
    public void createOrder() {
        userService.checkLogin();
        // ... 业务逻辑
        logService.record("创建订单");
        emailService.send("订单已创建");
    }
}

OrderService像一个全能管家,又要管订单、又要管用户、又要发邮件。更惨的是单元测试时,你想mock一个EmailService,发现它是new出来的——硬编码是mock的天敌

Spring托管后的人生赢家
@Service
public class OrderService {
    
    // 依赖由Spring容器注入,OrderService只管订单
    @Autowired
    private UserService userService;
    
    @Autowired
    private LogService logService;
    
    @Autowired
    private EmailService emailService;
    
    public void createOrder() {
        userService.checkLogin();
        // ... 专注订单业务
        logService.record("创建订单");
        emailService.send("订单已创建");
    }
}
UML 类图
+----------------+         +------------------+
|   Application  |<>-------|   BeanFactory    |
|    Context     |  1    1 |  (容器抽象)       |
+----------------+         +--------+---------+
                                    |
                                    | 创建 & 管理
                                    v
+----------------+         +------------------+
|   OrderService |<--------|  Dependency      |
|  (业务组件)     |  依赖    |  Injection       |
+----------------+         +------------------+
       | 使用
       v
+----------------+
| UserService    |
| LogService     |
| EmailService   |
+----------------+
核心思想

** Don’t call me, I’ll call you. ** 好莱坞原则——不是对象去找依赖,是容器把依赖塞给对象。


模式二:切面编织模式(Aspect Weaving Pattern)

概念

在不修改原有代码的前提下,横向切入增强逻辑。AOP不是简单的代理包装,它是一种"时空折叠"——你可以在方法执行前、后、甚至异常时"插入"代码。

无AOP的黑暗年代
@Service
public class UserService {
    
    public User getUser(Long id) {
        long start = System.currentTimeMillis();  // 日志1:计时
        try {
            System.out.println("[日志] 查询用户: " + id);  // 日志2
            User user = userDao.selectById(id);           // 真正业务
            System.out.println("[日志] 查询结果: " + user); // 日志3
            return user;
        } catch (Exception e) {
            System.out.println("[异常] 查询失败: " + e);    // 日志4
            throw e;
        } finally {
            System.out.println("[耗时] " + (System.currentTimeMillis() - start) + "ms"); // 日志5
        }
    }
    
    // 每个方法都写这一堆?复制粘贴100次?
    public void saveUser(User user) { /* ... 同样的模板代码 ... */ }
    public void deleteUser(Long id) { /* ... 同样的模板代码 ... */ }
}

业务代码被日志、监控、事务搞得乌烟瘴气。这就像一个演员,每次上台还得自己搬道具、调灯光、放背景音乐——AOP就是给演员配了个幕后团队

AOP后的清爽世界
@Service
public class UserService {
    // 干干净净,只关注业务
    public User getUser(Long id) {
        return userDao.selectById(id);
    }
    
    public void saveUser(User user) {
        userDao.insert(user);
    }
}

// 切面:专门干杂活的幕后团队
@Aspect
@Component
public class ServiceLogAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        long start = System.currentTimeMillis();
        
        System.out.println("[前置] 方法 " + methodName + " 开始执行");
        
        try {
            Object result = joinPoint.proceed(); // 执行业务方法
            System.out.println("[返回] 方法 " + methodName + " 返回值: " + result);
            return result;
        } catch (Throwable e) {
            System.out.println("[异常] 方法 " + methodName + " 抛异常: " + e.getMessage());
            throw e;
        } finally {
            System.out.println("[后置] 方法 " + methodName + " 耗时: " 
                + (System.currentTimeMillis() - start) + "ms");
        }
    }
}
通知类型一览(切面团队的工种)
通知类型 注解 比喻 执行时机
前置通知 @Before 开场报幕员 方法执行前
后置通知 @AfterReturning 谢幕鼓掌 方法成功返回后
异常通知 @AfterThrowing 救护车 方法抛异常时
最终通知 @After 保洁阿姨 无论如何都执行(finally)
环绕通知 @Around 导演 包裹整个方法,权力最大
核心思想

横切关注点分离。日志、权限、事务、监控这些"横着走"的逻辑,不该和业务代码"竖着"纠缠在一起。


模式三:自动装配模式(Autowiring Pattern)

概念

容器自动识别并满足组件的依赖需求,无需显式指定bean之间的连线@Autowired不是简单的注入注解,它是一种"智能匹配"哲学。

@Autowired 的三重境界
第一重:按类型(默认)
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService; // Spring按类型找PaymentService的实现
}
第二重:按名称(类型冲突时)
@Service
public class OrderService {
    @Autowired
    @Qualifier("alipayService")  // 类型有多个?按名字点名
    private PaymentService paymentService;
}
第三重:Optional(找不到也不炸)
@Service
public class OrderService {
    @Autowired(required = false)  // 这个依赖 optional,没有就算了
    private SmsService smsService;
}
装配决策流程图
遇到 @Autowired
      |
      v
按类型查找候选Bean
      |
      +-- 没找到?---> required=true ? 抛异常 : 注入null
      |
      +-- 找到1个?---> 直接注入,收工
      |
      +-- 找到N个?---> 下一步判断
                          |
                          v
                    是否有 @Qualifier?
                          |
                    +-- 有 ---> 按@Qualifier指定名称匹配
                    |
                    +-- 没有 ---> 检查字段名/参数名是否匹配某个candidate
                                      |
                                    匹配上?---> 注入
                                      |
                                    没匹配?---> 抛 NoUniqueBeanDefinitionException
与JSR-250 @Resource 的差异
public class Comparison {
    @Autowired  // Spring原生:先byType,type冲突再byName
    private UserService userService;
    
    @Resource(name = "userService")  // JSR标准:先byName,name找不到再byType
    private UserService userService2;
}

一句话记忆@Autowired是相亲先看重条件(type),条件匹配多了再看名字;@Resource是相亲先看名字(name),名字对不上再看条件。


模式四:条件装配模式(Conditional Assembly Pattern)

概念

Bean的注册不是硬编码的,而是基于运行时环境条件动态决定。Spring Boot的自动配置核心就是这个模式——“只有满足条件,我才加载这个Bean”。

@Conditional 家族全览
// 类路径下存在某个类时,才注册这个Bean
@ConditionalOnClass(RedisOperations.class)
public class RedisAutoConfiguration { }

// 类路径下不存在某个类时
@ConditionalOnMissingClass("com.mysql.jdbc.Driver")
public class EmbeddedDatabaseConfiguration { }

// 容器中存在某个Bean时
@ConditionalOnBean(DataSource.class)
public class JpaConfiguration { }

// 容器中不存在某个Bean时
@ConditionalOnMissingBean(JedisConnectionFactory.class)
public class LettuceConnectionConfiguration { }

// 配置属性匹配某个值时
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
public class RedisConnectionConfiguration { }

// 在Web环境下才生效
@ConditionalOnWebApplication
public class WebMvcAutoConfiguration { }
手写条件注解:只在"双11"开启的促销Bean
// 1. 定义条件注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DoubleElevenCondition.class)
public @interface ConditionalOnDoubleEleven { }

// 2. 实现条件判断
public class DoubleElevenCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        LocalDate today = LocalDate.now();
        // 每年11月1日到11月11日开启促销模式
        return today.getMonthValue() == 11 && today.getDayOfMonth() <= 11;
    }
}

// 3. 使用
@Component
@ConditionalOnDoubleEleven
public class DoubleElevenPromotionService {
    public void applyDiscount() {
        System.out.println("双11大促!全场五折!不讲道理!");
    }
}
Spring Boot自动配置的幕后
// spring-boot-autoconfigure.jar/META-INF/spring.factories
// (Spring Boot 2.x版本)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration

// 这些类头上都有一堆@Conditional,Spring Boot逐个判断该不该激活
核心思想

环境自适应。同样的代码包,在开发环境、测试环境、生产环境表现不同——不是改配置文件的差别,是加载的Bean集合本身就不同。


模式五:模板方法Spring化模式(Template Callback Pattern)

概念

经典模板方法模式定义了算法骨架,子类重写某些步骤。Spring把它玩出了花——用回调接口代替继承,用匿名类/Lambda代替子类

JDBC的进化史
// === 原始时代:JDBC纯手写,代码重复到令人发指 ===
public User queryUser(long id) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = dataSource.getConnection();           // 1. 获取连接
        ps = conn.prepareStatement("SELECT * FROM user WHERE id=?");
        ps.setLong(1, id);                           // 2. 设置参数
        rs = ps.executeQuery();                      // 3. 执行查询
        if (rs.next()) {
            User user = new User();                  // 4. 处理结果
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            return user;
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);               // 5. 异常处理
    } finally {
        try { if (rs != null) rs.close(); } catch (SQLException e) {}      // 6. 关资源
        try { if (ps != null) ps.close(); } catch (SQLException e) {}
        try { if (conn != null) conn.close(); } catch (SQLException e) {}
    }
    return null;
}
// 每次查数据库都写这一堆?连接的获取、资源的关闭、异常的处理——全是重复劳动!
// === Spring时代:JdbcTemplate拯救你 ===
@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public User queryUser(long id) {
        // 只管SQL和结果映射,连接、资源、异常全交给模板
        return jdbcTemplate.queryForObject(
            "SELECT * FROM user WHERE id=?",
            (rs, rowNum) -> {                       // RowMapper回调:只关注"怎么映射结果"
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                return user;
            },
            id                                      // 参数
        );
    }
    
    public List<User> queryAll() {
        return jdbcTemplate.query(
            "SELECT * FROM user",
            (rs, rowNum) -> {                       // 同一个模板,不同的回调
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                return user;
            }
        );
    }
    
    public int insertUser(User user) {
        return jdbcTemplate.update(                  // 增删改用update
            "INSERT INTO user(name, email) VALUES(?, ?)",
            user.getName(),
            user.getEmail()
        );
    }
}
回调接口解析
JdbcTemplate 的核心骨架(固定不变):
+----------------------------------------+
| 1. 获取 Connection                     |
| 2. 创建 PreparedStatement              |
| 3. 设置参数(占位符 ? 替换)             |
| 4. 【回调点】执行 & 处理 ResultSet      |  <--- RowMapper/ResultSetExtractor 介入
| 5. 关闭 ResultSet                      |
| 6. 关闭 PreparedStatement              |
| 7. 关闭 Connection / 归还连接池         |
+----------------------------------------+
// 回调接口:你只需实现这个,剩下的Spring帮你搞定
@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

// 其他Spring模板中的回调:
// - RowCallbackHandler:逐行处理(适合大数据量,不一次性加载)
// - ResultSetExtractor:整体提取(适合复杂映射,如一对多)
// - PreparedStatementSetter:自定义参数设置
// - PreparedStatementCreator:自定义PreparedStatement创建
核心思想

不变的是骨架,变的是回调。经典模板方法用继承(IS-A),Spring模板用组合(HAS-A + callback)——更灵活,更解耦。


模式六:事件驱动响应模式(Event-Driven Response Pattern)

概念

组件之间不直接调用,而是通过发布-订阅事件来通信。Spring的ApplicationEvent机制是观察者模式的Spring进化版。

直接调用的耦合之痛
@Service
public class OrderService {
    @Autowired private InventoryService inventoryService;
    @Autowired private PaymentService paymentService;
    @Autowired private NotificationService notificationService;
    @Autowired private PointsService pointsService;
    @Autowired private LogService logService;
    
    public void createOrder(Order order) {
        // 下单要干的事越来越多,这里越来越长...
        inventoryService.decrease(order);      // 1. 扣库存
        paymentService.charge(order);           // 2. 扣款
        notificationService.send(order);        // 3. 发通知
        pointsService.add(order);               // 4. 加积分
        logService.record(order);               // 5. 记日志
        // 哪天要加"发优惠券",又得改这里...
        // 哪天"加积分"要异步执行,又得改这里...
    }
}

OrderService像一个电话接线员,直接打给每个部门。部门越来越多,接线员疯了。

事件驱动后的优雅
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    private final LocalDateTime createdAt;
    
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
        this.createdAt = LocalDateTime.now();
    }
    // getters...
}

// 2. 发布事件(Publisher:我只管发广播,不管谁听)
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void createOrder(Order order) {
        // 核心逻辑:保存订单
        orderRepository.save(order);
        
        // 广播:订单已创建!各部门自行处理!
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}
// 3. 监听事件(Listener:各干各的,互不影响)

@Component
public class InventoryListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("[库存系统] 扣减库存: " + event.getOrder().getItems());
    }
}

@Component
public class NotificationListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("[通知系统] 发送短信给用户: " + event.getOrder().getUserId());
    }
}

@Component
public class PointsListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("[积分系统] 增加积分: " + event.getOrder().getAmount());
    }
}

@Component
public class CouponListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("[优惠券系统] 发放新人优惠券");
    }
}
异步事件:给监听器加"快车道"
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean("eventExecutor")
    public Executor eventExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("event-");
        executor.initialize();
        return executor;
    }
}

@Component
public class PointsListener {
    // 异步执行:发积分慢慢算,别卡主流程
    @EventListener
    @Async("eventExecutor")
    public void onOrderCreated(OrderCreatedEvent event) {
        // 这个方法会在独立线程中执行
        System.out.println("[积分系统-异步] 线程: " + Thread.currentThread().getName());
        pointsService.calculateAndAdd(event.getOrder());
    }
}
事件传播机制
特性 说明
同步默认 事件发布和监听在同一线程,按注册顺序执行
@Async 指定监听器异步执行,不阻塞发布者
@Order(1) 控制监听器的执行顺序,数字越小越先
@TransactionalEventListener 绑定事务阶段(AFTER_COMMIT等),事务成功后才执行
SpEL条件 @EventListener(condition = "#event.order.amount > 1000") 只处理大额订单
核心思想

发布者不知道谁在听,监听者不知道谁发的。彻底解耦。这就是观察者模式的最高境界——从一个对象喊一嗓子,全世界响应,但彼此不相识。


三、设计模式总览图

+------------------+     +------------------+     +------------------+
|   经典GoF模式     |     |  Spring演化模式   |     |     关系         |
+------------------+     +------------------+     +------------------+
|                  |     |                  |     |                  |
| 策略模式          | --> | 自动装配模式       |     | Spring自动发现    |
| (Strategy)       |     | (Autowiring)     |     | 策略实现类        |
|                  |     |                  |     |                  |
| 模板方法模式      | --> | 模板回调模式       |     | 继承转组合+Lambda |
| (Template Method)|     | (Template Callback)|   | 更灵活            |
|                  |     |                  |     |                  |
| 观察者模式        | --> | 事件驱动响应模式    |     | ApplicationEvent |
| (Observer)       |     | (Event-Driven)   |     | 机制增强          |
|                  |     |                  |     |                  |
| 工厂模式          | --> | 容器托管模式       |     | BeanFactory      |
| (Factory)        |     | (Container)      |     | 统一托管生命周期   |
|                  |     |                  |     |                  |
| 代理模式          | --> | 切面编织模式       |     | AOP动态代理       |
| (Proxy)          |     | (Aspect Weaving) |     | JDK/CGLIB        |
|                  |     |                  |     |                  |
| 条件判断逻辑      | --> | 条件装配模式       |     | @Conditional     |
| (if-else)        |     | (Conditional)    |     | 解耦环境判断      |
|                  |     |                  |     |                  |
+------------------+     +------------------+     +------------------+

四、选型速查表

场景 经典方案 Spring方案 为什么选Spring方案
多算法替换 策略模式 + 工厂 @Autowired Map 自动发现 无需手动注册,加类即生效
日志/事务/权限横切 静态代理/装饰器 @Aspect AOP 零侵入,配置化
复杂查询模板 模板方法(继承) JdbcTemplate(回调) 组合优于继承,Lambda简洁
模块间通信 观察者模式(手动维护) ApplicationEvent 内置异步、事务绑定、条件过滤
环境适配 if-else判断 @Conditional 系列 声明式,可扩展自定义条件
依赖管理 手动new/工厂 IoC容器 + @Autowired 生命周期托管、循环依赖解决、Mock友好

五、写在最后

设计模式不是圣经,是前人踩坑后的路标

GoF 23将发布于1994年——那时还没有Spring,没有注解,没有Lambda。30年后的今天,Spring框架把这些模式的精髓吸收进来,用更现代的方式重新演绎:

  • 策略模式 → @Autowired 自动装配 + Map<String, T> 策略注册表
  • 模板方法 → JdbcTemplate / RestTemplate / RedisTemplate 回调式模板
  • 观察者模式 → ApplicationEvent 发布订阅机制
  • 工厂模式 → BeanFactory / @Configuration + @Bean
  • 代理模式 → Spring AOP 动态代理(JDK/CGLIB)

学设计模式,不要背UML图。打开Spring源码,看看BeanFactory怎么管理生命周期,AbstractApplicationContext怎么刷新容器,DefaultAdvisorChainFactory怎么构建AOP拦截器链——最好的设计模式课,就在你每天都在用的框架里

记住:代码写得像个笑话没关系,只要设计模式用得正经。

更多推荐