跳到内容
Caiden's Blog
返回

Spring在业务开发中常用技巧

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 来完成策略模式:

image-20250320205138056

我们可以定义一个 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 可以做很多事情:

这里实现一个简单的方法日志记录注解,搭配 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,不过需要注意以下几点:

  1. 最好显式设置rollbackFor参数,来指定需要回滚的异常。
  2. 此注解依赖 AOP 来管理事务,所以如果方法调用是同一个类中调用,事务管理器是没法生效的(因为没经过代理对象,是直接调用的 this当前对象)。
@Service
public class OrderService {

    @Transactional(rollbackFor = Exception.class)
    public void transactionTest(Long userId, Double amount, String description) {

    }
}

分享到:

上一篇
Prompt Engineering(提示词工程)
下一篇
AI 大模型相关概念及工具总结