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) {
      } }
   |