代理模式详解(二)-动态代理
上一篇用一个小案例的方式引入了代理模式(实际上是静态代理)。相对来说,自己去写代理类的方式就是静态代理,比如上一章的GamePlayerProxy类、OrderServiceStaticProxy类,他们都属于我们自己写的代理类。那这么说的话,动态代理就不用自己写代理类了吗?我觉得是也不是,比如spring大名顶顶的aop(面向切面编程),只需要配置,spring框架就帮我们自动实现了代理,而我们只需要关注切面的业务代码。那什么是动态代理呢?动态代理指的是,在代码的实现阶段我们不需要关心代理谁,而在运行阶段才指定代理哪个对象。
在java中,常见的实现动态代理的方式有两种:(1)jdk动态代理;(2)cglib动态代理。
我们先看游戏代练的例子通过JDK动态代理的实现:
定义一个GamePlayerJdkDynProxy类:
public class GamePlayerJdkDynProxy implements InvocationHandler { private Object target; //目标代理类,不需要持有具体的代理对象 public Object getProxyInstance(Object target){ this.target = target; Class clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equalsIgnoreCase("login")){ //前置方法可以根据条件是否调用 before(args[0]);//登陆的时候才打印 } Object object = method.invoke(target,args); if(method.getName().equalsIgnoreCase("upgrade")){ //后置方法可以根据条件是否调用 after(); //升级之后才打印 } return object; } private void before(Object args){ System.out.println("正在使用"+args.toString()+"登陆游戏"); } private void after(){ System.out.printf("完成任务!"); } }
测试代码:
public class Client { public static void main(String[] args) { //获取代理类 GamePlayerJdkDynProxy gamePlayerJdkDynProxy = new GamePlayerJdkDynProxy(); IGamePlayer playerProxy = (IGamePlayer) gamePlayerJdkDynProxy.getProxyInstance(new GamePlayer("小A")); System.out.println(playerProxy.getClass().getName()); //这里我打印了代理类的类名 playerProxy.login("我是小A呀","123"); //登陆账号 playerProxy.win(); //赢了一局 playerProxy.upgrade(); //上星耀了 } }
测试:
@Autowired private OrderService orderService; @Test public void testAop(){ OrderEntity order1 = new OrderEntity(); order1.setId("1"); order1.setCreateTime(1587818744477l); order1.setOrderInfo("{订单1}"); System.out.println(order1.toString()); orderService.createOrder(order1); OrderEntity order2 = new OrderEntity(); order2.setId("1"); order2.setCreateTime(1587818744478l); order2.setOrderInfo("{订单2}"); System.out.println(order2.toString()); orderService.createOrder(order2); }
运行结果:
com.sun.proxy.$Proxy0
正在使用我是小A呀登陆游戏
小A登陆游戏,用户名为:我是小A呀
我是小A呀 玩家赢了一局
恭喜:我是小A呀 玩家上到星耀了
完成任务!
我们可以看到,动态代理与静态代理实现的效果是一样的。但是它却可以对一些代理的方法是否需要前置通知,后置通知做一个灵活的处理,比如有人登陆小A的账号,小A立马就可以知道,上到星耀完成认为了,也立马会得到这样的一个通知,真的是太好了!这种方式对于代码的解藕是非常有帮助的,对于日志、事物、权限等都可以在系统设计初期不用考虑,而在设计后通过动态代理的方式切过去(这就是AOP)。动态代理居然如此诱人,那我们再细探它的究竟吧!
看到 com.sun.proxy.$Proxy0相信小伙们肯定会有疑问,这个玩意是什么?!为了一探这个类的究竟,我们准备把这个类的字节码输出到本地。
我们在测试代码的下面追加文件输出的代码:
public static void main(String[] args) throws IOException { //获取代理类 GamePlayerJdkDynProxy gamePlayerJdkDynProxy = new GamePlayerJdkDynProxy(); IGamePlayer playerProxy = (IGamePlayer) gamePlayerJdkDynProxy.getProxyInstance(new GamePlayer("小A")); System.out.println(playerProxy.getClass().getName()); //这里我打印了代理类的类名 playerProxy.login("我是小A呀","123"); //登陆账号 playerProxy.win(); //赢了一局 playerProxy.upgrade(); //上星耀了 try{ byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{GamePlayer.class}); FileOutputStream os = new FileOutputStream("/Users/wangpei/Downloads/$Proxy.class"); os.write(bytes); os.close(); }catch (Exception e){ e.printStackTrace(); } }
运行之后我们可以看到$proxy类输出到了下载目录下面,我们可以用反编译工具jad进行反编译之后打开,或者直接用idea打开:
具体类容如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.proxy.statics.game.GamePlayer; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements GamePlayer { private static Method m1; private static Method m6; private static Method m2; private static Method m5; private static Method m11; private static Method m13; private static Method m0; private static Method m10; private static Method m3; private static Method m12; private static Method m9; private static Method m4; private static Method m8; private static Method m7; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void win() throws { try { super.h.invoke(this, m6, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void upgrade() throws { try { super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Class getClass() throws { try { return (Class)super.h.invoke(this, m11, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void notifyAll() throws { try { super.h.invoke(this, m13, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void wait() throws InterruptedException { try { super.h.invoke(this, m10, (Object[])null); } catch (RuntimeException | InterruptedException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String getName() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void notify() throws { try { super.h.invoke(this, m12, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void wait(long var1) throws InterruptedException { try { super.h.invoke(this, m9, new Object[]{var1}); } catch (RuntimeException | InterruptedException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final void login(String var1, String var2) throws { try { super.h.invoke(this, m4, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final void wait(long var1, int var3) throws InterruptedException { try { super.h.invoke(this, m8, new Object[]{var1, var3}); } catch (RuntimeException | InterruptedException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } public final String getUserName() throws { try { return (String)super.h.invoke(this, m7, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m6 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("win"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m5 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("upgrade"); m11 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("getClass"); m13 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("notifyAll"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m10 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("wait"); m3 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("getName"); m12 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("notify"); m9 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("wait", Long.TYPE); m4 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String")); m8 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("wait", Long.TYPE, Integer.TYPE); m7 = Class.forName("com.proxy.statics.game.GamePlayer").getMethod("getUserName"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
有没有一种离真理越来越近的感觉!接下来这个半路出家的一个代理类。我们可以看到它继承了Proxy类,并且实现了GamePlay类接口,并且重写了login()等方法。在静态块中用反射查找到了目标对象的所有方法并且保存了所有方法的引用,重写的方法用反射调用目标对象的方法。然来如此!这不是把静态代理给还原了吗!?接下来应该就很容易推测到,为什么我们需要实现InvocationHandler接口了,答案一定是在继承的Proxy类上!我们再来看看Proxy类(有兴趣的小伙伴可以自行去看看Proxy类的究竟,这里贴出部分代码):
public class Proxy implements java.io.Serializable { private static final long serialVersionUID = -2222568056686623797L; /** parameter types of a proxy class constructor */ private static final Class<?>[] constructorParams = { InvocationHandler.class }; /** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); /** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h; //这里持有了InvocationHandler的引用,我们的GamePlayerJdkDynProxy InvocationHandler吗?
}
从Proxy类中我们可以看出,Proxy持有了InvocationHandler的引用,我们的GamePlayerJdkDynProxy InvocationHandler吗?看到这里,大家是不是就能理解动态代理的原理了呢,实际上JDK的动态采用了字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的。
JDK动态代理生成对象的步骤如下:
- 获取被代理对象的引用,并且反射获取它的所有接口;
- JDk动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口(这个类可以理解为静态代理的代理类);
- 动态生成java代码,新加的业务逻辑方法由一定的逻辑代码调用。(这种感觉很像用代码写代码,其实很多自动生成代码的技术都是这个原理);
- 编译新生成的Java代码.class文件;
- 重新加载到JVM中运行。
明白了JDK动态代理的原理,我们再来看看cglib动态代理
还是以游戏代练为例子:
public class GamePlayerCglibDynProxy implements MethodInterceptor { private Object target; public Object getProxyInstance(Object target){ this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if(method.getName().equalsIgnoreCase("login")){ //前置方法可以根据条件是否调用 before(args[0]);//登陆的时候才打印 } Object obj = methodProxy.invokeSuper(o,args); if(method.getName().equalsIgnoreCase("upgrade")){ //后置方法可以根据条件是否调用 after(); //升级之后才打印 } return obj; } private void before(Object args){ System.out.println("正在使用"+args.toString()+"登陆游戏"); } private void after(){ System.out.printf("完成任务!"); } }
测试代码:
public static void main(String[] args) { //将内存中的class写入磁盘 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/wangpei/Downloads/cglib"); IGamePlayer proxy = (IGamePlayer) new GamePlayerCglibDynProxy().getProxyInstance(new GamePlayer("小A")); System.out.println(proxy.getClass().getName()); //这里我打印了代理类的类名 proxy.login("我是小A呀","123"); //登陆账号 proxy.win(); //赢了一局 proxy.upgrade(); //上星耀了 }
运行结果:
com.proxy.statics.game.GamePlayer$$EnhancerByCGLIB$$71b87dbe
正在使用我是小A呀登陆游戏
null登陆游戏,用户名为:我是小A呀
我是小A呀 玩家赢了一局
恭喜:我是小A呀 玩家上到星耀了
完成任务!
在打印的结果信息中,我发现通过目标类传入的用名字信息输出为 null,这是为什么呢?在之后跟踪cglib生成的代理类发现,原来cglib生成的代理类是通过继承来实现的,而生成的子类对象并不是由带有name的有参构造方法创建的(这里跟jdk的代理区别是,jdk代理完整的还原了静态代理,所以动态代理生成的代理对象持有目标对象的引用),子类通过重写父类的方法来达到代理的作用,如下图:
其实,cglib的动态代理相比较jdk代理来说要复杂的多,其生成的字节码文件也有许多我们看不懂的代码,原因是cglib动态代理使用ASM框架来写class字节码,并且生成来3个字节码文件,所以CGlib代理生成代理类比jdk动态代理效率要低。但是jdk动态代理方法是通过反射机制调用的,cglib代理则是通过FastClass机制直接调用方法,所以cglib的代理的执行效率要比jdk动态代理的执行效率要高,(感兴趣的小伙伴可以研究一下FastClass机制)另外cglib的动态代理依靠的是子类重写父类的方式实现的,所以对于需要代理final类型的方法的时候,可能就不太好使了。
我想动态代理分享到这里就差不多了,我更多的是希望读者能够了解其中的原理。因为在我们日常的开发中,spring aop为我们提供了非常好的动态代理框架了,在我们了解其原理后,知道动态代理与静态代理的区别,知道如何处去用,那本篇博文的目的也达到了。
最后稍微总结一下静态代理与动态代理的区别:静态代理只能对特有的一个类去完成代理操作,如果这个类有变更,增加新的方法的时候,相应的代理类也要增加对应的方法,违背了开闭原则,而动态代理采用在运行时生成代码的方式,取消了对目标被代理类的扩展限制,降低耦合,符合开闭原则。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】