Spring 在业务开发中常用技巧:IOC 实现策略模式、AOP 实现拦截、Event 异步解耦、事务管理
1. IOC 实现策略模式
这个是平时开发中一个常用的技巧,有了 Spring 的 IOC 我们可以用很少的代码就实现策略模式。
现在我们需要开发一个接口,接口可以处理不同类型的数据,所以我们就需要根据不同的数据类型,写不同的处理方法。
普通的逻辑就是写 if-else,参考以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Slf4j public class DataHandlerOld {
void handleData(DataType dataType, String data) { if (dataType == DataType.TYPE_A) { handleTypeAData(data); } else if (dataType == DataType.TYPE_B) { handleTypeBData(data); } else { log.warn("can not handle data type {}", dataType); } }
private void handleTypeBData(String data) { log.info("handleTypeBData {}", data); }
private void handleTypeAData(String data) { log.info("handleTypeAData {}", data); } }
|
如果后面数据类型越来越多,这种 if-else 显然很不合适,过于耦合,且违反开闭原则,所以可以借助 Spring 来完成策略模式:

我们可以定义一个 DataHandler 接口,里面定义 handleData 处理数据的方法;
getSupportDataType 方法返回当前处理器可处理的数据类型;
afterPropertiesSet 是在 bean 属性值设置完成后的初始化操作(当然也可以用 BeanPostProcess,有很多种方式),初始化的时候调用 getSupportDataType 把这个数据类型作为 key,value 是 this 即当前实例对象,就完成了「数据类型 -> 数据处理器」的映射关系组装。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class DataHandlerFactory {
private static final Map<DataType, DataHandlerFacade> DATA_HANDLER_FACADE_MAP = new HashMap<>();
public static void register(DataType dataType, DataHandlerFacade dataHandlerFacade) { DATA_HANDLER_FACADE_MAP.put(dataType, dataHandlerFacade); }
public static DataHandlerFacade get(DataType dataType) { return DATA_HANDLER_FACADE_MAP.get(dataType); } }
public interface DataHandlerFacade extends InitializingBean {
void handleData(String data);
DataType getSupportDataType();
@Override default void afterPropertiesSet() throws Exception { DataHandlerFactory.register(getSupportDataType(), this); }
}
@Slf4j @Component public class TypeADataHandler implements DataHandlerFacade{ @Override public void handleData(String data) { log.info("TypeADataHandler handle data: {}", data); }
@Override public DataType getSupportDataType() { return DataType.TYPE_A; } }
@Slf4j @Component public class TypeBDataHandler implements DataHandlerFacade { @Override public void handleData(String data) { log.info("TypeBDataHandler handle data: {}", data); }
@Override public DataType getSupportDataType() { return DataType.TYPE_B; } }
|
使用:
1 2 3 4 5 6 7 8 9 10 11 12
| @Test void iocTest() { String testData = "test"; DataHandlerFacade typeAHandler = DataHandlerFactory.get(DataType.TYPE_A); typeAHandler.handleData(testData);
DataHandlerFacade typeBHandler = DataHandlerFactory.get(DataType.TYPE_B); typeBHandler.handleData(testData); }
2025-03-20 21:01:53.825 INFO 18720 --- [ main] f.p.newms.spring.ioc.TypeADataHandler : TypeADataHandler handle data: test 2025-03-20 21:01:53.826 INFO 18720 --- [ main] f.p.newms.spring.ioc.TypeBDataHandler : TypeBDataHandler handle data: test
|
符合开闭原则:如果新增数据类型,只需要新增一个 DataHandlerFacade 的实现类即可,无需修改原有代码;
这里主要用到了 Spring 的 IOC 中的 Bean 生命周期的技巧,在 Bean 实例化的过程中,利用 afterPropertiesSet
“钩子函数”来达到“自动注册”的效果!
2. AOP 实现日志记录
AOP 可以做很多事情:
- 日志记录:记录方法调用、参数、返回值、异常等信息;
- 性能监控:统计方法执行时间、资源消耗;
- 安全控制:权限验证、身份认证;Spring Security 就是大量使用 AOP 实现方法级别的安全控制如
@PreAuthorize
, @PostAuthorize
, @Secured
- 缓存:缓存方法返回值;Spring 也用 AOP 实现了如 @Cacheable;
- 事务管理:Spring 声明式事务完全基于 AOP ,参考 @Transactional 注解;
- 参数校验: Spring Validation 结合了 JSR 303/349 规范和 AOP 来实现参数校验如@Validated` 注解
这里实现一个简单的方法日志记录注解,搭配 AOP 记录方法入参、返回值及执行时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface EnableLog { }
@Slf4j @Aspect @Component public class LogAspect {
@Pointcut("@annotation(fun.powercheng.newms.spring.aop.EnableLog)") public void logPointcut() { }
@Around("logPointcut()") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().toShortString(); Object[] args = joinPoint.getArgs();
log.info("方法 {} 开始执行, 参数: {}", methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis(); Object result = null; try { result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); long elapsedTime = endTime - startTime; log.info("方法 {} 执行完成, 返回值: {}, 执行耗时: {} ms", methodName, result, elapsedTime); return result;
} catch (Throwable e) { long endTime = System.currentTimeMillis(); long elapsedTime = endTime - startTime; log.error("方法 {} 执行异常, 异常信息: {}, 执行耗时: {} ms", methodName, e.getMessage(), elapsedTime); throw e; } } }
@Service public class AopTestService {
@EnableLog public String test(String param) throws InterruptedException { Thread.sleep(3_000); return "test " + param + " success!"; } }
|
测试:
1 2 3 4 5
| @Test void aopTest() throws InterruptedException { String testData = "hello"; aopTestService.test(testData); }
|
结果日志:
1 2
| 2025-03-20 21:16:32.015 INFO 22352 --- [ main] f.powercheng.newms.spring.aop.LogAspect : 方法 AopTestService.test(..) 开始执行, 参数: [hello] 2025-03-20 21:16:35.031 INFO 22352 --- [ main] f.powercheng.newms.spring.aop.LogAspect : 方法 AopTestService.test(..) 执行完成, 返回值: test hello success!, 执行耗时: 3015 ms
|
3. 通过 Event 异步解耦
我们业务中也有很多“监听”的操作,比如说有告警了,我们需要记录告警日志、发送告警邮件、发送告警短信等操作,但是我们不能在收到告警之后,就依次记录日志、发送邮件、发送短信,如果告警后面触发的操作越来越多,这个类也就会越来越大,对于这种情况,我们就需要用Event 异步解耦,参考以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @ToString @Getter public class AlarmEvent extends ApplicationEvent {
private final String message;
private final String severity;
public AlarmEvent(Object source, String message, String severity) { super(source); this.message = message; this.severity = severity; } }
@RequiredArgsConstructor @Service @Slf4j public class AlarmService {
private final ApplicationEventPublisher eventPublisher;
public void sendAlarm(String message, String severity) { log.info("发布告警事件:message={}, severity={}", message, severity); AlarmEvent alarmEvent = new AlarmEvent(this, message, severity); eventPublisher.publishEvent(alarmEvent); }
}
@Slf4j @Component public class LogAlarmEventListener {
@EventListener public void onListenAlarmEvent(AlarmEvent alarmEvent) { log.info("收到告警,记录日志...: {}", alarmEvent); } }
@Slf4j @Component public class MailAlarmEventListener {
@EventListener public void onListenAlarmEvent(AlarmEvent alarmEvent) { log.info("收到告警,发送邮件...: {}", alarmEvent); } }
|
4. 通过 Spring 管理事务
最常见的就是在方法上使用注解@Transactional
,不过需要注意以下几点:
- 最好显式设置
rollbackFor
参数,来指定需要回滚的异常。 - 此注解依赖 AOP 来管理事务,所以如果方法调用是同一个类中调用,事务管理器是没法生效的(因为没经过代理对象,是直接调用的 this当前对象)。
1 2 3 4 5 6 7 8
| @Service public class OrderService {
@Transactional(rollbackFor = Exception.class) public void transactionTest(Long userId, Double amount, String description) {
} }
|