https://www.cnblogs.com/zhangxufeng/p/9160869.html
AOP的主要角色
- Aspect:切面,pointcut+advice;
- Join point:连接点,增强的目标方法,目标对象上切入的具体位置
- Pointcut:切点,用于定义切入哪些位置,定义了连接点集合
- Advice:通知,也即切面逻辑,指定了当前用于切面的业务模块。location+logic
- Advisor:通知器,pointcut+advice
<aop:aspect>和<aop:advisor>:
在面向切面编程时,我们一般会用<aop:aspect>,<aop:aspect>定义切面,大多用于日志、缓存。
在进行事务管理时,我们一般会用<aop:advisor>,<aop:advisor>定义(通知器跟切面一样,也包括通知和切点)。大多用于事务管理。
< aop:aspect>定义切面时,只需要定义一般的bean就行,而定义< aop:advisor>中引用的通知时,通知必须实现Advice接口。
< aop:advisor>和< aop:aspect>其实都是将通知和切面进行了封装,原理基本上是一样的,只是使用的方式不同而已。
Advice的主要类型
- @Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;
- @AfterReturning:该注解标注的方法在业务模块代码执行之后执行;
- @AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;
- @After:该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;
- @Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;
- @DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。
- @Aspect:严格来说,其不属于一种Advice,该注解主要用在类声明上,指明当前类是一个组织了切面逻辑的类,并且该注解中可以指定当前类是何种实例化方式,主要有三种:singleton、perthis和pertarget,具体的使用方式后面会进行讲解。
这里需要说明的是,@Before是业务逻辑执行前执行,与其对应的是@AfterReturning,而不是@After,@After是所有的切面逻辑执行完之后才会执行,无论是否抛出异常。
切点
- execution
由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。
- within
within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。
- args
args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。
- this和target
this和target需要放在一起进行讲解,主要目的是对其进行区别。this和target表达式中都只能指定类或者接口,在面向切面编程规范中,this表示匹配调用当前切点表达式所指代对象方法的对象,target表示匹配切点表达式指定类型的对象。比如有两个类A和B,并且A调用了B的某个方法,如果切点表达式为this(B),那么A的实例将会被匹配,也即其会被使用当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例也即被匹配,其将会被使用当前切点表达式的Advice环绕。
在讲解Spring中的this和target的使用之前,首先需要讲解一个概念:业务对象(目标对象)和代理对象。对于切面编程,有一个目标对象,也有一个代理对象,目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。
在Spring中,其对this的语义进行了改写,即如果当前对象生成的代理对象符合this指定的类型,那么就为其织入切面逻辑。简单的说就是,this将匹配代理对象为指定类型的类。target的语义则没有发生变化,即其将匹配业务对象为指定类型的类。
this和target的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring使用的代理方式主要有两种:Jdk代理和Cglib代理(关于这两种代理方式的讲解可以查看本人的文章代理模式实现方式及优缺点对比)。针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:
如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。
结合上述两点说明,这里理解this和target的异同就相对比较简单了。我们这里分三种情况进行说明:
this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。
- @within
前面我们讲解了within的语义表示匹配指定类型的类实例,这里的@within表示匹配带有指定注解的类。
- @annotation
@annotation的使用方式与@within的相似,表示匹配使用@annotation指定注解标注的方法将会被环绕。
- @args
@within和@annotation分别表示匹配使用指定注解标注的类和标注的方法将会被匹配,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。
- @DeclareParents
@DeclareParents也称为Introduction(引入),表示为指定的目标类引入新的属性和方法。关于@DeclareParents的原理其实比较好理解,因为无论是Jdk代理还是Cglib代理,想要引入新的方法,只需要通过一定的方式将新声明的方法织入到代理类中即可,因为代理类都是新生成的类,因而织入过程也比较方便。
- perthis和pertarget
在Spring AOP中,切面类的实例只有一个,比如前面我们一直使用的MyAspect类,假设我们使用的切面类需要具有某种状态,以适用某些特殊情况的使用,比如多线程环境,涉及到一些共享变量的访问,此时单例的切面类就不符合我们的要求了。在Spring AOP中,切面类默认都是单例的,但其还支持另外两种多例的切面实例的切面,即perthis和pertarget,需要注意的是perthis和pertarget都是使用在切面类的@Aspect注解中的。这里perthis和pertarget表达式中都是指定一个切面表达式,其语义与前面讲解的this和target非常的相似,perthis表示如果某个类的代理类符合其指定的切面表达式,那么就会为每个符合条件的目标类都声明一个切面实例;pertarget表示如果某个目标类符合其指定的切面表达式,那么就会为每个符合条件的类声明一个切面实例。从上面的语义可以看出,perthis和pertarget的含义是非常相似的。
实现原理
AOP实现的关键在于AOP框架自动创建的AOP代理。AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改原来原来的对象,而是在需要的时候重新生成一个代理对象,这个代理对象包含了目标对象的全部方法,并且在指定的切点做了了处理,而且最终还是回调原对象的方法。
动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那就要选择使用CGLIB来动态代理目标类。
CGLIB会让生成的代理类继承当前对象,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。
在CGLIB底层底层实现是通过ASM字节码处理框架来转换字节码并生成新的代理类
JDK动态代理原理
关键源码解析 https://www.cnblogs.com/liuyun1995/p/8144706.html
https://blog.csdn.net/m0_38043362/article/details/80388698
Spring AOP实现
https://blog.csdn.net/convict_eva/article/details/81084833
https://blog.csdn.net/convict_eva/article/details/81101432
https://blog.csdn.net/convict_eva/article/details/81105144
ProxyConfig 管理创建proxy的基本陪配置
TargetClassAware 定义了获取proxy目标对象class对象的方法
Advised主要是定义了与配置代理对象的通知相关的一些方法
AdvisedSupport是对Advised接口的实现类,实现配置管理Advicor
ProxyCreatorSupport 实现创建proxy对象
ProxyFactory 对编程式aop代理创建的支持
ProxyFactoryBean 对基于BeanFactory的aop代理创建的支持
(与编程式aop类似,主要是提供面向配置支持,结合bean容器对FactoryBean的支持,就像直接操作代理对象一样)
AspectJProxyFactory spring对解析基于aspectj风格的方面配置来创建代理对象的支持
实现有两种方式
1. ProxyFactoryBean方式: 这种方式是通过配置实现
2. ProxyFactory方式:这种方式是通过编程实现
ProxyFactoryBean