【Spring】AOP_LemmonTreelss的博客-CSDN博客
一、前言
之前简单聊过AOP的一些概念。今天再次深入学习一下。现在大部分项目都是基于微服务框架实现的。比如dubbo、spring cloud。我们作为提供方,负责某一个微服务,这个微服务里会有多个方法,为了不给调用方埋坑。我们针对对每一个对外提供的方法都进行「try …catch…」操作。根据返回码判断正常异常情况。而不是直接把异常信息抛给调用方。
如果每一个对外提供的方法都进行「try …catch…」操作。技术上可行,但是代码比较丑陋,可读性较差。如果改进?
二、AOP概述
可以使用AOP(面向切面)来进行改进。面向对象将程序抽象成各个不同的对象,不同对象各司其职,互不干扰。但是有的时候会出现不同组件(对象)间出现公共的行为。比如每一个方法都进行「try …catch…」。这种场景就可以采用AOP了。在
不修改源代码
的前提下,为系统中不同组件添加通用功能。也就是传说中的无侵入。
AOP采用
横向抽取
机制,取代了
纵向继承体系
重复性代码。
三、AOP详解
1、连接点(Join Point):类里面哪些方法可以被增强,这些方法被称为连接点。比如1个类提供了add、delete、update、select方法。这4个方法都被称为连接点。
2、切入点(Pointcut ):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。比如我只增强add、update方法。实际被增强的方法为切入点
3、增强/通知(Advice):指定这些pointcut被增强的时机,与逻辑。是
切面代码真正被执行的地方
。
Before Advice: 在 JoinPoints 执行前增强
After Advice: 在 JoinPoints 执行后增强(不管是否抛出异常都会增强)
After returning advice: 在 JoinPoints 执行正常退出后增强(抛出异常则不会被增强)
After throwing advice: 方法执行过程中抛出异常后增强
Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可增强切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了
举个生活中的例子,一个小饭店里有10个菜。这10个菜就是JoinPoint。而我只选择了锅包肉,锅包肉就是JoinCut。我们可以统计吃锅包肉的时间、可以选择吃锅包肉之前或之后买单。
通过吃锅包肉时间、买单等与
吃锅包肉这个业务动作
解耦,都是统一写在advice的逻辑里。
talk is cheap, show me your code!
public interface TestService {
// 吃锅包肉
void eatGuoBaoRou();
// 吃蘑菇
void eatMushroom();
// 吃白菜
void eatCabbage();
}
@Component
public class TestServiceImpl implements TestService {
@Override
public void eatGuoBaoRou() {
System.out.println("吃锅包肉");
}
@Override
public void eatMushroom() {
System.out.println("吃蘑菇");
}
@Override
public void eatCabbage() {
System.out.println("吃白菜");
}
}
@Aspect
@Component
public class TestAdvice {
// 1. 定义 PointCut
@Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatGuoBaoRou())")
private void eatGuoBaoRou(){}
// 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中增强逻辑
@Around("eatGuoBaoRou()")
public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
System.out.println("吃锅包肉开始时间");
// 原来的 TestServiceImpl.eatGuoBaoRou 逻辑,可视情况决定是否执行
point.proceed();
System.out.println("吃锅包肉结束时间");
}
}
吃锅包肉开始时间
吃锅包肉
吃锅包肉结束时间
可以看到通过AOP操作,在吃锅包肉方法前后加上相关逻辑。
原有执行逻辑无侵入
。
回到开头的例子又该如何解决呢?
PointCut 的 AspectJ pointcut expression language 声明式表达式,这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint , 比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被增强,使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码
1、定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {
}
2、使用注解
public class TestServiceImpl {
@GlobalErrorCatch
public void test() {
// 此处写服务里的执行逻辑
}
}
3、指定注解形式的pointcut及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…」全部干掉
四、AOP的实现原理
动态代理?