在开始动态代理的描述之前,让我们认识下代理。代理:即代替担任执行职务。在面向对象世界中,即寻找另一个对象代理目标对象与调用者交互。Java中分为静态代理和动态代理。这里对于静态代理不做详述。它们之间的区别,即前者是编译时生成代理对象,后者在运行时生成代理对象,体现一静一动。

一.前言

Java中实现动态代理的技术非常繁多,目前主流技术是以下三种:

  • JDK动态代理
  • CGLIB
  • Javassist

JDK动态代理和CGLIB在Spring AOP中有所使用,Javassit在Hibernate中有所使用。当然还有很多其他的动态代理实现技术:ASM、Byte Buddy、JiteScript、Proxetta等等。这里只对JDK动态代理和CGLIB做详细说明。关于其他代理技术的介绍可以参考 Are there alternatives to cglib?

本篇文章只对JDK动态代理的实现做深入了解,CGLIB原理后续文章再做详细介绍。

二.实现动态代理的Demo

创建JDK动态代理可以分为以下几个步骤:

  • 创建被代理对象的接口
  • 创建被代理对象
  • 创建InvocationHandler
  • 创建代理对象并调用
1.创建被代理对象的接口
public interface EchoService {
void echo(String message);
}
2. 创建被代理对象
public class EchoServiceImpl implements EchoService {
@Override
public void echo(String message) {
System.out.println(message);
}
}
3. 创建InvocationHandler
public class DefaultCommonHandler implements InvocationHandler {
private Object object; public DefaultCommonHandler(Object o) {
this.object = o;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke");
return method.invoke(object, args);
}
}
4.创建代理对象
public class JdkDynamicProxyTest {

    public static void main(String[] args) throws IOException {
InvocationHandler handler = new DefaultCommonHandler(new EchoServiceImpl());
EchoService proxyObject = (EchoService) Proxy
.newProxyInstance(EchoService.class.getClassLoader(), new Class[]{EchoService.class}, handler);
proxyObject.echo("invoking");
}
}

上述执行结果:

before invoke

invoking

从以上的编写过程和执行结果中,应该大致的感受到动态代理的运行逻辑,接下来深入理解其原理。

三.深入认知原理前的基础准备

在进一步深入认知JDK动态代理原理之前,先来学习下JDK动态代理实现的基础。Java中提供两个api:Proxy类和InvocationHandler调用委派处理器,用于实现JDK动态代理。

先来看下Proxy的Java docs描述:

{@code Proxy} provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods

从描述中可以看出,Proxy有以下特点:

  • Proxy提供了静态方法用于运行时创建代理类的class对象和代理类的实例对象
  • Proxy是所有的动态代理类的超类

Proxy提供了两种方式创建代理类的对象:

1.先创建代理的类的class对象,然后通过class对象获取构造函数,通过构造函数对象Constructor创建实例对象
InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(),
new Class[] { Foo.class });
Foo f = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class })
.newInstance(new Object[] { handler });
2. 通过Proxyy停的newProxyInstance方法直接创建代理类的实例。本质上也是上面的过程。
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class }, handler);

本篇文章主要目的是介绍动态代理的原理,不是对Proxy的具体实现细节做深入了解。这里不再赘述Proxy的细节。

再来看下InvocationHandler。InvocationHandler是动态代理实现的关键,代理模式中的扩展点就是由其承担的。代理的逻辑都是从InvocationHandler中的invoke方法实现。首先看下Java docs描述:

{@code InvocationHandler} is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance,the method invocation is encoded and dispatched to the {@code invoke} method of its invocation handler

从描述中得知,InvocationHandler与代理类的实例有关,每个代理类中都持有一个实现InvocationHandler接口的实例。代理类上的方法的调用将被组织和委派到InvocationHandler的统一方法invoke上。

下面从源码上简单介绍下上述的实现:

/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h; /**
* Prohibits instantiation.
*/
private Proxy() {
} /**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*/
protected Proxy(InvocationHandler h) {
doNewInstanceCheck();
this.h = h;
}

以上代码片段是Proxy中的成员域和构造方法。Proxy类中持有InvocationHandler实例,且封装访问为protected,并在Proxy的构造器中初始化。前面提到所有的代理类都继承了Proxy类,所以代理类中拥有InvocationHandler实例,并在代理类初始化时调用父类Proxy的构造器对InvocationHandler进行赋值。

四.分析代理对象中的细节

如果要分析代理对象的细节,必然需要阅读代理对象中代码实现。

  1. 可以阅读字节码,但是可读性太低且技术门槛过高
  2. 反编译代理对象的.class文件

以上两种方式都需要获取代理对象的.class字节码文件。Java中提供了参数配置生成代理对象的class文件。

Java中生成class文件方法是ProxyGenerator,生成代码如下:

public static byte[] generateProxyClass(final String var0, Class[] var1) {
ProxyGenerator var2 = new ProxyGenerator(var0, var1);
final byte[] var3 = var2.generateClassFile();
if(saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
var1.write(var3);
var1.close();
return null;
} catch (IOException var2) {
throw new InternalError("I/O exception saving generated file: " + var2);
}
}
});
}
return var3;
}

主要是if条件中参数决定是否生成代理对象的.class文件:

private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

从JDK源代码中可以分析出只要,设置参数sun.misc.ProxyGenerator.saveGeneratedFiles即可。从这里可以看出只要设置即可:

 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

Boolean类中的getBoolean如下:

public static boolean getBoolean(String name) {
boolean result = false;
try {
result = toBoolean(System.getProperty(name));
} catch (IllegalArgumentException e) {
} catch (NullPointerException e) {
}
return result;
}

从以上生成.class文件的generateProxyClass方法中可以看出,使用的是FileOutputStream文件输出流,文件的输出目录需要预先创建 好,否则将会抛出异常FileNotFoundException

在准备生成好.class文件后,下面详细分析生成的代理类。生成的代理类类名都是以$Proxy0开头,如:$Proxy0.class。

如下就是代理类反编译后的代码:

package com.sun.proxy;

import com.java.proxy.api.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements EchoService { private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2; public $Proxy0(InvocationHandler var1) throws {
super(var1);
} public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final void echo(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.java.proxy.api.EchoService").getMethod("echo", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

往往探知究竟还是需要阅读源码。这里我们使用这种看起来土但是却是无比简单易用的方式来分析JDK生成的代理类的细节。

这里最让人关注无非就是动态代理类的调用时怎样委派到最终实际的被代理对象上的这个过程的。在阅读source前,先直观的感受下调用过程图:

从图中看出,可以将整个调用过程分为几个部分:

  • 调用者对象
  • 代理对象
  • 实现invocationHandler对象
  • 被代理对象

调用者invoke代理对象,代理对象进行组织和委派给InvocationHandler的统一接口invoke方法,invocationHandler中invoke通过反射的方式调用被代理对象的方法。

下面再来分析下动态生成的代理类的基本特点:

  • 代理类集成Proxy类且实现创建代理对象时的列表中的接口
  • 代理类是final,不能再被继承
  • 代理类中持有InvcationHandler的实例
  • 代理类对Object中的toString、hashCode、equals方法进行了代理,对构造时的接口中的所有方法进行了代理
  • 代理使用静态语句块,利用反射获取了这些方法的描述对象Method。在创建代理类实例时就已经将这些方法解析,toString、hashCode、equals一般是前三个方法m0、m1、m2
  • 代理类中生成的代理方法都是final
  • 代理类不会对被代理对象中的final,private,protected方法进行代理
  • 代理类名以$Proxy开头

上面提到InvocationHandler的统一接口invoke,这个是至关重要的点。代理对象到别代理对象的过渡就是通过这个方法委派,下面看下这个接口的定义:

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

第一个参数是代理对象,第二个参数是调用者调用的被代理的代理方法,第三个参数是被代理方法的参数。

如果代理的所有接口中有重复方法,则取第一个方法。

参考

Dynamic Proxy Classes

dynamic-proxy

Are there alternatives to cglib?

动态代理(一)——JDK中的动态代理的更多相关文章

  1. 动态代理的两种方式,以及区别(静态代理、JDK与CGLIB动态代理、AOP+IoC)

    Spring学习总结(二)——静态代理.JDK与CGLIB动态代理.AOP+IoC   目录 一.为什么需要代理模式 二.静态代理 三.动态代理,使用JDK内置的Proxy实现 四.动态代理,使用cg ...

  2. 通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.

    目录 场景引入 动态代理引入 动态代理进阶 总结 个人认为动态代理在设计模式中算是比较难的, 本篇文章将从无到有, 从一个简单代码示例开始迭代, 逐步深入讲解动态代理思想. 场景引入 假设现在有一个坦 ...

  3. Spring学习总结(二)——静态代理、JDK与CGLIB动态代理、AOP+IoC

    一.为什么需要代理模式 假设需实现一个计算的类Math.完成加.减.乘.除功能,如下所示: package com.zhangguo.Spring041.aop01; public class Mat ...

  4. 总结两种动态代理jdk代理和cglib代理

    动态代理 上篇文章讲了什么是代理模式,为什么用代理模式,从静态代理过渡到动态代理. 这里再简单总结一下 什么是代理模式,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原 ...

  5. java设计模式中的动态代理

    Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...

  6. java动态代理(JDK和cglib)(转载)

    原文地址:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html 高亮部分是我的理解. JAVA的动态代理 代理模式 代理模式是常用的j ...

  7. 代理模式-jdk动态代理

    IDB package com.bjpowernode.proxy; /** * 代理类和目标类都必须使用同一个接口. */ public interface IDB { int insert(); ...

  8. 静态代理和动态代理(jdk/cglib)详解

    1.静态代理模式 代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色.其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口:RealSu ...

  9. Java 代理模式 (二) 动态代理

    代理模式 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式:即通过代理访问目标对象. 这样好处: 可以在目标对象实现的基础上,增强额外的功能操作.(扩展目标对象的功能). 代理模式的 ...

随机推荐

  1. 【转】解决Oracle 11g在用EXP导出时,空表不能导出

    一.问题原因: 11G中有个新特性,当表无数据时,不分配segment,以节省空间 .insert一行,再rollback就产生segment了. 该方法是在在空表中插入数据,再删除,则产生segme ...

  2. TCP 通信时序及状态变迁

    TCP 通信时序及状态变迁 参考链接: https://www.cnblogs.com/boxker/p/11214886.html https://blog.csdn.net/miss_ruoche ...

  3. django cookie,session,auth

    一.最完美的auth auth_user 是用来存储的用户注册的username,password auth 首先需要引入模块 from django.contrib import auth 用户认证 ...

  4. CentOS7.5下实现MySQL5.7主从同步

    这里使用两台Linux主机(一台充当MySQL主服务器,另一台充当MySQL从服务器),MySQL用yum安装,版本均为5.7,下表是它们所使用的操作系统以及IP地址. 两台Linux主机所使用的操作 ...

  5. 201871010106-丁宣元 《2019面向对象程序设计(java)课程学习进度条》

    <2019面向对象程序设计(java)课程学习进度条> 周次 (阅读/编写)代码行数 发布博客量/评论他人博客数量 课余学习时间(小时) 学习收获最大的程序阅读或编程任务 1 25/10 ...

  6. JMeter5.1开发JDBC协议接口脚本

    配置 qzcsbj是连接池名称 jdbc:mysql://localhost:3306/qzcsbj?useUnicode=true&characterEncoding=utf8&al ...

  7. VIJOS-P1066 弱弱的战壕

    JDOJ 1247: VIJOS-P1066 弱弱的战壕 题目传送门 Description 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了--b. mx在他的基地附近建立了 ...

  8. 2014-2015 ACM-ICPC, Asia Tokyo Regional Contest

    2014-2015 ACM-ICPC, Asia Tokyo Regional Contest A B C D E F G H I J K O O O O   O O         A - Bit ...

  9. DFS_BFS(深度优先搜索 和 广度优先搜索)

    package com.rao.graph; import java.util.LinkedList; /** * @author Srao * @className BFS_DFS * @date ...

  10. [RN] React Native 定义全局变量

    React Native 定义全局变量 React Native全局变量的两种使用方式 一.导出和导入 // 定义的页面 global.js var global = {authorization: ...