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. 使用GDI+ DrawDriverString实现行距及字符间距控制

    主要是要将一个标题和几段文字绘制到固定大小的图片上,如果一张放不下就生成多张.在使用DrawString是发现无法控制行距 namespace TibetTest {     public class ...

  2. APP-PAY-06153 When Trying To Open Organization Definition Form (文档 ID 1323165.1)

    In this Document Symptoms Cause Solution Applies to: Oracle Inventory Management - Version 11.5.10.2 ...

  3. 校园网之MentoHUST安装与使用

    作用:MentoHUST可以解决校园网锐捷客户端与Windows的兼容性问题,可以解决安装虚拟机之后虚拟机网卡与本地网卡冲突的问题,可以做到愉快的用校园网,并可以愉快的用校园网开Wifi给自己或者小伙 ...

  4. MySQL配置文件-my.ini

    下面允许我介绍一下MySQL的my.ini配置文件: my.ini是什么? my.ini是MySQL数据库中使用的配置文件,修改这个文件可以达到更新配置的目的. my.ini存放在哪里? my.ini ...

  5. CodeForces 400

    A - Inna and Choose Options Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d &a ...

  6. c程序设计语言_习题1-13_统计输入中单词的长度,并且根据不同长度出现的次数绘制相应的直方图

    Write a program to print a histogram of the lengths of words in its input. It is easy to draw the hi ...

  7. bzoj 3033 太鼓达人

    思路:首先一定是2^m次方的总数.用二进制从 000 一直到 111总过m个数,然后暴搜. #include<cstdio> #include<cstring> #includ ...

  8. MySQL开发中常用的查询语句总结

    1.查询数值型数据: SELECT * FROM tb_name WHERE sum > 100; 查询谓词:>,=,<,<>,!=,!>,!<,=>, ...

  9. gridview合并单元格

    记录用,以前写过,忘记了转自:http://marss.co.ua/MergingCellsInGridView.aspx public class GridDecorator { public st ...

  10. 问题-delphi XE2 Stack Overflow- save your work and restart CodeGear

    问题现象:某一天,启动DLEPHI XE2 后,新建一个工程,双击一个事件,“Stack Overflow- save your work and restart CodeGear delphi xe ...