文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深入理解JDK动态代理

2024-12-03 06:29

关注

代理模式的目的是在不修改原有类方法设计的基础上,对方法行为进行增强。

为了好理解,举个实际场景,我们业务场景中经常有限流的需求,常规操作是在需要限流的接口代码前加入调用次数判断的代码,但是这样每个需要限流的方法都需要加,工作量大不说,一方面不好维护,不能很清晰的知道每个接口限流值,另一方面,限流代码和业务代码堆叠在一起,也影响代码阅读。解法是做一套统一限流,一般好点的会有专门的接口限流平台,配置对应的接口名,设置限流值,直接就可以限流,实现方式就可以用动态代理。不修改原接口的实现,对接口进行增强。

动态代理的优势是实现无侵入式的代码扩展,做方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。

静态代理

既然有动态,那一定有静态,说下区别吧,

静态:最大的区别是静态是编译期就决定了,在程序运行之前,代理类的.class文件已经存在了。被代理类是什么,代理类实现方式。

举个栗子:

我现在有个接口,是把Json字符串解析成Object 对象,接口如下:

  1. public interface IProvider { 
  2.     
  3.   Object getData(String json); 
  4.    

接口的实现类如下:

  1. public class SimpleProvider implements IProvider { 
  2.     @Override 
  3.     public Object getData(String json) { 
  4.         //解析json 拿到数据 
  5.         return parseJson(json); 
  6.     } 

那现在有个需求,需要对 getData 方法做限流,指定用静态代理的方式。

需要很简单,我就直接贴了:

  1. public class ProviderProxy implements IProvider{ 
  2.  
  3.     //持有一个被代理对象的引用(在这里是SimpleProvider) 
  4.     IProvider iProvider; 
  5.  
  6.     public StaticProviderProxy(IProvider iProvider){ 
  7.         this.iProvider = iProvider; 
  8.     } 
  9.  
  10.     @Override 
  11.     public Object getData(String json) { 
  12.         //做限流检查 
  13.         if(callSpeed > flowLimt) { 
  14.           //流量超限 
  15.            throw FlowLimitException(); 
  16.         } 
  17.         Object object = iProvider.getData(json); 
  18.         return object; 
  19.     } 
  20. //main  
  21. public static void main(String[] args) { 
  22.   IProvider provider = new ProviderProxy(new SimpleProvider()); 
  23.     provider.getData("{\"data\":{}}"); 

这就是静态代理,代理类(ProviderProxy)实现和需要做方法增强的被代理类(SimpleProvider)实现同一个接口(IProvider),方法具体实现上做增强,这里是限流检查。

动态代理

Java 动态代理

还是以IProvider 接口为例,同样是要对 SimpleProvider 做增强,如下:

  1. public class ProviderHandler implements InvocationHandler { 
  2.     Object target; 
  3.  
  4.     public Object bind(Object target){ 
  5.         this.target = target; 
  6.         //这里生成了代理对象 
  7.         return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
  8.                 target.getClass().getInterfaces(), this); 
  9.     } 
  10.  
  11.     @Override 
  12.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  13.         //限流 
  14.         flowLimit(args); 
  15.         Object obj = method.invoke(target, args); 
  16.         //打印日志 
  17.         logger.info("print log..."); 
  18.         return obj; 
  19.     } 
  20. //main 
  21. public static void main(String[] args) { 
  22.    ProviderHandler providerHandler = new ProviderHandler(); 
  23.    IProvider iProvider = (IProvider) providerHandler.bind(new SimpleProvider()); 
  24.    iProvider.getData("weibo.data"); 

这里有三个对象:

SimpleProvider 对象 , 我们称之为被代理对象

ProviderHandler 对象,我们称之为执行者对象

Proxy对象 (通过在ProviderHandler bind方法中使用Proxy.newProxyInstance生成的对象) 我们称之为代理对象

这三个对象是什么关系呢?

Proxy是真正的代理类,SimpleProvider是被代理类,ProviderHandler是执行方法增强的执行者。

我们是为了增强SimpleProvider (被代理对象)的getData方法,就Proxy对象来代理被代理对象的执行,Proxy不亲自来做这件事,而是交给执行者对象ProviderHandler 来实现增加的目录,执行调用前的限流校验。

实际怎么实现的呢?

newProxyInstance源码

  1. public static Object newProxyInstance(ClassLoader loader, 
  2.                                           Class[] interfaces, 
  3.                                           InvocationHandler h) 
  4.         throws IllegalArgumentException 
  5.     { 
  6.         //对 Invocationhandler做判空处理 
  7.         Objects.requireNonNull(h); 
  8.         //复制[IProvider接口] 
  9.         final Class[] intfs = interfaces.clone(); 
  10.  
  11.        //根据IProvider的类加载器IProvider接口生成了Proxy类,关键:根据类加载器和接口对象在JVM缓存中生成一个类对象 
  12.         Class cl = getProxyClass0(loader, intfs); 
  13.         //获取构造器 
  14.         final Constructor cons = cl.getConstructor(constructorParams); 
  15.         //保存InvocationHandler的引用 
  16.         final InvocationHandler ih = h; 
  17.         //通过构造器实例化Proxy代理对象 
  18.         return cons.newInstance(new Object[]{h}); 
  19.     } 

代码注释写的很清晰。

可能这个地方大家都会疑惑,生成的Proxy对象是怎样调用执行者的invoke函数的。

这个地方通过这段代码将Proxy0的class字节码输出到文件。

  1. byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", WeiboProvider.class.getInterfaces()); 
  2. String path = "C:**/IdeaProjects/study/out/production/study/SimpleProxy.class"
  3. try(FileOutputStream fos = new FileOutputStream(path)) { 
  4.     fos.write(classFile); 
  5.     fos.flush(); 
  6.     System.out.println("代理类class文件写入成功"); 
  7.    } catch (Exception e) { 
  8.      System.out.println("写文件错误"); 
  9.  } 

反编译Proxy0如下:

  1. //Proxy0 是动态生成的类,继承自Proxy,实现了IProvider接口 
  2. public final class $Proxy0 extends Proxy implements IProvider { 
  3.     private static Method m1; 
  4.     private static Method m2; 
  5.     private static Method m3; 
  6.     private static Method m0; 
  7.  
  8.     public $Proxy0(InvocationHandler var1) throws  { 
  9.         super(var1); 
  10.     } 
  11.  
  12.     public final boolean equals(Object var1) throws  { 
  13.         try { 
  14.             return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); 
  15.         } catch (RuntimeException | Error var3) { 
  16.             throw var3; 
  17.         } catch (Throwable var4) { 
  18.             throw new UndeclaredThrowableException(var4); 
  19.         } 
  20.     } 
  21.  
  22.     public final String toString() throws  { 
  23.         try { 
  24.             return (String)super.h.invoke(this, m2, (Object[])null); 
  25.         } catch (RuntimeException | Error var2) { 
  26.             throw var2; 
  27.         } catch (Throwable var3) { 
  28.             throw new UndeclaredThrowableException(var3); 
  29.         } 
  30.     } 
  31.  
  32.     public final String getData(String var1) throws  { 
  33.         try { 
  34.             //m3就是IProvider 接口的getData方法  
  35.             //super.h 是父类java.lang.reflect.Proxy的属性 InvocationHandler 
  36.             return (String)super.h.invoke(this, m3, new Object[]{var1}); 
  37.         } catch (RuntimeException | Error var3) { 
  38.             throw var3; 
  39.         } catch (Throwable var4) { 
  40.             throw new UndeclaredThrowableException(var4); 
  41.         } 
  42.     } 
  43.  
  44.     public final int hashCode() throws  { 
  45.         try { 
  46.             return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); 
  47.         } catch (RuntimeException | Error var2) { 
  48.             throw var2; 
  49.         } catch (Throwable var3) { 
  50.             throw new UndeclaredThrowableException(var3); 
  51.         } 
  52.     } 
  53.  
  54.     static { 
  55.         try { 
  56.             m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); 
  57.             m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 
  58.             //m3就是IProvider 接口的getData方法 
  59.             m3 = Class.forName("aop.IProvider").getMethod("getData", new Class[]{Class.forName("java.lang.String")}); 
  60.             m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 
  61.         } catch (NoSuchMethodException var2) { 
  62.             throw new NoSuchMethodError(var2.getMessage()); 
  63.         } catch (ClassNotFoundException var3) { 
  64.             throw new NoClassDefFoundError(var3.getMessage()); 
  65.         } 
  66.     } 

重点在 return (String)super.h.invoke(this, m3, new Object[]{var1});代码。

$Proxy0继承Proxy类,实现了IProvider接口,所以也有getData()函数,而getData函数调用的是执行者InvocationHandler的invoke方法,m3是通过反射拿到的Method对象,所以看getData调用invoke传递的。三个参数,第一个是Proxy对象,第二个是getData方法对象,第三个是参数。

总结一下:

动态代理实际上就是帮我们在JVM内存中直接重新生成了代理类class和对应类对象,然后通过执行者InvocationHandler调用被代理对象SimpleProvider。

Spring AOP中的代理

Spring代理其实是对JDK动态代理和CGLIB代理进行了封装,并且引入了AOP的概念,同时引入了AspectJ中的一些注解:@pointCut @After 等。

  1. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 
  2.            if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { 
  3.    Class targetClass = config.getTargetClass(); 
  4.    if (targetClass == null) { 
  5.     throw new AopConfigException("TargetSource cannot determine target class: " + 
  6.       "Either an interface or a target is required for proxy creation."); 
  7.    } 
  8.       // 如果是接口,使用jdk代理  
  9.    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { 
  10.     return new JdkDynamicAopProxy(config); 
  11.    } 
  12.       //否则使用cglib 
  13.    return new ObjenesisCglibAopProxy(config); 
  14.   } 
  15.   else { 
  16.    return new JdkDynamicAopProxy(config); 
  17.   } 
  18.  } 

 

来源:安琪拉的博客内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯