首页>>后端>>java->设计和分析Java动态代理的技术实现

设计和分析Java动态代理的技术实现

时间:2023-12-05 本站 点击:0

你了解Java中的动态代理的作用是什么吗?动态代理又有哪些实现方式,真正的实现原理是什么呢? 掌握本章内容,在面试遇到aop如何实现的问题时,就不会只知道jdk代理接口和cglib集成子类、不知道真正的实现原理了。

本文将会讲解动态代理技术能够解决的问题、如何自己设计实现动态代理、以及Java中是如何实现的以及一些开源框架中的典型应用。

dynamic proxy是什么,可以用来做什么?

通过dynamic proxy动态代理,我们可以在运行为接口创建实现类,或为特定类进行功能增强(aop功能)。常用在框架实现中,在retrofit、spring、dubbo中都大量使用了动态代理技术,框架并不能提前知道用户编写的类是什么样的,而通过动态代理,可以在运行时进行实现接口或增强用户类。

举一些实际的例子。 例如在retrofit(一种Http调用框架)中,用户通过下面的方式来定义和使用远程http接口。

publicinterfaceGitHubService{@GET("users/{user}/repos")Call<List<Repo>>listRepos(@Path("user")Stringuser);}

先定义远程的http的接口形式,通过注解标注http method, url, 参数,返回对象等。然后就可以利用Retrofit类传入这个接口获得对象进行调用了。

Retrofitretrofit=newRetrofit.Builder().baseUrl("https://api.github.com/").build();GitHubServiceservice=retrofit.create(GitHubService.class);

背后就是使用了动态代理技术来生成这个接口类的实现,在用户调用listRepos方法的时候,封装成http请求进行调用,并把结果反序列化成接口的返回值返回给用户,用户不需要感知这些实现细节,大大减少了使用成本。

如果不用retrofit proxy,需要怎么实现呢?

List<Repo>listRepos(Stringuser)throwsException{Stringurl=String.format("https://api.github.com/users/%s/repos",user);OkHttpClientclient=newOkHttpClient().newBuilder().build();Requestrequest=newRequest.Builder().url(url).method("GET",null).build();Responseresponse=client.newCall(request).execute();Stringbody=response.body().string();returnJSONUtils.jsonToModel(body,List.class);}

不用retrofit直接用okhttp开发的话,就需要使用者手动拼url、把请求参数构建成Map,调用okhttp接口后,手动把string body反序列化转成业务类对象。 这样就需要使用者了解底层实现和调用方式,这些额外的细节信息增加了使用成本。使用动态代理则可以屏蔽这些可以由框架实现的细节,让使用者面向接口编程,把更多的精力放在实现业务功能而不是底层细节上。

Java中动态代理的不同实现方式

我们来实现动态代理,该如何实现

在Java生态中有多种实现动态代理的方式,在介绍各个实现方案之前,我们不妨想一下,如果我们要来实现这个动态代理功能该如何设计呢?

我们需要先明确动态代理要实现的功能,以为接口创建动态代理为例,动态代理工厂(Proxy工厂)要负责为用户指定的接口类(被Proxy目标类)创建实现类,并且能够让 用户传入指定的实现(InvocationHandler),最终在运行时得到一个这个接口类的实现类(Proxy结果类)的对象实例。

这里的关键点就是,明确我们要生成一个什么样的类,以及怎么在运行时生成这个类。

第一步先定义我们要生成的类的内容,我们要生成的类,需要实现用户定义的接口类,并且实现这个接口的所有方法。实现的方法体内,调用用户传入的InvocationHandler对象处理,并返回对应的结果。

例如我们把InvocationHandler定义为

interfaceInvocationHandler{Objectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable;}

用户定义的接口类(被Proxy类)为

interfaceService{Stringhello();}

那么我们要生成的代理类ProxyImpl可以是如下的类,构造函数接收InvocationHandler对象,然后在具体的方法中,转发给InvocationHnalder对象处理。

classProxyImplimplementsService{privatestaticMethodhelloMethod;static{try{helloMethod=Service.class.getDeclaredMethod("hello",int.class);}catch(NoSuchMethodExceptione){e.printStackTrace();}}privatefinalInvocationHandlerinvocationHandler;publicProxyImpl(InvocationHandlerinvocationHandler){this.invocationHandler=invocationHandler;}@OverridepublicStringhello(intarg){try{return(String)invocationHandler.invoke(this,helloMethod,newObject[]{arg});}catch(Throwablethrowable){thrownewRuntimeException(throwable);}}}

明确了要生成的类的内容,下一步就是该如何实现了。该怎么才能在运行时生成一个具体的类呢?幸好Java的类加载机制提供了这种可能,Java中具备在运行时加载类的能力,我们所需要做的就是在运行时生成这个类的class文件字节码。

class文件格式在Java虚拟机规范(https://docs.oracle.com/javase/specs/jvms/)中有的明确详细介绍,我们按照规范拼出对应的字节码就可以了。 自己拼字节码繁琐且容易出错(就像写机器码),我们可以利用asm、bytebuddy等字节码工具来提高效率减少错误(就像用汇编)。 除了拼字节码还有一种方式是拼好源代码然后使用Java编译器编译源代码到字节码,我们可以使用java compiler api或javassist来实现。

最终得到需要的类的字节码后,再使用classloader来进行加载就可以得到一个可以使用的class对象了,最后利用反射创建对应类的实例对象返回给用户。

Java中的不同动态代理的实现和以及各自优缺点

在Java生态中,大家常用的动态代理的方式有jdk proxy和cglib,这两种方式提供了直接的接口。javassist,asm是更底层通用的字节码层面操作工具,它们也能实现对等的功能。

实现方案优点缺点jdk proxyjdk自带实现,无需外部依赖只能支持接口的代理cglib能够支持普通类的代理,并能够实现aop等功能需要cglib依赖javassist,asm可以不在编译期依赖,在运行时使用(通过javaagent或attach),来实现类似框架监控、日志、tracing等能力比较底层,需要单独开发aop这种功能

jdk proxy的实现的详细分析

最后我们以jdk proxy为例,结合代码看一下具体的实现。

我们顺着jdk proxy的方法入口(java.lang.reflect.Proxy#newProxyInstance),可以看到Proxy先得到了Constructor,然后利用反射调用Constructor传入InvocationHandler参数得到了代理类实例。

getProxyConstructor方法中,调用了ProxyBuilder来构建Proxy代理类。

最终生成代理类的函数是java.lang.reflect.ProxyGenerator#generateClassFile。

generateClassFile做的事情就是我们在前面提到的按照class文件字节码格式要求拼出对应的字节码数组。

会针对每个proxy方法创建Method字段,创建静态代码块获取Method,然后创建各个Proxy类的方法的实现。

动态代理在开源框架中的应用

下面分析一下retrofit中对于动态代理的使用。 如本文最开始的示例,我们通过retrofit.create(GitHubService.class)就可以生成GitHubService这个接口的代理类的对象。

create方法的实现也非常简洁,classloader使用传入接口类的classloader,接口列表使用传入的接口类,invocationHandler的实现是通过method生成调用okhttp的逻辑来调用(对jdk8的default方法进行了特殊处理,因为default方法是用户的实现,不需要代理)。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/12178.html