AOP回顾

Aop

与OOP对比,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,但是这些问题和业务关系不大,也不会影响主逻辑,但是会散落到代码的各个部分,难以维护。而AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。

应用场景:日志记录、权限验证、效率检查、事务管理、exception。

JDK动态代理 CGLIB代理
编译时期的织入还是运行时期的织入? 运行时期织入 运行时期织入
初始化时期织入还是获取对象时期织入? 初始化时期织入 初始化时期织入

SpringAop和AspectJ的关系

Aop是一种概念,springAop、AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是自己的。

spring AOP提供两种编程风格

1
2
@AspectJ support        利用aspectj的注解
Schema-based AOP support xml aop:config 命名空间

Spring Aop的概念

1
2
3
4
5
6
7
aspect:一定要给spring去管理  
pointcut:切点,表示连接点的集合
Joinpoint:连接点 目标对象中的方法
Weaving:织入,把代理逻辑加入到目标对象上的过程
target:目标对象 原始对象
aop Proxy 代理对象 包含了原始对象的代码和增加后的代码的那个对象,如果是cglib则都一致。
advice:通知

advice通知类型

1
2
3
4
5
6
7
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。

@DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。

Proceedingjoinpoint 和JoinPoint的区别

Proceedingjoinpoint 继承了JoinPoint,并扩充实现了proceed()方法,用于继续执行连接点。JoinPoint仅能获取相关参数,无法执行连接点。

JoinPoint的方法

1
2
3
4
5
1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
2.Signature getSignature() :获取连接点的方法签名对象;
3.java.lang.Object getTarget() :获取连接点所在的目标对象;
4.java.lang.Object getThis() :获取代理对象本身;
proceed()有重载,有个带参数的方法,可以修改目标方法的的参数

Introductions

通过将需要添加的新的行为逻辑,以新的接口定义增加到目标对象上。以@Aspect声明一个实列变量,它的类型对应的是新增加的接口类型,然后通过DeclareParents对其进行标注。通过@DeclareParents指定新接口定义的实现类以及将要加诸其上的目标对象。简单来说就是可以扩展原有的目标对象。

例如,给定名为的接口UsageTracked和名为的接口的实现DefaultUsageTracked,新声明服务接口的所有实现者也都实现该UsageTracked接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Aspect
public class UsageTracking {

@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;

@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}

}

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

切面模型 perthis

perthis:每个切入点表达式匹配的连接点对应的AOP对象都会创建一个新的切面实例,使用@Aspect(“perthis(切入点表达式)”)指定切入点表达式;默认的情况下多个连接点对应的切面对象是一个。

使用方式如下:

@Aspect(“perthis(com.xyz.myapp.SystemArchitecture.businessService())”)

1
2
1. AspectJ对象的注入类型为prototype
2. 目标对象也必须是prototype的

原因:只有目标对象是原型模式的,每次getBean得到的对象才是不一样的,由此针对每个对象就会产生新的切面对象,才能产生不同的切面结果。

SpringAop支持AspectJ

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-aspectj-support

启用@AspectJ支持

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

XML配置启用@AspectJ支持:aop:aspectj-autoproxy/

声明一个Aspect

1
2
3
4
@Component
@Aspect
public class UserAspect {
}

声明切点pointCut

1
2
3
4
5
6
7
8
9
10
11
12
@Pointcut("execution(* transfer(..))")// 切入点表达式
private void anyOldTransfer() {}// 切入点签名

/**
* 申明切入点,匹配UserDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")

Advice通知

1
2
3
4
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}

AspectJ切入点指示符(PCD):

execution

用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。最小粒度方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;

example:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
within

将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。

1
2
3
within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
this

限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。

JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy)

target

在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。

1
2
3
4
5
6
7
8
9
10
11
如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理
JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。

而CGLIB继承被代理的类来实现。
所以使用target会保证目标不变,关联对象不会受到这个设置的影响。
但是使用this对象时,会根据该选项的设置,判断是否能找到对象。

@Pointcut("target(com.chenss.dao.IndexDaoImpl)")//目标对象,也就是被代理的对象。限制目标对象为com.chenss.dao.IndexDaoImpl类
@Pointcut("this(com.chenss.dao.IndexDaoImpl)")//当前对象,也就是代理对象,代理对象时通过代理目标对象的方式获取新的对象,与原值并非一个
@Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目标对象中的任意方法
@Pointcut("@within(com.chenss.anno.Chenss)")//等同于@targ
args

在参数为给定类型的实例的情况下,将匹配指定参数类型和指定参数数量的方法,与包名和类名无关。

1
2
3
4
5
6
7
/**
* args同execution不同的地方在于:
* args匹配的是运行时传递给方法的参数类型
* execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
*/
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified
@target

在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。

@args

限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。

@within

将匹配限制为具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释的类型中声明的方法的执行)。

@annotation

将匹配限制在连接点的主题(Spring AOP中正在执行的方法)具有给定注释的连接点上。

1
2
3
4
5
作用方法级别 上述所有表达式都有@ 比如@Target(里面是一个注解类xx,表示所有加了xx注解的类,和包名无关)

注意:上述所有的表达式可以混合使用,|| && !

@Pointcut(" @annotation(org.springframework.transaction.annotation.Transactional)")//匹配带有org.springframework.transaction.annotation.Transactional注解的方法