Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

学习 InvocationHandler 方式的动态代理

NotificationsYou must be signed in to change notification settings

fanofxiaofeng/basic-proxy

Repository files navigation

学习InvocationHandler 方式的动态代理

参考资料

  1. (豆瓣链接) Java 核心技术(卷I) 中的 6.5 小节
  2. (GitHub) JavaSE6Tutorial 第 16 章 反射(Reflection) 中的 16.2.5 小节(Proxy 類別)
  3. (掘金) 动态代理 Proxy 源码分析
  4. (Stack Overflow) How to create a directory in Java?
  5. (简书) JDK动态代理
  6. (ImportNew) Java Proxy 和 CGLIB 动态代理原理
  7. (GitHub) Proxy.java in openjdk
  8. (Stack Overflow) One answer to "Serializing a proxy class to file"

正文

动态代理涉及以下三个对象

  1. 代理对象(称为p,意为proxy)
  2. 处理器对象(称为h,意为handler)
  3. 真正干活的对象(称为r,意为robot,也就是下文会提到的名为打扫王 的机器人)

我自己想了个例子,不知道算不算贴切。大雄的妈妈大雄 打扫房间,大雄 不想做,于是把哆啦A梦 找来,让其打扫,哆啦A梦 从口袋里拿出了一个宝贝,名叫打扫王,最后打扫王 把房间打扫好了。

在这个例子里,大雄 是代理对象p,哆啦A梦 是处理器对象h打扫王 是真正干活的对象r

当我们调用p 上的一个方法时(大雄的妈妈大雄 打扫房间),p 会去调用h 中的名为invoke 的方法(大雄哆啦A梦 想办法并落实),而在这个invoke 方法里,r 中的相应方法会被调用(打扫王 干活)。

用到的代码

我们可以建立一个小项目 来粗略地模拟上述例子。该项目中的basic-proxy-introduction 模块与本文有关。

module1.png

红框中的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();    }}

通过newProxyInstance() 方法来产生代理类对象

相关代码的简要解释

我们可以通过调用public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 来动态地产生一个代理类对象pnewProxyInstance() 方法的 javadoc 中有如下内容

Returns a proxy instance for the specified interfaces that dispatches method invocations to the specified invocation handler.

newProxyInstance(...) 方法有 3 个入参。

  1. 入参loader 是类加载器。 可以用Thread.currentThread().getContextClassLoader() 来获得一个类加载器
  2. 入参interfaces 是一个数组。 通过它来描述我们希望代理类实现的接口。 本文以com.study.proxy.Clean 这个接口为例进行演示
  3. 入参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 接口,所以可以调用pwork() 方法

proxy.work();

运行结果

运行BasicProxy 中的main() 方法,效果如下图image.png

如果希望直接通过命令行运行的话,可以在项目顶层目录执行如下命令

mvn clean packagecd basic-proxy-introduction/java -jar target/basic-study-introduction.jar

分析

  1. BasicProxymain() 运行时,我们会将局部变量proxy 的实际类型输出(局部变量proxy 的静态类型为com.study.proxy.Clean,实际类型为jdk.proxy1.$Proxy0)
  2. DoraemonHandlerinvoke() 被调用时,会将入参proxy 的实际类型输出(入参proxy 的静态类型为java.lang.Object,实际类型为jdk.proxy1.$Proxy0)

先提出三个问题

  1. 不难看出12 中的proxy 的实际类型相同,那么它们是否就是同一个对象呢?(是的)
  2. proxy 的实际类型为jdk.proxy1.$Proxy0,这个类的结构如何,为何可以把对proxy 的一些方法调用都丢给handler 来处理?
  3. 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 文件了效果如下图class 文件

IDEA 中可以直接看到反编译后的结果,我把IDEA 里展示的结果附在了$Proxy0.java 中。

构造函数里有一个InvocationHandler 对象,其实就是通过构造函数把ph 关联起来的

public$Proxy0(InvocationHandlervar1) {super(var1);}

$Proxy0 的构造函数里会调用父类java.lang.reflect.Proxy 的构造函数$Proxy0 的构造函数

我们可以在java.lang.reflect.Proxy 类的构造函数里加上一个断点(具体位置如下图)在 java.lang.reflect.Proxy 里加一个断点然后debugBasicProxy 类的main 方法debug应该会在刚才的断点停下来。此时就会看到在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. 不难看出12 中的proxy 的实际类型相同,那么它们是否就是同一个对象呢?(是的)
  2. proxy 的实际类型为jdk.proxy1.$Proxy0,这个类的结构如何,为何可以把对proxy 的一些方法调用都丢给handler 来处理?
  3. proxy 的所有方法调用都会转化为handler 的方法调用吗?
第一个问题

上述代码中的super.h 对应的就是jdk.proxy1.$Proxy0 的构造函数里的var1,这个super.h 就是我们说的处理器对象h。而pwork() 方法调用也会转化为hinvoke() 方法调用,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 这些静态数据成员保存了需要代理的方法的信息,在调用hinvoke() 方法时,它们会作为参数参与函数调用的过程。

com.study.proxy.Clean 接口中只有一个work() 方法,m3 与之对应。那么m0,m1m2 是做什么的呢?看了代码后就会明白,它们和java.lang.Object 中定义的三个方法对应,具体如下

名称java.lang.Object 中的哪个方法对应
m0hashCode()
m1equals(java.lang.Object)
m2toString()
第三个问题

jdk.proxy1.$Proxy0 中的m3com.study.proxy.Clean 接口中的work() 方法对应。jdk.proxy1.$Proxy0 中的m0,m1,m2java.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}.

运行结果如下图所示

compare.png

其他

本项目中还有其他的模块

About

学习 InvocationHandler 方式的动态代理

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors2

  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp