在深入解析MapReduce架构设计与实现原理一书中读到动态代理这个东东。

代理是一种常用的设计模式,其目的是为其他对象提供一种代理一控制对这个对象的访问。代理类负责为委托类进行预处理(如安全检查、权限检查等)或者执行完后的后续处理(如清理对象或转发给其他代理等)。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

先看一个静态代理的例子

package xhj;interface CalculatePtl {int multiply(int a, int b);float divide(int a, int b);
}class CalculateImpl implements CalculatePtl {@Overridepublic int multiply(int a, int b) {// TODO Auto-generated method stubreturn a * b;}@Overridepublic float divide(int a, int b) {// TODO Auto-generated method stubreturn  (float)a / b;}}class CalculateProxy implements CalculatePtl {private CalculateImpl cImpl;public CalculateProxy (CalculateImpl cImpl) {this.cImpl = cImpl;}@Overridepublic int multiply(int a, int b) {// TODO Auto-generated method stub//预处理int r = cImpl.multiply(a, b);//后处理return r;}@Overridepublic float divide(int a, int b) {// TODO Auto-generated method stub//预处理float r = cImpl.divide(a, b);//后处理return r;}}public class StaticProxyExample {/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubCalculateImpl cImpl = new CalculateImpl();CalculateProxy proxy = new CalculateProxy(cImpl);int result = proxy.multiply(5, 3);System.out.println("multiply 5 3 = " + result);float dresult = proxy.divide(5, 3);System.out.println("divide 5 3 = " + dresult);}}

静态代理的缺点:1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

学习java动态代理之前,先了解一下几个类或接口:
1)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 

2)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
Object invoke(Object proxy, Method method, Object[] args) 

3)java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象

动态代理创建对象的过程可分为以下4步:
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler }); 

Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class },  handler ); 

下面看一个具体的动态代理的例子

package xhj;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;interface CalculateProtocol {int add (int a, int b);int subtract(int a, int b);
}class CalculateServer implements CalculateProtocol {@Overridepublic int add(int a, int b) {// TODO Auto-generated method stubreturn a + b;}@Overridepublic int subtract(int a, int b) {// TODO Auto-generated method stubreturn a - b;}}class CalculateHandler implements InvocationHandler {private Object objOriginal;public CalculateHandler(Object object) {this.objOriginal = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stub//预处理Object result = method.invoke(this.objOriginal, args);//后处理return result;}}public class DynamicProxyExample {public static void main(String[] args) {CalculateProtocol server = new CalculateServer();InvocationHandler handler = new CalculateHandler(server);CalculateProtocol client = (CalculateProtocol) Proxy.newProxyInstance(server.getClass().getClassLoader(), server.getClass().getInterfaces(), handler);//创建一个clientint r = client.add(5, 3);System.out.println("add 5 3 = " + r);r = client.subtract(10, 2);System.out.println("subtract 10 2 = " + r);}
}

接下来让我们来了解一下 Java 动态代理机制的一些特点。

首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:

由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

接着来了解一下被代理的一组接口有哪些特点。首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

java动态代理的美中不足:
诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface
代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java
的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

参考资料:
深入解析MapReduce架构设计与实现原理——董西成
java动态代理(JDK和cglib)
http://layznet.iteye.com/blog/1182924——java静态代理和动态代理
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/——Java 动态代理机制分析及扩展,第 1 部分

转载于:https://www.cnblogs.com/xhj-records/archive/2013/05/26/3099857.html

Java动态代理实现(转载\整理)相关推荐

  1. 由浅入深完全理解Java动态代理

    前言 看Retrofit源码的时候涉及到了Java动态代理,这个知识点之前在学习Java反射的时候就碰到过,不过也仅仅是停留在理论的学习.终于在Retrofit源码的时候看到了实际的使用,也是格外兴奋 ...

  2. java动态代理实现与原理详细分析(【转载】By--- Gonjan )

    [转载]By---    Gonjan  关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式 ...

  3. [转载]彻底理解JAVA动态代理

    http://www.cnblogs.com/flyoung2008/p/3251148.html 代理设计模式 定义:为其他对象提供一种代理以控制对这个对象的访问. 代理模式的结构如下图所示. 动态 ...

  4. Java 动态代理机制分析及扩展--转

    http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments http://www.ibm.com/developerworks/c ...

  5. Java 动态代理机制分析及扩展

    简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟推演了动态代理类的可能实现,向读者阐述了一个完整的 Java 动态代理运作过程,希望能帮助读者加深对 Java 动 ...

  6. java动态代理【一】

    java动态代理的定义:为其他目标类的方法增加切面的逻辑,即在执行目标类方法的时候,先去执行一段如校验检测的逻辑代码.java通俗一点就是生成一个继承目标类的子类,并在每个调用方法都添加一段逻辑. 应 ...

  7. Java动态代理与Cglib代理

    为什么80%的码农都做不了架构师?>>>    最近又继续回来死磕Spring源码,以前看的也忘得差不多了,这次先把Spring使用的动态代理cglib看了一下,打好基础知识. cg ...

  8. JAVA 动态代理学习记录

    打算用JAVA实现一个简单的RPC框架,看完RPC参考代码之后,感觉RPC的实现主要用到了两个方面的JAVA知识:网络通信和动态代理.因此,先补补动态代理的知识.---多看看代码中写的注释 参考:Ja ...

  9. 初看Mybatis 源码 (二) Java动态代理类

    先抛出一个问题,用过Mybatis的都知道,我们只需要定义一个Dao的接口,在里面写上一些CRUD相关操作,然后配置一下sql映射文件,就可以达到调用接口中的方法,然后执行sql语句的效果,为什么呢? ...

最新文章

  1. linux vsftp的配置
  2. Hadoop 同步集群时间ntp
  3. android 4.0.4 usb调试,安卓手机4.0怎么打开usb调试?图文详解附视频
  4. 没有安装hiredis
  5. 正在打dota的过程中,接到淘宝网面试电话
  6. 面向对象(Python):学习笔记之异常
  7. 网络模拟器NS-2及其应用分析
  8. 009-Shell 函数
  9. 我花了一夜用数据结构给女朋友写个H5走迷宫游戏
  10. 手机wps怎么设置打印横竖_wps怎么设置横向打印
  11. iOS15适配本地通知功能
  12. 秋招详细攻略——从准备到面试
  13. 使用 wireshark 抓本地包
  14. 小程序瀑布流_小程序界面设计 | 太古可口可乐会员平台
  15. 牛客竞赛数学专题班生成函数I 题解
  16. VS Code运行SpringBoot项目
  17. 基于图像的人数统计方法
  18. 【Oracle SQL数据库-教学1】--- 开篇
  19. 招聘系统自研工时评估及方案说明书
  20. 明美新能在创业板IPO过会:计划募资4.5亿元,2022年营收约39亿元

热门文章

  1. 延迟初始化Spring Bean的几种方式
  2. python 笔记 之 线程锁
  3. bzoj2588: Spoj 10628. Count on a tree 主席树
  4. 高性能爬虫原理与应用
  5. [WM C++]从资源文件中加载显示png/jpg图片
  6. Vmware虚拟机使用Nat方式连接笔记本无线网卡
  7. 《Cisco防火墙》一8.7 通过NAT规则定义连接限制
  8. tornado总结4-html模板使用2
  9. VMware虚拟机桥接方式与真实主机共享上网
  10. 屏蔽×××S 2008报表导出格式