如何使用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
相关产品推荐
相关产品推荐

