嘿,各位码农同胞们!
每天用着 @Autowired
、@Service
、@Transactional
,你有没有想过,这些看似简单的注解背后,其实隐藏着软件工程几十年来的智慧结晶——设计模式?
我们常常把“写出优雅、可维护、可扩展的代码”挂在嘴边,但一到实际开发,代码就容易变成一坨难以维护的“面条”。究其原因,往往是因为我们缺少了“内功心法”,而设计模式,就是这套心法。
Spring Framework 和 Spring Boot 本身就是设计模式的集大成者。它不仅在内部大量使用了经典的设计模式,更通过其强大的 IoC 和 AOP 容器,让我们能够极其方便地在自己的业务代码中应用这些模式。
目录
一. 代理模式 (Proxy Pattern):万物皆可“代理”的 Spring AOP
二. 工厂模式 (Factory Pattern):Bean 的“制造工厂”
实战代码:使用 FactoryBean 创建一个复杂的支付客户端
三. 装饰器模式 (Decorator Pattern):给对象动态“穿上”新功能
四. 策略模式 (Strategy Pattern):自由切换的算法“锦囊”
一. 代理模式 (Proxy Pattern):万物皆可“代理”的 Spring AOP
一句话概括:为你真正的目标对象创建一个代理“替身”,客户端访问的是这个替身,从而让你有机会在不修改原对象代码的情况下,对访问进行控制或增强。
这就像你想见一位大明星(目标对象),但你不能直接联系他,而是要通过他的经纪人(代理)。经纪人可以帮你过滤掉不重要的请求、安排日程、甚至在你见面前做一些准备工作。
Spring Boot 中的体现
代理模式是 Spring AOP (面向切面编程) 的核心。当你使用 @Transactional
、@Async
、@Cacheable
甚至自定义的切面时,Spring 并没有改变你原有类的代码。相反,它在运行时为你创建了一个代理对象。
当你从容器中获取这个 Bean(比如通过 @Autowired
)时,你拿到的其实是这个代理。当你调用 Bean 的方法时,实际上是代理在执行。代理会先执行它自己的增强逻辑(比如开启事务、检查缓存),然后再去调用你真正的业务方法,最后再执行一些收尾工作(比如提交/回滚事务)。
架构图解
实战代码:自定义一个方法耗时监控切面
让我们来写一个简单的 AOP 切面,用于记录所有 Service 层方法的执行时间。
1. 添加 AOP 依赖 在 pom.xml
中确保有 spring-boot-starter-aop
。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 创建一个注解
// PerformanceLog.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceLog {}
3. 编写切面类
// PerformanceLogAspect.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceLogAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceLogAspect.class);
// 环绕通知,拦截所有标注了 @PerformanceLog 的方法
@Around("@annotation(PerformanceLog)")
public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
// 调用原始方法
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
log.info("Method {} executed in {} ms", pjp.getSignature(), (endTime - startTime));
return result;
}
}
4. 在业务代码中使用
// MyBusinessService.java
import org.springframework.stereotype.Service;
@Service
public class MyBusinessService {
@PerformanceLog
public void doSomething() {
try {
// 模拟业务耗时
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Doing something important...");
}
}
现在,每次调用 doSomething
方法时,Spring 都会通过代理先执行 logPerformance
的逻辑,你会在控制台看到方法的执行耗时,而 MyBusinessService
本身完全无感知。
二. 工厂模式 (Factory Pattern):Bean 的“制造工厂”
一句话概括:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
你想要一杯咖啡,你不会自己去种咖啡豆、研磨、冲泡,而是去咖啡店(工厂),告诉店员你要拿铁还是美式,店员(工厂方法)会为你做好。你不用关心咖啡的具体制作过程。
Spring Boot 中的体现
整个 Spring 的 IoC 容器本质上就是一个巨大的、超级灵活的工厂。ApplicationContext
就是这个工厂的门面。当你调用 applicationContext.getBean("myBean")
时,你就是在命令工厂生产一个叫 "myBean" 的产品。
Spring 会根据你的配置(XML、注解)来决定如何创建这个 Bean:是创建一个全新的实例(Prototype),还是返回一个已经存在的单例(Singleton)。
更具体的体现是 FactoryBean
接口。如果你想让一个 Bean 的创建过程变得非常复杂,或者想在创建前后做一些特殊处理,你可以实现 FactoryBean
接口。这个 Bean 自己就成了一个小工厂,专门用来生产另一种类型的 Bean。
架构图解
实战代码:使用 FactoryBean
创建一个复杂的支付客户端
假设我们需要根据配置来创建不同的支付客户端(比如 AliPayClient
或 WeChatPayClient
)。
1. 定义产品接口和实现
// PaymentClient.java
public interface PaymentClient {
void pay(long amount);
}
// AliPayClient.java
public class AliPayClient implements PaymentClient {
public AliPayClient(String endpoint, String appId) { /* ... complex initialization ... */ }
@Override public void pay(long amount) { System.out.println("AliPay paying: " + amount); }
}
// WeChatPayClient.java
public class WeChatPayClient implements PaymentClient {
public WeChatPayClient(String apiKey, String mchId) { /* ... complex initialization ... */ }
@Override public void pay(long amount) { System.out.println("WeChatPay paying: " + amount); }
}
2. 实现 FactoryBean
// PaymentClientFactoryBean.java
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("paymentClient") // 注意这里的 Bean 名字
public class PaymentClientFactoryBean implements FactoryBean<PaymentClient> {
@Value("${payment.type}")
private String type;
@Override
public PaymentClient getObject() throws Exception {
// 在这里实现复杂的创建逻辑
if ("alipay".equalsIgnoreCase(type)) {
return new AliPayClient("alipay.com", "app123");
} else if ("wechat".equalsIgnoreCase(type)) {
return new WeChatPayClient("wechat_key", "mch456");
} else {
throw new IllegalArgumentException("Unsupported payment type");
}
}
@Override
public Class<?> getObjectType() {
return PaymentClient.class;
}
@Override
public boolean isSingleton() {
return true; // 我们希望它是单例的
}
}
3. 配置和使用 在 application.properties
中:
payment.type=alipay
在业务代码中,你可以像注入普通 Bean 一样注入 PaymentClient
:
// OrderService.java
@Service
public class OrderService {
private final PaymentClient paymentClient;
// Spring 注入的不是 FactoryBean 本身,而是它 getObject() 返回的对象
public OrderService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
public void placeOrder(long amount) {
paymentClient.pay(amount);
}
}
现在,你只需要修改配置文件里的 payment.type
,OrderService
就能无缝切换支付方式,完全解耦了创建和使用。
三. 装饰器模式 (Decorator Pattern):给对象动态“穿上”新功能
一句话概括:在不改变原有对象接口的情况下,动态地给一个对象添加一些额外的职责。就功能而言,装饰器模式相比生成子类更为灵活。
想象一个“套娃”,你有一个核心的娃娃(原对象),然后你可以一层一层地给它套上更大的娃娃(装饰器),每一层都给它增加一点新的外观,但它本质上还是那个娃娃。
Spring Boot 中的体现
装饰器模式在 Spring 核心中不那么显眼,但在其依赖的技术中非常常见。最经典的例子是 Java IO 类,比如 new BufferedReader(new FileReader("file.txt"))
。
在 Spring Web 体系中,HttpServletRequestWrapper
和 HttpServletResponseWrapper
就是标准的装饰器。你可以创建一个 Wrapper 来修改原始请求或响应的行为,比如修改 Header,或者对输出内容进行压缩。
此外,Spring Session 使用装饰器模式来包装 HttpSession
,以实现分布式会话管理。
架构图解
实战代码:一杯咖啡的“七十二变”
我们来模拟一个制作咖啡的场景,基础是一杯清咖啡,然后可以动态地加奶、加糖。
1. 定义组件接口和基础实现
// Coffee.java
public interface Coffee {
double getCost();
String getDescription();
}
// SimpleCoffee.java
public class SimpleCoffee implements Coffee {
@Override public double getCost() { return 10.0; }
@Override public String getDescription() { return "Simple Coffee"; }
}
2. 创建抽象装饰器和具体装饰器
// CoffeeDecorator.java
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public double getCost() { return decoratedCoffee.getCost(); }
public String getDescription() { return decoratedCoffee.getDescription(); }
}
// MilkDecorator.java
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
@Override public double getCost() { return super.getCost() + 3.0; }
@Override public String getDescription() { return super.getDescription() + ", with Milk"; }
}
// SugarDecorator.java
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) { super(coffee); }
@Override public double getCost() { return super.getCost() + 1.5; }
@Override public String getDescription() { return super.getDescription() + ", with Sugar"; }
}
3. 在 Spring Service 中使用
// CoffeeShopService.java
@Service
public class CoffeeShopService {
public void orderCoffee() {
// 点一杯基础咖啡
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " costs $" + coffee.getCost());
// 加奶
Coffee milkCoffee = new MilkDecorator(coffee);
System.out.println(milkCoffee.getDescription() + " costs $" + milkCoffee.getCost());
// 再加糖
Coffee sweetMilkCoffee = new SugarDecorator(milkCoffee);
System.out.println(sweetMilkCoffee.getDescription() + " costs $" + sweetMilkCoffee.getCost());
// 也可以这样套娃:一杯加了双份奶和一份糖的咖啡
Coffee myFavoriteCoffee = new SugarDecorator(new MilkDecorator(new MilkDecorator(new SimpleCoffee())));
System.out.println(myFavoriteCoffee.getDescription() + " costs $" + myFavoriteCoffee.getCost());
}
}
这种方式让你能以任意组合来扩展功能,而不需要创建 CoffeeWithMilk
、CoffeeWithSugar
、CoffeeWithMilkAndSugar
等等无穷无尽的子类。
四. 策略模式 (Strategy Pattern):自由切换的算法“锦囊”
一句话概括:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。此模式让算法的变化独立于使用算法的客户。
你出门旅游,可以根据路况和预算选择不同的出行策略:坐飞机(速度快,价格高)、坐火车(性价比高)、自驾(自由度高)。你的目的地不变,但到达目的地的策略可以随时切换。
Spring Boot 中的体现
策略模式是在 Spring 应用中最常被开发者主动使用的模式之一,因为 Spring 的依赖注入(DI)简直是为它量身定做的。
最经典的场景就是支付。一个电商系统需要支持多种支付方式:支付宝、微信支付、信用卡支付。你可以定义一个 PaymentStrategy
接口,然后为每种支付方式创建一个实现类。
在你的支付服务中,你不需要写一堆 if-else
来判断用哪种支付方式。你可以直接注入一个 Map<String, PaymentStrategy>
,Spring 会自动把所有实现了 PaymentStrategy
接口的 Bean 收集起来,以它们的 Bean Name 为 Key 放入 Map。这样,你只需要根据传入的支付类型字符串,就能从 Map 中动态获取并执行对应的策略。
架构图解
实战代码:优雅地实现多种支付方式
1. 定义策略接口和具体策略
// PaymentStrategy.java
public interface PaymentStrategy {
String getPayType();
void pay(double amount);
}
// AliPaymentStrategy.java
@Component
public class AliPaymentStrategy implements PaymentStrategy {
@Override public String getPayType() { return "alipay"; }
@Override public void pay(double amount) { System.out.println("Processing AliPay payment of " + amount); }
}
// WechatPaymentStrategy.java
@Component
public class WechatPaymentStrategy implements PaymentStrategy {
@Override public String getPayType() { return "wechat"; }
@Override public void pay(double amount) { System.out.println("Processing WeChat Pay payment of " + amount); }
}
2. 创建策略的“上下文”(即使用者)
// PaymentService.java
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategyMap;
// Spring 会注入所有 PaymentStrategy 类型的 Bean
public PaymentService(List<PaymentStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PaymentStrategy::getPayType, Function.identity()));
}
public void processPayment(String payType, double amount) {
PaymentStrategy strategy = strategyMap.get(payType);
if (strategy == null) {
throw new IllegalArgumentException("Invalid payment type: " + payType);
}
strategy.pay(amount);
}
}
看,PaymentService
里没有任何 if-else
。如果你想增加一种新的支付方式,比如“信用卡支付”,你只需要新增一个 CreditCardPaymentStrategy
类并实现接口,不需要修改 PaymentService
的任何代码!这完美符合“开闭原则”。
结论:设计模式是内功,Spring 是神兵利器
-
代理模式 让你无侵入地增强代码,是 AOP 的基石。
-
工厂模式 将创建与使用解耦,是 IoC 容器的核心思想。
-
装饰器模式 提供了比继承更灵活的功能扩展方式。
-
策略模式 让你可以优雅地管理和切换算法,告别
if-else
地狱。
so,下次当你写代码时,不妨停下来想一想:这里是不是可以用某种设计模式来优化一下?当你开始这样思考时,你就走在了通往“代码艺术家”的路上。
now,打开你的 IDE,去重构那段让你头疼的“面条代码”吧!