Java设计模式:从老干部到Spring潮人
本文里的设计模式分两类:经典老干部(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拦截器链——最好的设计模式课,就在你每天都在用的框架里。
记住:代码写得像个笑话没关系,只要设计模式用得正经。
更多推荐
所有评论(0)