spring-aop 两种代理方式 JDK动态代理和CGLIB动态代理
Spring的两大特性是IOC和AOP:
Spring的核心特性就是IOC和AOP,IOC(Inversion of Control),即“控制反转”;AOP(Aspect-OrientedProgramming),即“面向切面编程”。下面分享我个人对AOP特性的理解。
AOP的实际应用:
面向切面编程,通常被定义为系统关注点的一种分离技术。系统是许多不同组件组成,组件的作用除了要实现自身的业务逻辑,通常都会被要求添加日志,事务管理,和缓存或安全等问题。这些系统服务进程被称为横切关注点,在系统中多个组件都会相继使用。
AOP实现的关键在于AOP框架自动创建AOP代理,AOP代理主要分为静态代理(AspectJ)和动态代理(StringAop)
AspectJ:会被称为编译时增强,AspectJ是静态代理的增强,采用编译时生成AOP代理类,会具有更好的性能,就是需要使用特定的编译器去处理,使用不太方便。
Spring-Aop:是以动态代理的方式,运行时生成AOP代理类,不会改变字节码,在内存中生成一个方法的AOP临时对象。在运行过程中需要每次生成AOP代理,性能会相对受到影响。
Spring-Aop动态代理实现有两种方式,jdk动态代理和CGIB动态代理。
JDK动态代理:是通过反射接收被代理的类。
一般主要涉及到以下两个类:
(1)Interface InvocationHandler:该接口中仅定义了一个方法
(2)Proxy:该类即为动态代理类,(详情看实例代码注解)
下面就来演示下jdk代理的实现原理。
1、jdk动态实现aop拦截
自定义要实现的接口MyInv,需要实现的方法add();
/**
* Created by zzm on 2020/04/19
* desc: jdk动态aop代理需要实现的接口
*/
public interface MyInv {
public void add();
}
用要代理的目标类MyInvImpl实现上面我们定义的接口,在目标类的add方法的前后实现拦截,加入自定义切面逻辑。这就是aop的魅力所在:代码与代码之间没有耦合。
/**
* Created by zzm on 2020/04/19
* desc: 被代理的类,即目标类target
*/
public class MyInvImpl implements MyInv {
@Override
public void add() {
System.out.println("目标类的add方法");
System.out.println("时间和机会才是最后的本钱!");
}
}
用MyInvoQuote类去实现InvocationHandler接口,并且实现接口中的invoke方法。在该方法中加入切面逻辑,实现自己的目标接口是在method.invoke里完成。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by zzm on 2020/04/19
* desc:这里加入切面实现InvocationHandler 接口的类
*/
public class MyInvoQuote implements InvocationHandler {
private Object target;
public MyInvoQuote(Object target) { // 构造函数
this.target = target;
}
/**
* 这个抽象方法在代理类中动态实现。
*
* @param proxy 第一个参数obj一般是指代理类
* @param method method是被代理的方法
* @param args args为该方法的参数数组
* @return
*
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before---》切面执行逻辑");
Object invoke = method.invoke(target, args);//通过反射执行,目标类的方法
System.out.println("after---》切面执行逻辑");
return invoke;
}
}
最后写个测试方法。实现aop方式之一的:jdk动态代理
public class test {
public static void main(String[] args) {
MyInvImpl myInvImpl = new MyInvImpl(); // 实现需要代理的类
MyInvoQuote handler = new MyInvoQuote(myInvImpl); //构造实现InvocationHandler接口的实现类
// Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例
//这里的proxyInstance就是我们目标类的增强代理类
MyInv proxyInstance = (MyInv) Proxy.newProxyInstance(myInvImpl.getClass().getClassLoader(), // getClassLoader() 取得该Class对象的类装载器
myInvImpl.getClass()
.getInterfaces(), handler); //getInterfaces()获取该class对象下的方法可通过索引找到对应方法
// 列: myInvImpl.getClass().getInterfaces()[0] 获得myInvImpl对象所实现的第一个接口
//Proxy.newProxyInstance()返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)
proxyInstance.add();
//打印增强过的类类型
System.out.println("打印增强过的类类型:" + proxyInstance.getClass());
}
}
结果
在实现过程中证实了jdk动态代理的核心是InvocationHandler接口和Proxy类
注意:jdk动态代理的应用前提,反射机制在生成类的过程中比较高效,不过必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性
cglib动态代理:底层则是借助asm来实现的
下面演示下cglibProxy动态代理
目标类,cglib不需要定义目标类的统一接口
/**
* Created by zzm on 2020/04/19
* desc: 被代理的类,即目标类target
*/
public class MyInvImpl {
public void myBase() {
System.out.println("目标类的myBase方法");
System.out.println("时间和机会才是最后的本钱!");
}
}
实现动态代理类CglibProxy,需要实现MethodInterceptor接口,实现intercept方法。该代理中在myBase方法前后加入了自定义的切面逻辑,目标类myBase方法执行语句为proxy.invokeSuper(object, args);
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by zzm on 2020/04/19
* desc:这里加入切面实现MethodInterceptor 接口的类
*/
public class MyInvoQuote implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before添加切面,方法执行前");
methodProxy.invokeSuper(o, objects);
System.out.println("after添加切面,方法执行后");
return null;
}
}
测试类:
public class test {
public static void main(String[] args) {
MyInvoQuote proxy = new MyInvoQuote(); //构造实现 MethodInterceptor接口的类
Enhancer enhancer = new Enhancer();
//设置代理目标
enhancer.setSuperclass(MyInvImpl.class);
//回调方法的参数为代理类对象CglibProxy,
// 最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
enhancer.setCallback(proxy);
//此刻,base不是单车的目标类,而是增强过的目标类
MyInvImpl base = (MyInvImpl) enhancer.create();
base.myBase();
Class<? extends MyInvImpl> baseClass = base.getClass(); //java 泛型 上界通配符(Upper Bounds Wildcards)
//查看增强过的类的父类是不是未增强的Base类
System.out.println("增强过的类的父类:"+baseClass.getSuperclass().getName());
System.out.println("============打印增强过的类的所有方法==============");
FanSheUtils.printMethods(baseClass); //自定义获取类下面方法的工具类
// 没有被增强过的base类
MyInvImpl base2 = new MyInvImpl();
System.out.println("未增强过的类的父类:"+base2.getClass().getSuperclass().getName());
System.out.println("=============打印增未强过的目标类的方法===============");
FanSheUtils.printMethods(base2.getClass());//打印没有增强过的类的所有方法
}
}
查看结果
before添加切面,方法执行前
目标类的myBase方法
时间和机会才是最后的本钱!
after添加切面,方法执行后
增强过的类的父类:com.billiards.service.impl.MyInvImpl
============打印增强过的类的所有方法==============
public final equals(java.lang.Object);
public final toString();
public final hashCode();
CGLIB$SET_THREAD_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;);
public static CGLIB$SET_STATIC_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;);
public getCallback(int);
public getCallbacks();
public static CGLIB$findMethodProxy(org.springframework.cglib.core.Signature);
final CGLIB$equals$1(java.lang.Object);
final CGLIB$myBase$0();
final CGLIB$hashCode$3();
final CGLIB$clone$4();
final CGLIB$toString$2();
static CGLIB$STATICHOOK1();
private static final CGLIB$BIND_CALLBACKS(java.lang.Object);
未增强过的类的父类:java.lang.Object
=============打印增未强过的目标类的方法===============
public myBase();
自此,cglib动态代理实现的AOP拦截机制已经基本实现
asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
spring aop的两个核心方式已经简单实现。后续实现Spring-boot中使用aop切面,使用注解的形式更加简化了代码量,通过注解是使用springaop两种形式中的(AspectJ)方式实现的。掌握原理在使用注解的形式去做,才不会一头雾水。