Spring 在业务开发中常用技巧:IOC 实现策略模式、AOP 实现拦截、Event 异步解耦、事务管理
1. IOC 实现策略模式
这个是平时开发中一个常用的技巧,有了 Spring 的 IOC 我们可以用很少的代码就实现策略模式。
现在我们需要开发一个接口,接口可以处理不同类型的数据,所以我们就需要根据不同的数据类型,写不同的处理方法。
普通的逻辑就是写 if-else,参考以下代码:
@Slf4j
public class DataHandlerOld {
/**
* 模拟不同类型的数据,采用不同的处理方法
*
* @param dataType 数据类型
* @param data 数据
*/
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 即当前实例对象,就完成了「数据类型 -> 数据处理器」的映射关系组装。
完整代码如下:
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;
}
}
使用:
@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 记录方法入参、返回值及执行时间。
@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!";
}
}
测试:
@Test
void aopTest() throws InterruptedException {
String testData = "hello";
aopTestService.test(testData);
}
结果日志:
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 异步解耦,参考以下代码:
@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 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当前对象)。
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void transactionTest(Long userId, Double amount, String description) {
}
}