- Notifications
You must be signed in to change notification settings - Fork1
fanofxiaofeng/basic-proxy
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
学习InvocationHandler
方式的动态代理
- (豆瓣链接) Java 核心技术(卷I) 中的 6.5 小节
- (GitHub) JavaSE6Tutorial 第 16 章 反射(Reflection) 中的 16.2.5 小节(Proxy 類別)
- (掘金) 动态代理 Proxy 源码分析
- (Stack Overflow) How to create a directory in Java?
- (简书) JDK动态代理
- (ImportNew) Java Proxy 和 CGLIB 动态代理原理
- (GitHub) Proxy.java in openjdk
- (Stack Overflow) One answer to "Serializing a proxy class to file"
动态代理涉及以下三个对象
- 代理对象(称为
p
,意为proxy
) - 处理器对象(称为
h
,意为handler
) - 真正干活的对象(称为
r
,意为robot
,也就是下文会提到的名为打扫王 的机器人)
我自己想了个例子,不知道算不算贴切。大雄的妈妈 让大雄 打扫房间,大雄 不想做,于是把哆啦A梦 找来,让其打扫,哆啦A梦 从口袋里拿出了一个宝贝,名叫打扫王,最后打扫王 把房间打扫好了。
在这个例子里,大雄 是代理对象p
,哆啦A梦 是处理器对象h
,打扫王 是真正干活的对象r
当我们调用p
上的一个方法时(大雄的妈妈 让大雄 打扫房间),p
会去调用h
中的名为invoke
的方法(大雄 让哆啦A梦 想办法并落实),而在这个invoke
方法里,r
中的相应方法会被调用(打扫王 干活)。
我们可以建立一个小项目 来粗略地模拟上述例子。该项目中的basic-proxy-introduction 模块与本文有关。
红框中的5个文件内容如下
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>basic-proxy-introduction</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>com.study</groupId> <artifactId>basic-proxy</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.13.1</version> </dependency> </dependencies> <build><!-- Please refer to https://maven.apache.org/shared/maven-archiver/examples/classpath.html--> <finalName>basic-proxy-introduction</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>com.study.proxy.BasicProxy</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> <configuration> <argLine>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</argLine> </configuration> </plugin> </plugins> </build></project>
其中定义了Clean
接口
packagecom.study.proxy;/** * 打扫接口 */publicinterfaceClean {voidwork();}
打扫王 实现了Clean
接口
packagecom.study.proxy;publicclassKingRobotimplementsClean {publicvoidwork() {System.out.println("\uD83E\uDD16: 我是 打扫王");for (inti =0;i <=100;i +=10) {System.out.printf("\uD83E\uDD16: 打扫房间的任务完成了 %s%%%n",i); }System.out.println("\uD83E\uDD16: 打扫房间的任务完成了✅");System.out.println("\uD83E\uDD16:\uD83D\uDC4B"); }}
哆啦A梦 充当处理器(handler)
packagecom.study.proxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;publicclassDoraemonHandlerimplementsInvocationHandler {/** * 干活的机器人(即 "打扫王") */privatefinalObjecttarget;publicDoraemonHandler(Objecttarget) {this.target =target; }/** * @param proxy 代理对象, 即 大雄 * @param method 描述略 * @param args 描述略 * @return 描述略 * @throws Throwable 描述略 */publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable {System.out.println("proxy 的类型是 " +proxy.getClass().getName());System.out.println("被代理的方法的名称为: " +method.getName());System.out.println("我是哆啦A梦,脏活累活还是丢给我兜里的宝贝机器人来做吧➡️➡️➡️\uD83E\uDD16");returnmethod.invoke(target,args); }}
BasicProxy.java
(下方展示的内容与最终版本有些差异)
packagecom.study.proxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;publicclassBasicProxy {publicstaticvoidmain(String[]args) {InvocationHandlerhandler =newDoraemonHandler(newKingRobot());Cleanproxy = (Clean)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),newClass[]{Clean.class},handler);System.out.printf("proxy.hashCode() is: %s%n",proxy.hashCode());System.out.println();proxy.work(); }}
我们可以通过调用public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
来动态地产生一个代理类对象p
,newProxyInstance()
方法的 javadoc 中有如下内容
Returns a proxy instance for the specified interfaces that dispatches method invocations to the specified invocation handler.
newProxyInstance(...)
方法有 3 个入参。
- 入参
loader
是类加载器。 可以用Thread.currentThread().getContextClassLoader()
来获得一个类加载器 - 入参
interfaces
是一个数组。 通过它来描述我们希望代理类实现的接口。 本文以com.study.proxy.Clean
这个接口为例进行演示 - 入参
h
是一个处理器。InvocationHandler
是一个接口, 处理器通过调用InvocationHandler
中的public Object invoke(Object proxy, Method method, Object[] args)
方法来完成代理过程。在本例中,我们用DoraemonHandler
类来实现InvocationHandler
接口。DoraemonHandler
类的构造函数中需要传入实际干活的对象r
(即打扫王 的实例)。对应的代码为
InvocationHandlerhandler =newDoraemonHandler(newKingRobot());
3 个入参我们都可以获取到了,那么现在就可以产生代理对象(即大雄)了
Cleanproxy = (Clean)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),newClass[]{Clean.class},handler);
由于代理对象p
也实现了Clean
接口,所以可以调用p
的work()
方法
proxy.work();
运行BasicProxy
中的main()
方法,效果如下图
如果希望直接通过命令行运行的话,可以在项目顶层目录执行如下命令
mvn clean packagecd basic-proxy-introduction/java -jar target/basic-study-introduction.jar
- 在
BasicProxy
的main()
运行时,我们会将局部变量proxy
的实际类型输出(局部变量proxy
的静态类型为com.study.proxy.Clean
,实际类型为jdk.proxy1.$Proxy0
) - 在
DoraemonHandler
的invoke()
被调用时,会将入参proxy
的实际类型输出(入参proxy
的静态类型为java.lang.Object
,实际类型为jdk.proxy1.$Proxy0
)
先提出三个问题
- 不难看出
1
和2
中的proxy
的实际类型相同,那么它们是否就是同一个对象呢?(是的) proxy
的实际类型为jdk.proxy1.$Proxy0
,这个类的结构如何,为何可以把对proxy
的一些方法调用都丢给handler
来处理?- 对
proxy
的所有方法调用都会转化为handler
的方法调用吗?
参考资料[8] 中提到了保存jdk.proxy1.$Proxy0
对应的 class 文件的方法。我们在生成代理对象之前,加入如下代码
static {System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");}
那么现在BasicProxy.java
变为如下的样子
packagecom.study.proxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;publicclassBasicProxy {static {System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true"); }publicstaticvoidmain(String[]args) {InvocationHandlerhandler =newDoraemonHandler(newKingRobot());Cleanproxy = (Clean)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),newClass[]{Clean.class},handler);System.out.printf("proxy.hashCode() is: %s%n",proxy.hashCode());System.out.println();proxy.work(); }}
现在可以成功保存jdk.proxy1.$Proxy0
这个类对应的class
文件了效果如下图
在IDEA
中可以直接看到反编译后的结果,我把IDEA
里展示的结果附在了$Proxy0.java 中。
构造函数里有一个InvocationHandler
对象,其实就是通过构造函数把p
和h
关联起来的
public$Proxy0(InvocationHandlervar1) {super(var1);}
$Proxy0
的构造函数里会调用父类java.lang.reflect.Proxy
的构造函数
我们可以在java.lang.reflect.Proxy
类的构造函数里加上一个断点(具体位置如下图)然后
debug
BasicProxy
类的main
方法应该会在刚才的断点停下来。此时就会看到在
java.lang.reflect.Proxy
的构造函数里,h
的确是DoraemonHandler
的实例
与work()
方法相关的一段代码如下
publicfinalvoidwork() {try {super.h.invoke(this,m3, (Object[])null); }catch (RuntimeException |Errorvar2) {throwvar2; }catch (Throwablevar3) {thrownewUndeclaredThrowableException(var3); }}
三个问题的答案,我把刚才提到的三个问题再贴一下
- 不难看出
1
和2
中的proxy
的实际类型相同,那么它们是否就是同一个对象呢?(是的)proxy
的实际类型为jdk.proxy1.$Proxy0
,这个类的结构如何,为何可以把对proxy
的一些方法调用都丢给handler
来处理?- 对
proxy
的所有方法调用都会转化为handler
的方法调用吗?
上述代码中的super.h
对应的就是jdk.proxy1.$Proxy0
的构造函数里的var1
,这个super.h
就是我们说的处理器对象h
。而p
的work()
方法调用也会转化为h
的invoke()
方法调用,invoke()
方法的第一个参数是this
,也就是p
对象了。所以第一个问题的答案为是的。
jdk.proxy1.$Proxy0
中有一个静态数据成员m3
,它与com.study.proxy.Clean
接口中的work()
方法相对应。
当我们通过p
调用work()
方法时,h
中的invoke()
方法会被调用(在调用invoke()
方法时,第二个参数填的就是m3
)。
所以jdk.proxy1.$Proxy0
中的m0
,m1
,m2
,m3
这些静态数据成员保存了需要代理的方法的信息,在调用h
的invoke()
方法时,它们会作为参数参与函数调用的过程。
com.study.proxy.Clean
接口中只有一个work()
方法,m3
与之对应。那么m0
,m1
和m2
是做什么的呢?看了代码后就会明白,它们和java.lang.Object
中定义的三个方法对应,具体如下
名称 | 与java.lang.Object 中的哪个方法对应 |
---|---|
m0 | hashCode() |
m1 | equals(java.lang.Object) |
m2 | toString() |
jdk.proxy1.$Proxy0
中的m3
与com.study.proxy.Clean
接口中的work()
方法对应。jdk.proxy1.$Proxy0
中的m0
,m1
,m2
与java.lang.Object
中的hashCode()
,equals(java.lang.Object)
,toString()
方法分别对应。所以如果我们通过p
来调用上述的四个方法之外的方法的话,就不会被代理了。如果我们在BasicProxy.java
里通过p
来调用getClass()
方法和hashCode()
方法,会看到前者不会 被代理,后者会 被代理。
不过为何java.lang.Object
中的这三个方法也会被代理对象处理呢?参考文章[7] 中提到
An invocation of the {@code hashCode},{@code equals}, or {@code toString} methods declared in{@code java.lang.Object} on a proxy instance will be encoded anddispatched to the invocation handler's {@code invoke} method inthe same manner as interface method invocations are encoded anddispatched, as described above. The declaring class of the{@code Method} object passed to {@code invoke} will be{@code java.lang.Object}. Other public methods of a proxyinstance inherited from {@code java.lang.Object} are notoverridden by a proxy class, so invocations of those methods behavelike they do for instances of {@code java.lang.Object}.
运行结果如下图所示
本项目中还有其他的模块
- basic-proxy-annotation: 注解中的一些功能也是通过动态代理来实现的,可以参考动态代理在注解(
annotation
)中的应用 一文 - basic-proxy-impl: 利用
ASM
来生成动态代理类的byte[]
- basic-proxy-kotlin-impl: 与basic-proxy-impl 类似,但是改用
kotlin
实现
About
学习 InvocationHandler 方式的动态代理
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.