AOP 基本概念的理解
面向切面 AOP 主要是在编译期或运行时, 对程序进行织入, 实现代理,
对原代码毫无侵入性, 不破坏主要业务逻辑, 减少程序的耦合度.
主要应用范围:
日志记录, 性能统计, 安全控制, 事务处理, 异常处理等等
名词性概念
切面 (Aspect)
通常是一个类, 在里面可以定义切入点和通知, 即
切面 = 切入点 + 通知
.
连接点 (Joint Point)
被拦截到的点, 因为 Spring 只支持方法类型的连接点, 所以在 Spring 中连接点指的就是被拦截的到的方法, 实际上连接点还可以是字段或者构造器.
切入点 (Pointcut)
对连接点进行拦截的定义.
通知 (Advice)
拦截到连接点之后所要执行的代码, 通知分为前置, 后置, 异常, 最终, 环绕通知五类.
AOP 代理
AOP 框架创建的对象, 代理就是目标对象的加强. Spring 中的 AOP 代理可以使 JDK 动态代理, 也可以是 CGLIB 代理, 前者基于接口, 后者基于子类.
织入 (Weaving)
把切面加入到对象, 并创建出代理对象的过程.
动态代理
在运行期间生成对象进行代理,
spring AOP 就是动态代理.
静态代理
自己编写代理对象, 在编译器织入,
AspectJ 就是在编译期间进行织入, 从而减少对运行时效率的影响.
SpringAOP
根据对象是否是实现类选择代理的方法
如果要代理的对象实现了接口, spring AOP 会根据接口, 使用 JDK Proxy 创建代理
如果没有实现接口, 则通过 CGLIB 代理
当然也可以指定都使用 CGLIB 进行切入, 而 JDK 方法不适用于没有接口实现的目标类
对于 JDK proxy
Jdk proxy 会实现目标类的接口, 代理逻辑就在新的实现类中
只能对实现类进行代理, 性能也优于 CGLIB, 所以平时都是分为 "接口 - 实现 iml" 来设计.
对于 CGLIB
cglib 会创建一个代理目标类的子类, 而代理逻辑就在这一子类中添加,
然后得到这一子类的对象, 也就是代理增强后的对象, 一切都是动态的,
具体实现有待学习.
切面类
使用 @Aspect 注解定义切面类
比如这样
- @Component
- @Aspect
- @Oder(99)
- public class webLogAspect {
- }
另外对同一个方法有多个切面进行代理的时候, 难免需要区分执行顺序,
这时候可以使用 @Order 注解定义优先级, 数字越低, 级别越高.
切面 Advice
执行顺序 around -> before -> around -> after -> afterRuternning
@After
第一个参数都必须是 JoinPoint 类型
在目标方法完成后通知,
无论方法是以何种方式完成, 异常也是如此
@After-returning
第一个参数都必须是 JoinPoint 类型
结束并安全返回时通知, 异常不通知
@After-throwing
第一个参数都必须是 JoinPoint 类型
异常时通知
@Before
第一个参数都必须是 JoinPoint 类型
顾名思义
@Around
最强大的通知
第一个形参必须是 ProceedingJoinPoint 类型,
- public interface ProceedingJoinPoint extends JoinPoint {
- void set$AroundClosure(AroundClosure var1);
- default void stack$AroundClosure(AroundClosure arc) {
- throw new UnsupportedOperationException();
- }
- Object proceed() throws Throwable;
- Object proceed(Object[] var1) throws Throwable;
- }
可以看到它继承自 JoinPoint, 多了 proceed 方法
注解的方法体内调用 ProceedingJoinPoint 参数的 proceed() 方法才会执行目标.
调用这个方法时, 还可以传入一个 Object[] 对象作为参数
可以通过这个切面决定方法是否执行, 改变传入参数, 改变返回值, 检查异常等.
参数方法
- getArgs(): Returns the method arguments.
- getThis(): Returns the proxy object.
- getTarget(): Returns the target object.
- getSignature(): Returns a description of the method that is being advised.
- toString(): Prints a useful description of the method being advised.
切点表达式
有关切点表达式, 建议阅读 传送门 https://www.jianshu.com/p/a0b9c53ac019
可以定义一个切点, 之后就不用一一指定, 直接带入 value 即可, 例如:
- @Pointcut("execution(* com.hyg.app.controller..*.*(..))")
- public void webLog(){}
- @Before(value = "webLog()")
- public void doBefore (JoinPoint jp){
- String name = jp.getSignature().getName();
- System.out.println(name+"开始执行");
- }
- execution
表达式中最常用的方法是 execution, 粒度最小
对于 execution(* com.hyg.App.controller..*.*(..))
execution 表达式的类型指定
第一个 * 代表 任意的返回值类型
com.jiuxian aop 所切的包名
包后面.. 表示当前包及其子包, 一个. 是当前包, 两个. 就包括所有子包
第二个 * 表示类名, 代表所有类
.*(..) 表示任何方法, 括号代表参数 .. 表示任意参数
后面那串东西, 可以记为每一个. 都是更深的一个粒度
来个精确点的示例:
execution(* com.hyg.App.service.*Service.add*(String))
表示所有类型的, com.hyg.App.service 包下的,
所有以 Service 结尾的类,
以 add 开头的, 参数类型为 String 的方法
例子
在官方文档中有很多示例, 如下
所有 public 方法
execution(public * *(..))
所有名字以 set 开头的方法
execution(* set*(..))
所有 AccountService 中实现的接口的方法
execution(* com.xyz.service.AccountService.*(..))
所有 service 包下的方法
execution(* com.xyz.service.*.*(..))
所有在 service 包以及子包下的方法
execution(* com.xyz.service..*.*(..))
within(com.xyz.service.*) 所有 service 包下方法
within(com.xyz.service..*) 所有 service 包和子包下方法
this(com.xyz.service.AccountService) 匹配 accountservice 代理的对象
target(com.xyz.service.AccountService) 实现了 AccountService 接口的对象
拥有 transactional 注解的
@annotation(org.springframework.transaction.annotation.Transactional)
传递的参数是 Serializable 的
args(java.io.Serializable):
关于 execution 和 within 的区别
execution 可以指定方法返回类型, 类名, 方法名和参数名等与方法相关的部件,
而 within 的最小粒度是类.
关于 this 和 target 的区别
this 匹配的是代理类, 即目标对象被代理后的代理对象
target 则是匹配普通的目标对象
以下情况外, 二者的效果都是一致的.
this(SomeObject) 或 target(SomeObject), 这里 SomeObject 实现了某个接口: 对于这种情况, 虽然表达式中指定的是一种具体的对象类型, 但由于其实现了某个接口, 因而 Spring 默认会使用 Jdk 代理为其生成代理对象, Jdk 代理生成的代理对象与目标对象实现的是同一个接口, 但代理对象与目标对象还是不同的对象, 由于代理对象不是 SomeObject 类型的, 因而此时是不符合 this 语义的, 而由于目标对象就是 SomeObject 类型, 因而 target 语义是符合的, 此时 this 和 target 的效果就产生了区别; 这里如果强制 Spring 使用 Cglib 代理, 因而生成的代理对象都是 SomeObject 子类的对象, 其是 SomeObject 类型的, 因而 this 和 target 的语义都符合, 其效果就是一致的.
args
用于指定参数类型, 类型必须是全路径的
官网解释
- execution: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.
- within: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).
- this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
- target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.
- args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.
- @target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.
- @args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.
- @within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).
- @annotation: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.
--- 官方文档 ---
我的博客: https://www.seyana.life/post/12
来源: https://www.cnblogs.com/mckc/p/12449067.html