在说解决方案前,首先我们要看下与切面相关的几个定义
JoinPoint: 程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,或者是执行方法中异常抛出时,也可以是属性被修改时等时机,在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的
Pointcut: JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,这就需要某些条件来筛选出那些需要被织入的 JoinPoint,Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述) 来定位到匹配的 joinpoint
Advice: 代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,主要有五个织入时机
-
Before Advice: 在 JoinPoints 执行前织入
-
After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入)
-
After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入)
-
After throwing advice: 方法执行过程中抛出异常后织入
-
Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了 综上所述,切面(Aspect)我们可以认为就是 pointcut 和 advice,pointcut 指定了哪些 joinpoint 可以被织入,而 advice 则指定了在这些 joinpoint 上的代码织入时机与逻辑
画外音:织入(weaving),将切面作用于委托类对象以创建 adviced object 的过程(即代理,下文会提)。
例如:
首先我们先定义一个如下注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {
}
然后将所有 service 中方法里的 「try... catch...」移除掉,在方法签名上加上上述我们定义好的注解
public class TestServiceImpl implements TestService {
@Override
@GlobalErrorCatch
public ServiceResultTO<Boolean> test() {
// 此处写服务里的执行逻辑
boolean result = xxx;
return ServiceResultTO.buildSuccess(result);
}
}
然后再指定注解形式的 pointcuts 及 around advice
@Aspect
@Component
public class TestAdvice {
// 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut
@Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)")
private void globalCatch(){}
// 2. 将 around advice 作用于 globalCatch(){} 此 PointCut
@Around("globalCatch()")
public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable {
try {
return point.proceed();
} catch (Exception e) {
System.out.println("执行错误" + e);
return ServiceResultTO.buildFailed("系统错误");
}
}
}
通过这样的方式,所有标记着 GlobalErrorCatch 注解的方法都会统一在 handlerGlobalResult 方法里执行,我们就可以在这个方法里统一 catch 住异常,所有 service 方法中又长又臭的 「try...catch...」全部干掉。