深入理解JDK动态代理

时间:2021-05-02

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

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

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

静态代理

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

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

举个栗子:

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

  • publicinterfaceIProvider{
  • ObjectgetData(Stringjson);
  • }
  • 接口的实现类如下:

  • publicclassSimpleProviderimplementsIProvider{
  • @Override
  • publicObjectgetData(Stringjson){
  • //解析json拿到数据
  • returnparseJson(json);
  • }
  • 那现在有个需求,需要对 getData 方法做限流,指定用静态代理的方式。

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

  • publicclassProviderProxyimplementsIProvider{
  • //持有一个被代理对象的引用(在这里是SimpleProvider)
  • IProvideriProvider;
  • publicStaticProviderProxy(IProvideriProvider){
  • this.iProvider=iProvider;
  • }
  • @Override
  • publicObjectgetData(Stringjson){
  • //做限流检查
  • if(callSpeed>flowLimt){
  • //流量超限
  • throwFlowLimitException();
  • }
  • Objectobject=iProvider.getData(json);
  • returnobject;
  • }
  • }
  • //main
  • publicstaticvoidmain(String[]args){
  • IProviderprovider=newProviderProxy(newSimpleProvider());
  • provider.getData("{\"data\":{}}");
  • }
  • 这就是静态代理,代理类(ProviderProxy)实现和需要做方法增强的被代理类(SimpleProvider)实现同一个接口(IProvider),方法具体实现上做增强,这里是限流检查。

    动态代理

    Java 动态代理

    • 动态代理类:在程序运行时,通过反射机制动态生成。
    • 动态代理类通常代理接口下的所有类。静态一般指定某个类代理。
    • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。静态是编译期确定的。

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

  • publicclassProviderHandlerimplementsInvocationHandler{
  • Objecttarget;
  • publicObjectbind(Objecttarget){
  • this.target=target;
  • //这里生成了代理对象
  • returnProxy.newProxyInstance(target.getClass().getClassLoader(),
  • target.getClass().getInterfaces(),this);
  • }
  • @Override
  • publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
  • //限流
  • flowLimit(args);
  • Objectobj=method.invoke(target,args);
  • //打印日志
  • logger.info("printlog...");
  • returnobj;
  • }
  • }
  • //main
  • publicstaticvoidmain(String[]args){
  • ProviderHandlerproviderHandler=newProviderHandler();
  • IProvideriProvider=(IProvider)providerHandler.bind(newSimpleProvider());
  • iProvider.getData("weibo.data");
  • }
  • 这里有三个对象:

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

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

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

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

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

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

    实际怎么实现的呢?

    newProxyInstance源码

  • publicstaticObjectnewProxyInstance(ClassLoaderloader,
  • Class<?>[]interfaces,
  • InvocationHandlerh)
  • throwsIllegalArgumentException
  • {
  • //对Invocationhandler做判空处理
  • Objects.requireNonNull(h);
  • //复制[IProvider接口]
  • finalClass<?>[]intfs=interfaces.clone();
  • //根据IProvider的类加载器IProvider接口生成了Proxy类,关键:根据类加载器和接口对象在JVM缓存中生成一个类对象
  • Class<?>cl=getProxyClass0(loader,intfs);
  • //获取构造器
  • finalConstructor<?>cons=cl.getConstructor(constructorParams);
  • //保存InvocationHandler的引用
  • finalInvocationHandlerih=h;
  • //通过构造器实例化Proxy代理对象
  • returncons.newInstance(newObject[]{h});
  • }
  • 代码注释写的很清晰。

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

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

  • byte[]classFile=ProxyGenerator.generateProxyClass("$Proxy0",WeiboProvider.class.getInterfaces());
  • Stringpath="C:**/IdeaProjects/study/out/production/study/SimpleProxy.class";
  • try(FileOutputStreamfos=newFileOutputStream(path)){
  • fos.write(classFile);
  • fos.flush();
  • System.out.println("代理类class文件写入成功");
  • }catch(Exceptione){
  • System.out.println("写文件错误");
  • }
  • 反编译Proxy0如下:

  • //Proxy0是动态生成的类,继承自Proxy,实现了IProvider接口
  • publicfinalclass$Proxy0extendsProxyimplementsIProvider{
  • privatestaticMethodm1;
  • privatestaticMethodm2;
  • privatestaticMethodm3;
  • privatestaticMethodm0;
  • public$Proxy0(InvocationHandlervar1)throws{
  • super(var1);
  • }
  • publicfinalbooleanequals(Objectvar1)throws{
  • try{
  • return((Boolean)super.h.invoke(this,m1,newObject[]{var1})).booleanValue();
  • }catch(RuntimeException|Errorvar3){
  • throwvar3;
  • }catch(Throwablevar4){
  • thrownewUndeclaredThrowableException(var4);
  • }
  • }
  • publicfinalStringtoString()throws{
  • try{
  • return(String)super.h.invoke(this,m2,(Object[])null);
  • }catch(RuntimeException|Errorvar2){
  • throwvar2;
  • }catch(Throwablevar3){
  • thrownewUndeclaredThrowableException(var3);
  • }
  • }
  • publicfinalStringgetData(Stringvar1)throws{
  • try{
  • //m3就是IProvider接口的getData方法
  • //super.h是父类java.lang.reflect.Proxy的属性InvocationHandler
  • return(String)super.h.invoke(this,m3,newObject[]{var1});
  • }catch(RuntimeException|Errorvar3){
  • throwvar3;
  • }catch(Throwablevar4){
  • thrownewUndeclaredThrowableException(var4);
  • }
  • }
  • publicfinalinthashCode()throws{
  • try{
  • return((Integer)super.h.invoke(this,m0,(Object[])null)).intValue();
  • }catch(RuntimeException|Errorvar2){
  • throwvar2;
  • }catch(Throwablevar3){
  • thrownewUndeclaredThrowableException(var3);
  • }
  • }
  • static{
  • try{
  • m1=Class.forName("java.lang.Object").getMethod("equals",newClass[]{Class.forName("java.lang.Object")});
  • m2=Class.forName("java.lang.Object").getMethod("toString",newClass[0]);
  • //m3就是IProvider接口的getData方法
  • m3=Class.forName("aop.IProvider").getMethod("getData",newClass[]{Class.forName("java.lang.String")});
  • m0=Class.forName("java.lang.Object").getMethod("hashCode",newClass[0]);
  • }catch(NoSuchMethodExceptionvar2){
  • thrownewNoSuchMethodError(var2.getMessage());
  • }catch(ClassNotFoundExceptionvar3){
  • thrownewNoClassDefFoundError(var3.getMessage());
  • }
  • }
  • }
  • 重点在 return (String)super.h.invoke(this, m3, new Object[]{var1});代码。

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

    总结一下:

    • 动态代理的本质就是,生成一个继承自Proxy,实现被代理接口(IProvider)的类 - Proxy0。
    • Proxy0 持有InvocationHandler实例,InvocationHandler 持有SimpleProvider实例。Proxy0调用接口 getData方法时,先传递给InvocationHandler,InvocationHandler再传递给SimpleProvider实例。

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

    Spring AOP中的代理

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

  • publicAopProxycreateAopProxy(AdvisedSupportconfig)throwsAopConfigException{
  • if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){
  • Class<?>targetClass=config.getTargetClass();
  • if(targetClass==null){
  • thrownewAopConfigException("TargetSourcecannotdeterminetargetclass:"+
  • "Eitheraninterfaceoratargetisrequiredforproxycreation.");
  • }
  • //如果是接口,使用jdk代理
  • if(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){
  • returnnewJdkDynamicAopProxy(config);
  • }
  • //否则使用cglib
  • returnnewObjenesisCglibAopProxy(config);
  • }
  • else{
  • returnnewJdkDynamicAopProxy(config);
  • }
  • }
  • 原文地址:https://mp.weixin.qq.com/s/34LAnTGhqe7DTYmT1PRQJg

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章