Java的代理有两种:静态代理和动态代理,动态代理又分为 基于jdk的动态代理 和 基于cglib的动态代理 ,两者都是通过动态生成代理类的方法实现的,但是基于jdk的动态代理需要委托类实现接口,基于cglib的动态代理不要求委托类实现接口。

接下来主要分析一下基于jdk的动态代理的实现原理。

一 动态代理例子

首先来看一个动态代理的例子:

# 测试类,主要功能是生成代理类并调用代理方法 TargetFactory.java
public class TargetFactory {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
TargetFactory tf = new TargetFactory();
Target tt = new Target();
Display dy = (Display) tf.getInstance(tt, new InvokerHandler(tt));
try {
dy.f();
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getInstance(Object target, InvocationHandler handler){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler);
}
}
# 接口 Display.java
interface Display {
public void f();
public void g();
}
# 实现了接口的目标类 Target.java
public class Target implements Display{
@Override
public void f() {
System.out.println("Targer f() method");
}
@Override
public void g() {
System.out.println("Targer g() method");;
}
}
# 实现了InvocationHandler接口的代理类的调用处理类 InvokerHandler.java
public class InvokerHandler implements InvocationHandler {
private Object target;
public InvokerHandler(Object t){
target = t;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("beforem invoke method");
method.invoke(target, args);
System.out.println("after invoke method");
return null;
}
}  

运行上面的例子,结果为:

beforem invoke method
Targer f() method
after invoke method

二 代理类分析

我们从生成的代理类入手来进行分析,代理类默认是只存在于内存中的,我们可以通过添加如下代码来将代理类存储在磁盘上:

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

FAQ1:添加此代码后程序有时会抛出 java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException : test\java\dynamicProxy\$Proxy0.class (系统找不到指定的路径。)。这个问题不得不说一下代理类的生成路径。系统是根据接口的描述符来选择生成路径的,如果有一个接口的描述符都为public的,那么代理类就被放置在用户目录下面,可以通过System.getProperty("user.dir")来获取到。接口中只要有一个是非public的,那么代理类的放置路径就为System.getProperty("user.dir")+File.separator+该接口的包路径。谈到这里,我们可以想象一下如果有两个接口是非public的,而它们属于不同的包,那么将会抛出IllegalArgumentException的异常。

代理类的命名是 “$Proxy”(由Proxy类中的proxyClassNamePrefix字段指定的)+代理类的序号(Proxy类中的nextUniqueNumber字段,从0开始),考虑到多线程的问题在操作nextUniqueNumber时先要获取到nextUniqueNumberLock的对象锁。

获得了代理类的class文件后我们使用jd-gui(free for no commercial)来进行反编译获取到源码,本文的$Proxy0.class 反编译的结果如下

//代理类都继承 Proxy 类 并且实现代理接口Display
public final class $Proxy0 extends Proxy implements Display
{
//构造函数的入参为 例子中InvokerHandler的实例
//也就是 Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler); 的入参 handler
public $Proxy0(InvocationHandler paramInvocationHandler) throws
{
/*接着调用Proxy的构造函数,把handler赋值给Proxy类的h字段,下面注释为Proxy的构造函数
*protected Proxy(InvocationHandler h) {
* this.h = h;
*}
*/ super(paramInvocationHandler); } private static Method m1;
private static Method m3;
private static Method m4;
private static Method m0;
private static Method m2; static
{
try
{
//通过反射获取接口中的方法f() 和 g(),这就决定了委托类必须实现接口,不然的话没有办法通过反射来调用委托类中的方法
m3 = Class.forName("test.java.dynamicProxy.Display").getMethod("g", new Class[0]);
m4 = Class.forName("test.java.dynamicProxy.Display").getMethod("f", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
//在调用代理类中的接口方法时,代理类会将此方法和方法的参数作为入参来调用paramInvocationHandler的invoke函数,在invoke函数中调用委托类中对应的函数
public final void g() throws
{
try
{
//m3代表的是g方法,null是g的入参,因为g没有入参所以为null
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw newpublic final void f() throws
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} public final boolean equals(Object paramObject) throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} public final int hashCode() throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} public final String toString() throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
}

基于jdk动态代理所生成的代理类和静态代理类一样都要实现接口,动态代理类需要继承Proxy(不知道为何要继承此类)。动态代理invoke函数中的对于委托类的方法调用是反射调用,效率上比这静态代理要差一些。

在编码时,静态代理类需要用户实现每一个接口方法,而动态代理只需要实现 InvocationHandler 中的invoke函数,因此动态代理使得代码比较简洁,所有对method的预处理都在invoke函数中完成。

三 代理类的产生

上面使用反编译的手段来分析了代理类的源码,下面要介绍一下代理类到底是怎么生成的

 1. Proxy.newProxyInstance方法

newProxyInstance 是 Proxy类中的静态方法,它的作用就是根据入参来返回一个代理类的实例,下面来介绍一下入参:

Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler);

target.getClass().getClassLoader():是用来加载代理类的class loader

new Class<?>[]{Display.class}:是需要代理的接口,一个代理类可以代理多个接口,所以这里是个数组

handler: 传递委托类方法调用的调用处理类,在例子一种对应的是InvokerHandler的实例
我们来看一下 newProxyInstance的源码:

 public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
//生成代理类
Class cl = getProxyClass(loader, interfaces); try {
//获取构造函数,生成并返回代理类的实例
//根据第二节中对生成代理类的分析,构造函数的参数类型为 { InvocationHandler.class }
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}

2. getProxyClass

这个函数主要实现了下列的功能:

  • 对需要代理的接口进行合法性验证(接口对传入newProxyInstance的Class loader是否可见,是否是接口类型,接口去重)

在此只关注一下接口的去重。此函数中采用HashSet的方法来简单进行去重,代码如下:

Set interfaceSet = new HashSet();

if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
  • 使用本地缓存loaderToCache来缓存已经产生的代理类

先来看一下本地缓存的初始化,它采用WeakHashMap这个特殊的Map类型,关于WeakHashMap在此不再赘述

private static Map loaderToCache = new WeakHashMap();

loaderToCache 的类型是<ClassLoader,<Object,Class>>,涉及到缓存操作的代码如下,代码比较简单,又有完整的注释,这里不予过多分析,以免画蛇添足之嫌。

Map cache;
synchronized (loaderToCache) {
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap();
loaderToCache.put(loader, cache);
}
/*
* This mapping will remain valid for the duration of this
* method, without further synchronization, because the mapping
* will only be removed if the class loader becomes unreachable.
*/
} /*
* Look up the list of interfaces in the proxy class cache using
* the key. This lookup will result in one of three possible
* kinds of values:
* null, if there is currently no proxy class for the list of
* interfaces in the class loader,
* the pendingGenerationMarker object, if a proxy class for the
* list of interfaces is currently being generated,
* or a weak reference to a Class object, if a proxy class for
* the list of interfaces has already been generated.
*/
synchronized (cache) {
/*
* Note that we need not worry about reaping the cache for
* entries with cleared weak references because if a proxy class
* has been garbage collected, its class loader will have been
* garbage collected as well, so the entire cache will be reaped
* from the loaderToCache map.
*/
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
/*
* The class generation that we are waiting for should
* take a small, bounded time, so we can safely ignore
* thread interrupts here.
*/
}
continue;
} else {
/*
* No proxy class for this list of interfaces has been
* generated or is being generated, so we will go and
* generate it now. Mark it as pending generation.
*/
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
  • 产生缓存中没有的代理类,并将该类存入缓存

如果缓存中没有所需的代理类,则由下面的这个函数来根据需要代理的接口产生。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

3. ProxyGenerator

这个类是jdk动态代理的核心类,class文件的生成就是在这个类中完成的,在分析这个类之前,首先来看一下class file的 格式,这里只简要的提一下,可以在jvm规范中找到详细的解释。

ClassFile {
u4 magic; //此处必须为0xCAFEBABE
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count; //代理类中的field info没有属性
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];//代理类中的method info只有Code属性和Exceptions属性
u2 attributes_count; // 代理类没有属性,所以attributes_count=0
attribute_info attributes[attributes_count];
}

java class file中最复杂的就是各种各样的attribute,而在代理类中只存在两种属性"Code" 和 "Exceptions",由此可以看出,该类也是比较简单的。

constant_pool[constant_pool_count-1] 是class file中不可缺少的元素,这里需要提一下,在class file中引用constant_pool中的元素时下标是从1 开始的,比如constant_pool_count是39 那么只能使用constant_pool[1] --> constant_pool[38]的

元素。

在ProxyGenerator 中使用静态内部类ConstantPool来管理constant pool,在ConstantPool中使用private List<Entry> pool = new ArrayList<Entry>(32) 来存储constant pool entries,用private Map<Object,Short> map = new HashMap<Object,Short>(16) 来存储entries 与下标之间的对应关系,这样的设计避免了需要轮询pool来查找需要存入的条目是否已经存在了。

4. ProxyGenerator.generateClassFile

在ProxyGenerator中generateClassFile 是入口函数,该函数可以对照class file的结构来阅读。

    private byte[] generateClassFile() {

        /* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/ /*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/ /* hashCodeMethod equalsMethod toStringMethod 存在于每个生成的代理类中
* addProxyMethod 中会扫描每个接口中的方法,对于函数签名一致的方法则判断
* 抛出的异常类型是否一致,如果不一致且没有继承关系,则不抛出异常。如果不
* 一致但异常有继承关系的,则抛出子类的异常
* 例如 接口1中 void f() throws Exception
* 接口2中 void f() throws IOException
* 代理类中 void f() throws IOException
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class); /*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
} /*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
/*这里需要强调一下,签名一致的函数返回值类型不一样且没有继承关系,则是不兼容的
* 如果返回值类型不一致而返回值存在继承关系的,在代理类中返回值类型为子类
* 例如: 接口1 superclass f();
* 接口2 subclass f();
* 代理类中 subclass f(); 其中 subclass extends superclass
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
} /* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
methods.add(generateConstructor());//生成构造函数的字节码 for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) { // add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC)); // generate code for proxy method and add it
//生成equals,toString,hashCode 和 接口方法的字节码
methods.add(pm.generateMethod());
}
}
//生成静态代码块的字节码
methods.add(generateStaticInitializer()); } catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
} if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} /* ============================================================
* Step 3: Write the final class file.
*/ /*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
} /*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout); try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION); cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes) } catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
} return bout.toByteArray();
}

JDK 动态代理分析的更多相关文章

  1. JDK动态代理、CGLib动态代理

    JDK动态代理源码 一.public static Object newProxyInstance ——> 调用下面这个方法二.Class<?> cl = getProxyClass ...

  2. MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析

    我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...

  3. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  4. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  5. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  6. 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)

    代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...

  7. 深挖JDK动态代理(二):JDK动态生成后的字节码分析

    接上一篇文章深挖JDK动态代理(一)我们来分析一下JDK生成动态的代理类究竟是个什么东西 1. 将生成的代理类编程一个class文件,通过以下方法 public static void transCl ...

  8. Java,JDK动态代理的原理分析

    1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...

  9. JDK动态代理[2]----JDK动态代理的底层实现之Proxy源码分析

    在上一篇里为大家简单介绍了什么是代理模式?为什么要使用代理模式?并用例子演示了一下静态代理和动态代理的实现,分析了静态代理和动态代理各自的优缺点.在这一篇中笔者打算深入源码为大家剖析JDK动态代理实现 ...

随机推荐

  1. OpenCV学习笔记:如何扫描图像、利用查找表和计时

    目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...

  2. Understanding Network Class Loaders

    By Qusay H. Mahmoud, October 2004     When Java was first released to the public in 1995 it came wit ...

  3. 理解Java ClassLoader机制

    当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader                |       extension cla ...

  4. JavaScript里的类和继承

    JavaScript与大部分客户端语言有几点明显的不同: JS是 动态解释性语言,没有编译过程,它在程序运行过程中被逐行解释执行JS是 弱类型语言,它的变量没有严格类型限制JS是面向对象语言,但 没有 ...

  5. MVVM Light中的Message

    比喻:像漂流瓶一样发送一个Message,任何人有兴趣就可以拾起来. MVVM Light中的Message的使用分为三个步骤: 1.创建一个类,包含要传递的Message. 2.在ViewModel ...

  6. bzoj2763: [JLOI2011]飞行路线 分层图+dij+heap

    分析:d[i][j]代表从起点到点j,用了i次免费机会,那就可以最短路求解 #include <stdio.h> #include <iostream> #include &l ...

  7. C# VS2010中,用微软自带的System.Data.OracleClient来连接Oracle数据库

    由于微软在.Net框架4.0中已经决定撤销使用System.Data.OracleClient,造成在VS2010中无法连接Oracle数据库,但它还依旧存在于.Net架构中,我们可以通过自己引用 C ...

  8. Content-Type伪装 - 将jsp伪装成css

    一.前期理论准备 1)目的:  在jsp中动态生成css语句,然后输出给浏览器解析.渲染. 2)浏览器解析文件的依据:  页面加载后,浏览器会发起各个请求去下载各种资源.  比如下载css文件,然后根 ...

  9. bzoj 3172 [Tjoi2013]单词(fail树,DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3172 [题意] 题目的意思是这样的,给若干个单词,求每个单词在这一堆单词中的出现次数. ...

  10. HDU 4135 Co-prime

    思路:直接用求(b,1)范围内互质的数,(a-1,1)范围内互质的数.再求反 就是敲一下容斥模板 #include<cstdio> #include<cstring> #inc ...