You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Spring AOP在Service层实现注解式抛出业务异常并消除重复代码

实现步骤

1. 定义对应业务异常的自定义校验注解

三个注解分别对应三个业务异常,支持配置方法参数的索引位置,适配不同参数顺序的方法:

// 对应GaNotFoundException的校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckGaExists {
    // 配置gId参数在方法参数列表的索引,默认第0位
    int gIdParamIndex() default 0;
}
// 对应PNExceedLimitException的校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPNLimit {
    int gIdParamIndex() default 0;
    // 配置pN参数在方法参数列表的索引,默认第1位
    int pNParamIndex() default 1;
}
// 对应InPException的校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckInPValid {
    int gIdParamIndex() default 0;
    int pNParamIndex() default 1;
}

2. 实现Ga上下文工具类

避免AOP校验过程中多次查询数据库,暂存查询到的Ga对象:

public class GaContextHolder {
    private static final ThreadLocal<Ga> GA_HOLDER = new ThreadLocal<>();

    public static void set(Ga ga) {
        GA_HOLDER.set(ga);
    }

    public static Ga get() {
        return GA_HOLDER.get();
    }

    public static void clear() {
        GA_HOLDER.remove();
    }
}

3. 实现AOP校验切面

在方法执行前完成所有校验逻辑,不满足条件直接抛出对应业务异常:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;

@Aspect
@Component
public class GaValidateAspect {
    @Resource
    private GaRepository repository;

    // 切点匹配加了三个校验注解的方法
    @Before("@annotation(CheckGaExists) || @annotation(CheckPNLimit) || @annotation(CheckInPValid)")
    public void doValidate(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();

        // 校验Ga是否存在
        CheckGaExists existsAnno = method.getAnnotation(CheckGaExists.class);
        if (existsAnno != null) {
            String gId = (String) args[existsAnno.gIdParamIndex()];
            Ga ga = repository.findById(gId);
            if (ga == null) {
                throw new GaNotFoundException(gId);
            }
            GaContextHolder.set(ga);
        }

        // 校验pN是否超出上限
        CheckPNLimit limitAnno = method.getAnnotation(CheckPNLimit.class);
        if (limitAnno != null) {
            String gId = (String) args[limitAnno.gIdParamIndex()];
            Integer pN = (Integer) args[limitAnno.pNParamIndex()];
            Ga ga = GaContextHolder.get() == null ? repository.findById(gId) : GaContextHolder.get();
            if (ga.getIniVal().get(N_OF_P) <= pN) {
                throw new PNExceedLimitException(gId);
            }
        }

        // 校验P状态是否有效
        CheckInPValid validAnno = method.getAnnotation(CheckInPValid.class);
        if (validAnno != null) {
            String gId = (String) args[validAnno.gIdParamIndex()];
            Integer pN = (Integer) args[validAnno.pNParamIndex()];
            Ga ga = GaContextHolder.get() == null ? repository.findById(gId) : GaContextHolder.get();
            boolean invalid = (PTEnum.P_1.equals(ga.getPT()) && ga.getP1().getP().get(pN).getS() == 0)
                    || (PTEnum.P_2.equals(ga.getPT()) && ga.getP2().getP().get(pN).getS() == 0);
            if (invalid) {
                throw new InPException(gId);
            }
        }
    }

    // 方法执行完清空上下文,避免内存泄漏
    @After("@annotation(CheckGaExists) || @annotation(CheckPNLimit) || @annotation(CheckInPValid)")
    public void clearContext() {
        GaContextHolder.clear();
    }
}

4. 改造原有业务方法

移除重复的校验抛出逻辑,直接加上对应注解即可,还能复用上下文里已经查询到的Ga对象避免重复查库:

@Override
@CheckGaExists
@CheckPNLimit
@CheckInPValid
public GaDTO execute(String gId, Integer pN) {
    Ga ga = GaContextHolder.get();
    GaDTO gaDTO = gaMapper.toDTO(ga);
    rls.forEach(rl -> rl.apply(gaDTO, pN));
    repository.save(gaMapper.toEntity(gaDTO));
    return gaDTO;
}

补充说明

  • 确保Spring配置开启了AOP支持,在启动类/配置类上添加@EnableAspectJAutoProxy注解即可
  • 如果方法的参数顺序和默认的不同,直接修改注解的参数索引即可,例如@CheckGaExists(gIdParamIndex = 2)

内容的提问来源于stack exchange,提问作者Burak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 03:15:10