目录
- Java的代理模式
- 1、什么是代理模式?
- 2、静态代理和动态代理
- 3、JDK动态代理的局限性
- 4、使用CGLIB代理机制完成未实现接口的类的代理
- 5、JDK动态代理和CGLIB动态代理对比
- 6、JDK动态代理为什么只能代理实现接口的类?
Java的代理模式
1、什么是代理模式?
代理模式( Proxy ),给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 通常会通过代理对象来为原对象添加额外的功能。
代理模式属于结构型模式主要用于处理类或对象的组合。
上面是比较正式的书面释义,举个通俗点的例子来帮助理解:
周末你躺在床上饿了想吃火鸡面,但是你有点懒,你找你女朋友帮你泡好并端到你面前喂你吃,最后你把火鸡面吃完了,说了句宝宝你真好~
这个例子中:你吃了一包火鸡面,是你做的动作。 但是你请了你的女朋友帮你泡火鸡面,你的女朋友就可以理解为是你的代理,此时你女朋友这个代理帮你泡了火鸡面(代理对象添加了额外的功能)。为了完成你吃火鸡面这件事 ,最后你女朋友还得端到你面前喂你吃(这里的喂你吃火鸡面就相当于 (代理对象控制了原对象的引用) )。
2、静态代理和动态代理
根据名称我们可以猜测,静态的代理一定不够灵活,运用到代码中一定是耦合的。
而动态代理顾名思义是动态的,灵活的,运用到代码中是解耦的。
静态代理:
在静态代理中,代理类是在编译时就确定的,即在代码中显式地定义了代理类(需要手动编写一个代理类)。
代理类通常与被代理类实现相同的接口,并且在代理类的方法中调用被代理类的方法。
静态代理的一个缺点是每次添加一个新的功能,都需要创建一个代理类,这样会导致类的数量增加,并且会造成代码的冗余。
代理类通常需要直接引用被代理类,因此代理类对被代理类有一定的依赖关系。如果被代理类的接口发生变化,代理类也需要相应地进行修改,这增加了代码的耦合性。
静态代理实现步骤:
- ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
- ②、自定义一个代理类实现前面定义的接口 ,并重写接口中需要被代理的方法,在重写的方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
- ③、通过代理类创建代理对象,使用代理对象替换原对象调用方法;
静态代理图示:
静态代理的代码示例:
public class TestStaticProxy {public static void main(String[] args) {YourBehavior you = new You();// 不使用代理you.eat("火鸡面");System.out.println("========================");// 使用代理YourBehavior yourGirlfriend = new YourGirlfriend(you);yourGirlfriend.eat("火鸡面");}
}/*** 行为接口 (代理类和被代理类都实现这个接口)* */
interface YourBehavior {/*** 吃* @param something 吃的东西*/void eat(String something);
}/*** 这个类代表你 (被代理类)* */
class You implements YourBehavior{@Overridepublic void eat(String something) {System.out.println("你吃:" + something);}
}/*** 这个类代表你女友 (代理类)* */
class YourGirlfriend implements YourBehavior{private You you;public YourGirlfriend() {}// 静态代理 必须依赖被代理类public YourGirlfriend(You you) {// 说明你的女朋友心里有你~this.you = you;}@Overridepublic void eat(String something) {System.out.println("帮你泡火鸡面,带到你面前");// 代理对象调用 原对象的方法you.eat("火鸡面");System.out.println("你说:谢谢宝~");}
}
运行结果:
吃:火鸡面
========================
帮你泡火鸡面,带到你面前
吃:火鸡面
你说:谢谢宝~
动态代理:
动态代理是在运行时生成的代理类,而不是在编译时确定的。Java中的动态代理机制主要依靠Java反射机制实现。
动态代理更加灵活,因为它可以在运行时决定要代理的对象及其行为,而不需要显式地为每个类编写代理类。
动态代理通常用于实现横切关注点(cross-cutting concerns AOP相关概念后续在Spring框架相关博客会详细介绍)的功能,
例如日志记录、性能监控、事务管理等。因为动态代理可以在运行时将这些功能动态地添加到方法调用中,而不需要修改原始类的代码。这就达到了解耦的目的。
Java中的动态代理主要是通过java.lang.reflect.Proxy类 和 InvocationHandler 接口实现的。
动态代理的代码示例:
JDK动态代理实现的步骤:
- ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
- ②、自定义一个类实现 InvocationHandler接口 并重写invoke方法,在 invoke 方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
- ③、通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象,使用代理对象调用方法。 参数说明: loader 目标对象的类加载器,interfaces目标对象实现的全部接口,h 自定义的InvocationHandler实例;
JDK动态代理图示:
JDK动态代理代码示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class TestDynamicProxy {public static void main(String[] args) {You you = new You();you.eat("火鸡面");System.out.println("========================");// 获取代理对象YourBehavior proxyInstance = (YourBehavior)ProxyFactory.getProxyObject(you);// 使用代理对象 调用方法proxyInstance.eat("火鸡面");}}/*** 行为接口 (代理类和被代理类都实现这个接口)*/
interface YourBehavior {/*** 吃** @param something 吃的东西*/void eat(String something);
}/*** 这个类代表你 (被代理类)*/
class You implements YourBehavior {@Overridepublic void eat(String something) {System.out.println("你吃:" + something);}
}class YouInvocation implements InvocationHandler {private Object targetObj;public YouInvocation() {}public YouInvocation(Object targetObj) {this.targetObj = targetObj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");// 调用原对象的方法Object result = method.invoke(targetObj, args);System.out.println("你说:谢谢宝~");return result;}
}// 代理工厂
class ProxyFactory {/*** 根据原对象生成代理对象** @param targetObj 原对象* @return 代理对象*/public static Object getProxyObject(Object targetObj){return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));}
}
运行结果:
你吃:火鸡面
========================
帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~
3、JDK动态代理的局限性
-
①、只能代理实现了接口的类: JDK动态代理的机制要求被代理的类必须实现至少一个接口,因为它是基于接口来生成代理类的。这意味着如果目标类没有实现接口,就无法使用JDK动态代理。
-
②、无法直接代理类的方法: JDK动态代理只能代理接口中定义的方法,无法直接代理类中的方法。如果需要代理类中的方法,就需要使用CGLIB等其他代理机制。
-
③、性能相对较低: 由于JDK动态代理是基于Java反射机制实现的,相比较于静态代理或者其他代理方式,它的性能会相对较低一些。这是因为在运行时生成代理类和方法调用的过程中,需要进行额外的反射操作,会带来一定的性能开销。
4、使用CGLIB代理机制完成未实现接口的类的代理
CGLIB(Code Generation Library)是一个Java字节码生成库,它被广泛用于在运行时动态生成新的类以实现代理、混入(Mixin)和其他类似的功能。
CGLIB通过生成目标类的子类,并在子类中重写需要代理的方法来实现代理功能,因此它可以代理那些没有实现接口的类。
CGLIB通常与其他代理机制(如JDK动态代理)相比具有更高的性能,因为它不需要依赖于接口,而且可以代理类中的方法,但是不包括final方法,因为被final修饰的方法无法被子类重写。
Spring框架就集成了CGLIB,默认情况下Spring 的AOP功能实现代理的方式:如果目标对象实现了接口,默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
CGLIB中实现动态代理的关键是 MethodInterceptor 接口和 Enhancer 类。
MethodInterceptor接口中的intercept方法 用来拦截增强被代理类的方法 ,Enhancer类的create方法用来创建代理对象。
CGLIB动态代理的实现步骤:
- ①、引入CGLIB坐标, 目前最新版本为3.3.0
https://central.sonatype.com/artifact/cglib/cglib/versions
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
选择目标类:选择需要代理的目标类,这个目标类可以是任意的普通类,不一定需要实现接口;
- ②、实现 MethodInterceptor接口,重写 intercept 方法用于拦截增强被代理类的方法;
- ③、创建Enhancer对象设置相应参数,调用 create()方法创建代理类;
CGLIB动态代理图示:
CGLIB动态代理代码示例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TestDynamicProxy {public static void main(String[] args) {You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();you.eat("火鸡面");System.out.println("================");youProxy.eat("火鸡面");}
}/*** 这个类代表你 (被代理类)*/
class You {public void eat(String something) {System.out.println("你吃:" + something);}public void sleep() {System.out.println("你睡觉了");}
}/*** 代理类 实现MethodInterceptor 重写 intercept*/
class YourGirlFriend implements MethodInterceptor {/*** @param o 被代理对象* @param method 被代理方法* @param objects 方法入参* @param methodProxy 用来调用原始方法* @return 方法返回值* @throws Throwable 异常*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if(method.getName().equals("eat")) {System.out.println("帮你泡火鸡面,带到你面前");} else if(method.getName().equals("sleep")) {System.out.println("帮你盖好被子,给你讲个故事");}Object object = methodProxy.invokeSuper(o, objects);if(method.getName().equals("eat")) {System.out.println("你说:谢谢宝~");} else if(method.getName().equals("sleep")) {System.out.println("你说:晚安~");}return object;}
}
5、JDK动态代理和CGLIB动态代理对比
①、原理
-
JDK动态代理:
JDK动态代理的原理主要基于Java的反射机制和java.lang.reflect.Proxy类与java.lang.reflect.InvocationHandler接口。
当通过代理对象调用方法时,实际上是调用了InvocationHandler的invoke()方法。这个方法内部会通过反射调用实际被代理对象的对应方法,并可以在此前后添加额外的处理逻辑。
在调用Proxy.newProxyInstance()时,如果代理类还没有被创建,JVM会动态地生成一个实现上述指定接口的代理类的字节码,并加载到JVM中。这个过程是透明的,开发者无需关心具体的生成细节。 -
CGLIB动态代理:
CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用。
CGLIB动态代理的核心在于通过继承和字节码操作技术,在运行时动态生成目标类的子类,并在子类中插入自定义的拦截逻辑,以此来达到在不修改目标类源码的情况下增强或控制其行为的目的。
②、使用场景
-
JDK动态代理:
目标类实现了接口,简单的功能增强 -
CGLIB动态代理:
无接口的类,复杂的AOP逻辑
③、性能(下面的说法有待考证)
-
JDK动态代理:
在早期JDK版本中,JDK动态代理的性能通常被认为低于CGLIB,主要是因为每次方法调用都需要通过反射(Method.invoke())来完成,反射操作相对较慢。
但从JDK 1.8开始,尤其是随着后续版本的不断优化,JDK动态代理的性能有了显著提升。特别是在某些场景下,其性能已经与CGLIB相当,甚至有所超越。
JDK动态代理在创建代理对象时的开销较小,因为它基于接口实现,不需要生成大量的字节码。 -
CGLIB动态代理:
CGLIB通过字节码技术生成目标类的子类,这种方式在创建代理实例时的开销较大,因为需要生成新的字节码。
一旦代理对象创建完成,CGLIB的直接方法调用(通过子类覆盖父类方法)在运行时通常比JDK动态代理的反射调用更快,特别是在频繁调用方法的场景下(这个在JDK8版本的测试下,优势并不明显,也许是方法调用的次数还不够多)。
CGLIB能够代理没有实现接口的类,提供了更广泛的适用范围,但这也意味着在运行时对类进行了修改,增加了潜在的风险。
简单测试下(使用StopWatch计算,JDK8版本):
分别使用JDK和CGLIB的动态代理去测试创建对象的速度,和调用代理方法的速度,分别进行创建50000次对象的时间比较和使用创建好的代理对象调用50000次代理方法的比较。
PS:被代理类是一样的,只是放在了不同的包下,其中JDK的被代理类多实现了一个接口。
测试代码如下:
import com.kinggm.testb.ProxyFactory;
import com.kinggm.testb.YourBehavior;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.util.StopWatch;import java.lang.reflect.Method;public class TestDynamicProxy {public static void main(String[] args) {StopWatch stopWatch = new StopWatch("测试CGLIB和JDK动态代理创建50000次代理对象的时间占比");stopWatch.start("CGLIB创建代理对象50000次");for (int i = 0; i < 50000; i++) {You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();}stopWatch.stop();stopWatch.start("JDK创建代理对象50000次");for (int i = 0; i < 50000; i++) {com.kinggm.testb.You you = new com.kinggm.testb.You();// 获取代理对象com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you);}stopWatch.stop();// ============================================================================================================You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();com.kinggm.testb.You you1 = new com.kinggm.testb.You();// 获取代理对象com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you1);StopWatch stopWatch1 = new StopWatch("测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比");stopWatch1.start("CGLIB代理调用方法50000次");for (int i = 0; i < 50000; i++) {youProxy.eat("火鸡面");}stopWatch1.stop();stopWatch1.start("JDK代理调用方法50000次");for (int i = 0; i < 50000; i++) {// 使用代理对象 调用方法proxyInstance.eat("火鸡面");}stopWatch1.stop();System.out.println(stopWatch.prettyPrint());System.out.println("========================================");System.out.println(stopWatch1.prettyPrint());}
}/*** 这个类代表你 (被代理类)*/
class You {public void eat(String something) {System.out.println("你吃:" + something);}public void sleep() {System.out.println("你睡觉了");}
}/*** 代理类 实现MethodInterceptor 重写 intercept*/
class YourGirlFriend implements MethodInterceptor {/*** @param o 被代理对象* @param method 被代理方法* @param objects 方法入参* @param methodProxy 用来调用原始方法* @return 方法返回值* @throws Throwable 异常*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");Object object = methodProxy.invokeSuper(o, objects);System.out.println("你说:谢谢宝~");return object;}
}
运行三次结果如下:
第一次:
StopWatch '测试CGLIB和JDK动态代理创建50000次代理对象的时间占比': running time = 250062000 ns
---------------------------------------------
ns % Task name
---------------------------------------------
183527300 073% CGLIB创建代理对象50000次
066534700 027% JDK创建代理对象50000次========================================
StopWatch '测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1168326000 ns
---------------------------------------------
ns % Task name
---------------------------------------------
583097900 050% CGLIB代理调用方法50000次
585228100 050% JDK代理调用方法50000次第二次:
StopWatch '测试CGLIB和JDK动态代理创建50000次代理对象的时间占比': running time = 270423500 ns
---------------------------------------------
ns % Task name
---------------------------------------------
198542800 073% CGLIB创建代理对象50000次
071880700 027% JDK创建代理对象50000次========================================
StopWatch '测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1194475300 ns
---------------------------------------------
ns % Task name
---------------------------------------------
616564500 052% CGLIB代理调用方法50000次
577910800 048% JDK代理调用方法50000次第三次:
StopWatch '测试CGLIB和JDK动态代理创建50000次代理对象的时间占比': running time = 243452600 ns
---------------------------------------------
ns % Task name
---------------------------------------------
184046700 076% CGLIB创建代理对象50000次
059405900 024% JDK创建代理对象50000次========================================
StopWatch '测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1227753400 ns
---------------------------------------------
ns % Task name
---------------------------------------------
572891200 047% CGLIB代理调用方法50000次
654862200 053% JDK代理调用方法50000次
从结果可以看出基本符合上面的性能总结:
JDK的动态代理创建代理对象的速度比CGLIB快的多,代理对象调用方法的速度不相上下。
6、JDK动态代理为什么只能代理实现接口的类?
如果别人问我这个问题,我会这样回答:
因为JDK提供的创建代理对象的API
Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));
第二个参数就是要传被代理对象实现的接口,这是王八的屁股,规定!
网上还有一种说法是:
因为Java语言的类是单继承的,JDK自动生成的代理类已经继承了Proxy类,要想保证代理类和被代理类有一致的行为(方法),显然不能通过继承重写方法来实现了,因为JDK自动生成的代理类已经继承了Proxy类;那么只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。那么在代理类中重写被代理类的方法就能完成代理(功能增强)。
**对于任何网上的说法持怀疑态度 **
有些比较模糊的观点最好还是去比较权威的书上查证 ,或者去看源码查证
下面就去看看JDK动态代理相关的源码,我们来考证下网上的说法对不对。
进入Proxy.newProxyInstance方法中
有个getProxyClass0方法 (注释:Look up or generate the designated proxy class —— 查找或者生成指定的代理类)
该方法返回生成的代理类的class对象
再往下看
再看下getProxyClass0方法内部:(英语真的挺重要,尤其是看源码注释的时候)
再继续看 ProxyClassFactory
ProxyClassFactory是个静态的内部工厂类
作用就是根据给定的类加载器和接口数组生成、定义并返回代理类
生成的代理类的前缀是$Proxy
再往下看 代理类包名和类名的生成规则
答案已经近在眼前了
进入ProxyGenerator.generateProxyClass方法
那我们就在JVM启动的时候 加上 启动参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
通过IDEA设置JVM启动参数:
这个时候再执行下代码 查看结果:
public static void main(String[] args) throws Exception{YourBehavior you = new You();Class<?> youClass = you.getClass();MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);YourBehavior proxyInstance = (YourBehavior)Proxy.newProxyInstance(youClass.getClassLoader(),youClass.getInterfaces(),myInvocationHandler);proxyInstance.eat("火鸡面");System.out.println(proxyInstance.getClass());}
结果:
帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~
class com.kinggm.test.$Proxy0
可以看到生成的代理类全限定名为: com.kinggm.test.$Proxy0
生成的文件在 项目根目录下:
由于我的接口
interface YourBehavior{void eat(String something);
}
并没有加public
所以生成的代理类包名 并不是默认的 com.sun.proxy 而是和我的接口包名相同
并且继承了 Proxy类 和被代理类实现了相同的接口YourBehavior
最后再看下 JDK自动生成的代理类结构:
(实际上生成的是.class文件,IDEA通过 FernFlower decompiler 插件把 .class字节码文件反编译成我们能看懂的.java类文件)
我们主要看三个地方$Proxy0的构造方法,初始化,重写的eat方法
①、构造方法
public $Proxy0(InvocationHandler var1) throws {super(var1);}
实际上执行的是父类 Proxy的构造方法,把我们自定义的InvocationHandler 传给父类Proxy中的InvocationHandler h
protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}
②、静态代码块初始化
其中m3 就是我们实现的接口中的目标方法eat
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.kinggm.YourBehavior").getMethod("eat", Class.forName("java.lang.String"));m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
③、重写的目标(eat)方法
public static void main(String[] args) {YourBehavior you = new You();MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);YourBehavior proxyInstance = (YourBehavior) Proxy.newProxyInstance(you.getClass().getClassLoader(),you.getClass().getInterfaces(),myInvocationHandler);proxyInstance.eat("火鸡面");}
当我们执行代理类的eat方法时实际上执行的是 $Proxy0中的 eat方法
public final void eat(String var1) throws {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}
$Proxy0中的 eat方法调用的是父类中初始化的 MyInvocationHandler 中的invoke方法
class MyInvocationHandler implements InvocationHandler {private YourBehavior targetObj;public MyInvocationHandler() {}public MyInvocationHandler(YourBehavior targetObj) {this.targetObj = targetObj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");Object result = method.invoke(targetObj,args);System.out.println("你说:谢谢宝~");return result;}
}
所以在执行目标方法eat前后,你女朋友帮你泡火鸡面,带到你面前,你对你女朋友说了 谢谢宝~
分析至此, 发现网上说的好像也有点道理, JDK自动生成的代理类利用继承Proxy来初始化InvocationHandler ,并处理代理对象的生成逻辑,Java又是单继承的语言,想保证代理类和被代理类有一致的行为(方法),就只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。最后在代理类中重写被代理类的方法通过调用父类Proxy中初始化的自定义InvocationHandler 中的invoke方法,就能执行到我们自己写的增强逻辑了,同时invoke方法中又去调用了原方法,就完成了整个代理过程喽。
最后再画个图总结下吧: