AOP
一、代理模式
客户端直接使用的是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。
作用:
- 代理对象完全包含真实对象,客户端使用的都是代理对象的方法,与真实对象没有直接关系
- 代理模式的职责:把不是真实对象该该做的事情从真实对象中剔除
1. 静态代理
在程序运行之前就已经存在了代理类的字节码文件,代理对象与真实对象的关系在运行前就已经确定了
public interface AService{
void save();
}
public class AServiceImpl implements AServic{
public void save()
}
public classs AServiceProxy implements AService{
private AService target
public void save(){
// 开启事务
target.save();
// 提交事务/回滚事务
}
}
优点:
- 业务类只用关注业务逻辑本身,保证了业务的重用性
- 把真实对象隐藏了起来
缺点:
- 代理对象的某个接口只服务于某一类性的对象,每一个真实对象都需要创建一个真实对象。
- 如果需要代理的方法有很多,则每一个方法都需要进行代理处理
- 如果接口增加一个方法,除了所有实现类需要实现这个方法,所有的代理类都需要实行此方法
2. 动态代理
字节码加载
java文件-------->编译器(javac)------->字节码文件------->类加载器-------->java虚拟机
如何动态创建一份字节码:
由于JVM通过字节码的二进制信息加载类的,如果我们在运行期间中,通过Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后把这个二进制数据加载转换成对应的类,如此,就完成了在代码中动态创建一个类的能力。
JDK动态代理
要求真实对象必须要有接口
1 java.lang.reflect.Proxy:java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
主要方法:
public static Object newProxyInstance(ClassLoader loader ,class<?>[]interfaces,InvocationHandler handler)
为指定类加载器、一组接口及处理器生成动态代理类实例
参数:
loader 类加载器,一般为真实对象的类加载器
interfaces 代理类需要实现的接口
handler 代理对象如何做增强
返回:
创建的代理对象
2 java.lang.reflect.InvocationHandler接口:
主要方法:
public Object invoke(Object proxy,Method method,Object[] args)
负责集中处理动态代理类上的所有方法调用
参数
proxy 生成的代理对象
method 当前调用的真实方法对象
args 当前调用方法的实参
JDK动态代理的步骤:
- 实现InvocationHandler接口,创建自己的增强代码的处理器
import java.lang.reflect.InvocationHandler
public classs TransactionManagerAdvice implements InvocationHandler{
private Object target;
private TransactionManagerAdvice txmanager;
public void setTarget(){
this.target=target;
}
public Object getProxyObject(){
return Proxy.newProxyInstance(target.getClass.getClassLoader,
target.getClass.getInterface,
new XX());
}
// 定义如何做增强
public Object invoke(Object proxy,Method method,Object[] args){
Object ret=null;
txmanager.begin()
try{
ret=method.invoke(target,args);
txmanager.commit()
}catch(Exception e){
txmanager.rollback()
}
return ret;
}
}
动态代理原理
proxy对象中方法 ----------TransactionManagerAdvice中invoke--------------method.invoke(target,args)------真实对象中的方法
JDK动态代理总结:
- Java动态代理是使用java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler来完成的
- 要使用JDK动态代理,委托必须要定义接口
- JDK动态代理将会拦截所有的public(因为只能调用接口中定义的方法,这样即使在接口中增加了新的方法,不用修改代码也会被拦截。
- 动态代理的最小单位是类(类中的所有方法都会被处理,如果只想拦截一部分方法,可以在invoke方法中对要执行的方法名进行判断)
CGLIB动态代理
JDK动态代理:必须要求真实对象有接口
CGLIB动态代理:可以针对没有接口
import org.springframework.cglib.proxy.InvocationHandler
public classs TransactionManagerAdvice implements InvocationHandler{
private Object target;
private TransactionManagerAdvice txmanager;
public void setTarget(){
this.target=target;
}
public <T>T getProxyObject(){
Enhancer enhancer=new Enhancer();
enhancer.setSuperClass(target.getClass());//将继承与哪个类来增强
enhancer.setCallBack(this); //设置增强的对象
return enhancer.create(); //创建代理对象
}
// 定义如何做增强
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
Object ret=null;
txmanager.begin()
try{
ret=method.invoke(target,args);
txmanager.commit()
}catch(Exception e){
txmanager.rollback()
}
return ret;
}
}
CGLIB动态代理总结:
- CGLIB可以生成委托类的子类,并重写父类会final修饰符的方法
- 要求类不能是final的,要拦截的方法是非final,非static,非private的
- 动态代理的最小单位是类(所有类中的方法都会被处理)
二、AOP 术语
AOP术语
- join point 连接点, 对应的是具体被拦截的方法,因为Spring只能支持方法,所以被拦截的对象往往就是特定的方法
- point cut 切点,切面 不单单应用与单个方法,也可能是多个类的不同方法,可以通过正则式和指示器的规则去定义,从而适配连接点,切点提供了这样的功能。
- advice 增强 ,是织入目标类的连接点上的一段程序代码分为前置增强(before advice)、后置增强(after advice)、环绕增强(around advice)、事后返回增强(afterReturning advice)和异常增强(afterThrowing advice)
- Target 目标对象,即被代理类
- introduction 引介 ,引介是一种特殊的增强,它为类添加一些属性和方法,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口地实现逻辑,让业务类成为这个接口类地实现类
- weaving 注入,将增强添加到目标类地具体连接点地工程,AOP像一台织布机一样将目标类和增强引介天衣无缝地编织在一起。
- aspect 切面,切面由切点和增强构成,它既包括横切逻辑的定义,也包括连接点地定义。spring AOP 就是负责实施切面地框架,它将切面所定义地横切逻辑织入到切面所定义的连接点中。
三、AOP开发
我们采用@Aspect的注解方式讨论AOP的开发。因为 Spring AOP只能对方法进行拦截,所以首先要确定拦截什么方法,让它能织入到约定的流程中。
1.确定连接点
首先我们需要确定连接点(在Spring中就是什么类的什么方法)
2.开发切面
切面主要包括连接点和增强两个部分
@Aspect 用于标注面
@Before 表示前置增强
@After 表示后置增强
@AfterReturning 表示后置返回增强
@AfterThowing 表示异常增强
@Around 表示环绕增强
execution(*com.demo.service.impl.AServiceImpl.a(..)) 定义了连接点
通常我们使用正则表达式来定义切点
package com.demo.aspect
@Aspect
public class MyAspect{
@Before("execution(*com.demo.service.impl.AServiceImpl.a(..))")
public void before(){
System.out.println("before");
}
@After("execution(*com.demo.service.impl.AServiceImpl.a(..))")
public void afer(){
System.out.println("after");
}
@AfterReturning("execution(*com.demo.service.impl.AServiceImpl.a(..))")
public void afterReturning(){
System.out.println("afterReturning");
}
@AfterThrowing("execution(*com.demo.service.impl.AServiceImpl.a(..))")
public void afterThrowing(){
System.out.println("afterThrowing");
}
}
3. 切点定义
在使用@Before、 @After 、@AfterReturning 、@AfterThowing 时还会定义一个正则式,用于表示何处启用AOP,但是上面荣誉的使用了这个正则式,可以进行如下额替换;
package com.demo.aspect
@Aspect
public class MyAspect{
@PointCut("execution(*com.demo.service.impl.AServiceImpl.a(..))")
public void pointCut(){
}
@Before("pointCut()")
public void before(){
System.out.println("before");
}
@After("pointCut()")
public void afer(){
System.out.println("after");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("afterReturning");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("afterThrowing");
}
}
@PointCut 用于定义切点,将它定义到方法上方,那么在后面的增强注解当中就可以使用方法名来进行定义了
看下方的正则式:
execution(* com.demo.service.impl.AServiceImpl.a(..))
- execution 表示在执行的时候,拦截里面的正则匹配的方法:
- *表示任意返回类型的方法;
- com.demo.service.impl.AServiceImpl 表示目标对象的全类名
- a 表示目标对象的方法
- (..)表示任意函数进行匹配
对于正则式而言,还可以使用AspectJ的指示器
项目类型 | 描述 |
---|---|
arg() | 限定连接点方法参数 |
@args() | 通过连接点方法参数上的注解进行限定 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理Bean引用为指定的类型 |
target() | 目标对象(即被代理对象) |
@target() | 限制目标对象的配置了指定的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限定连接点带有匹配注解类型 |
@annotation() | 限定带有指定注解的连接点 |
4.环绕增强
环绕增强是所有增强中最为强大的增强,使用它的场景是在你需要大幅度修改原有目标对象的服务逻辑时,否则都尽量使用其他的通知。
环绕增强是一个取代原有目标对象方法的增强,同时它还是提供了回调原有目标对象方法的能力
@Around("pointCut()")
public void around(PrceedingJoinPoint jp) throws Throwable{
System.out.println("around before......");
//回调目标对象的原有方法
jp.proceed();
System.out.println("around after ......");
}
它有一个ProceedingJoinPoint类型的参数 ,这个参数对象有一个proceed方法,可以通过这个方法调用原有目标对象的方法
jp.procced();
5.引介
Spring AOP 引入的定义就能增强接口的功能,如下用接口Validator增强了AService接口
package com.demo.aspect
public interface Validator{
public boolean Validate(A a);
}
package com.demo.aspect.impl
public class ValidatorImpl implements Validator{
@override
public boolean validate(A a){
System.out.printla("引入新的接口:"+Validator.class.getSimpleName);
}
}
@Aspect
public class MyAspect{
@DeclareParents(
value="com.demo.service.impl.AServiceImpl +";
defaultImpl="ValidatorImpl .class"
)
}
@DeclareParents 它的作用是引入新的类来增强服务,它有两个必须配置的属性:value 和defaultImpl
- value: 指向你要增强功能的目标对象
- defaultImpl:引入增强的类
使用时要强制转化为Validatoer类型才能使用增强的内容
6.织入
织入是一个生成动态代理对象并且将切面和目标对象方法编织成为约定流程的过程 ,spring boot推荐采用接口加实现类的模式。即通过JDK和CGLIB来实现
对于JDK而言,它是要求被代理的目标对象必须要有接口,而对CGLIB则不做要求。因此在默认的情况下,当你当你需要使用 AOP 的类拥有接口时 , 它会 以 JDK 动态代理运行,否则以 CGLIB 运行 。