要理解动态代理,不妨先来看看一个静态代理的例子。

  一.静态代理

  以一个电商项目的例子来说明问题,比如我定义了一个订单的接口IOrder,其中有一个方法时delivery,代码如下。

  

package com.xdx.learn;

public interface IOrder {
void delivery();//发货
void confirmReceipt();//确认收货
}

  Order类实现了该接口,如下所示。

  

package com.xdx.learn;

public class Order implements IOrder {
public void delivery() {
System.out.println("delivering the commodity");
} public void confirmReceipt() {
System.out.println("confirmReceipt the commodity"); } }

  假如写完这个项目后,老板想要在order类的每个方法中加入操作日志的功能,或者性能统计。可是我又不想再更改原来的order类,特别是在接手别人的项目的时候,我们特别不喜欢去修改既有的代码,一方面修改容易导致未知的问题,另外一方面,修改有时候不如自己重写快。

  此时我们可以为Order类写一个代理类,对原来的Order类进行一层简单的包装,以达到目的。

package com.xdx.learn;

public class OrderProxy implements IOrder {
private Order order;//注入原来的Order类 public Order getOrder() {
return order;
} public void setOrder(Order order) {
this.order = order;
} public void delivery() {
System.out.println("do some log");//添加一些关于日志的操作
order.delivery();//目标类(被代理类)的业务逻辑 } public void confirmReceipt() {
System.out.println("do some log");//添加一些关于日志的操作
order.confirmReceipt();//目标类(被代理类)的业务逻辑
} }

  调用代理类的方法如下所示。

    public static void main(String args[]){
Order order=new Order();//目标类对象
OrderProxy proxy=new OrderProxy();//代理类对象
proxy.setOrder(order);//注入
proxy.delivery();//带有日志功能的发货操作
proxy.confirmReceipt();//带有日志功能的确认收货操作
}

  从上面的例子可以看出,静态代理主要的好处是不需要修改原来的目标类,实现解耦。

  但是假如目标类里面有很多方法呢?或者假如我要为更多的目标类添加日志功能呢?那我必须为每个类都定义一个静态代理类,并且为每个类的每个方法写一个对应的加入了日志管理功能的方法。显然这是一项巨大的工程。这时候静态代理已经不能满足我们的需求了,我们需要的是动态代理。

  二.动态代理

  首先我们需要对动态代理和静态代理的概念做一下解释,所谓的静态代理就是代理类在运行之前就写好了,比如我们上面写好的OrderProxy这个类。与之对应的,动态代理的 代理类对象是当程序运行的时候才产生的,也就是说我们事先并没有定义一个代理类,而是需要用到的时候它才产生。动态代理又分为两种,一种是基于jdk的,一种是基于Cglib的。他们的实现原理有所不同。

  1.基于JDK的动态代理。

  我简单的描述一下整个过程涉及到的各方类。

  IOrder:接口

  Order:目标类,我们称为target。也就是被代理的类。

  然后我们需要一个实现了InvocationHandler接口的类OrderHander,在这个类中,我们生成一个代理对象并与该hander对象进行绑定,并且利用反射机制(主要是Method的invoke方法)来调用目标类的方法。且在方法中织入增强逻辑(例如日志管理功能)。这样说很抽象,我们来看实际的Hander类吧。

  

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class OrderHander implements InvocationHandler {
private Object target;// 目标类对象,即被代理的对象 public OrderHander(Object target) {
this.target = target;
} public Object Bind() {
// 绑定操作,生成一个target的代理类对象,该代理类与目标类实现相同的接口,所以需要传入接口的参数。
    ,并且将该代理对象与this,也就是该OrderHander对象绑定
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
} /**
* 该方法是InvocationHandler的唯一的方法,当与该OrderHander类对象绑定的代理类的方法被调用的时候,
* 就会执行该方法,并且传入代理对象,方法对象,以及方法的参数。这样我们才可以用反射机制来调用目标类的方法。
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("do some log");
method.invoke(target, args);// 注意这边的第一个参数target,即目标类,而非代理类。因为执行的是目标类的业务逻辑。
     System.out.println(method);
return null;
}
}
OrderHander 实现了InvocationHandler 接口,查看jdk源码,有一段是这样的。
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>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是一个接口,它被一个proxy instance(代理类对象)的invocation handler(调用 处理器)所实现。这句话虽简单,但是表述得很明确,invocation handler(调用 处理器)是属于某个代理类对象。这也是我们为什么在OrderHander 中将目标类的代理类与自身绑定的原因。

  下一句话:每个代理对象都有一个相关联的,当一个方法被代理对象调用的时候,这个被调用的方法会被编码并且分发到与这个代理类对象关联的invocation handler(调用 处理器)的invoke方法处执行。

  也就是说,代理类是依附于调用 处理器存在的,在调用它的方法的时候,并不会实际执行,而是转发到它所对应的调用处理器中的invoke方法执行。

  这也解释了,我们在OrderHander类中的invoke方法中不仅能织入增强(日志管理),而且通过method.invoke()方法调用了目标类的实际方法。

  我们来为上述调用处理器写一个main方法,证实我们的理论。

  

public static void main(String args[]){
Order order=new Order();//目标对象
System.out.println(order);
OrderHander orderHander=new OrderHander(order);//调用处理器对象
IOrder orderProxy=(IOrder) orderHander.Bind();//生成代理对象,注意这里是IOrder对象,可以理解为多态吧,并且与orderHander绑定了
orderProxy.delivery();
orderProxy.confirmReceipt();
}

  上述代码的执行结果如下:

  com.xdx.learn.Order@15db9742
  public abstract void com.xdx.learn.IOrder.delivery()
  do some log
  delivering the commodity
  public abstract void com.xdx.learn.IOrder.confirmReceipt()
  do some log
  confirmReceipt the commodity

  其中public abstract void com.xdx.learn.IOrder.delivery()和public abstract void com.xdx.learn.IOrder.confirmReceipt()是我在invoke方法中system.out.println(method)。可看到这个menthod对象是接口处method。这也解释得通为什么我们再获取一个代理对象的时候采用IOrder,即原始的接口类来定义。

  我们可以近似简单的理解:代理类跟目标类是同一个接口的不同实现类,所以可以用多态的形式来调用代理类的方法的方法,而转发给其handle对象来执行,在hander对象的invoke方法中,我们除了织入增强,还调用了method.invoke()方法,因为此处的method对象是接口的方法对象,所以同样可以传入目标类target这个对象作为参数。

  由此可见,基于jdk的动态代理依赖于接口的实现,这就要求我们必须为每个目标类都定义一个接口,才能采用这种代理方式。

  2.基于Cglib的动态代理方式

  基于Cglib的动态代理方式不是采用实现接口的方式去构造一个代理类,而是直接为代理类实现一个增强过后的子类,子类的生成运用了字节码技术,整个过程主要使用了Enhancer、MethodInterceptor、MethodProxy等类。

  首先需要引入Cglib的相关jar包,maven配置如下。

  

<!-- 动态代理cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>

  下面还是以一个例子来阐述Cglib动态代理的原理。

  目标类:Order类的代码如下:

  

package com.xdx.learn;

public class Order {
public void delivery(){
System.out.println("delivering the commodity");
}
public void confirmRecipet(){
System.out.println("confirmRecipeting the commodity");
} }

  新建一个CglibInterceptor,实现了MethodInterceptor接口,其代码如下所示。

  

package com.xdx.learn;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; public class CglibInterceptor implements MethodInterceptor {
// 增强器,它的作用是对目标类对象增强,生成一个代理类,并且指定一个回调对象,这个回调对象一般是一个MethodInterceptor对象。
// 通过MethodInterceptor对象的拦截功能,即intercept方法,我们可以做一些增强工作。
private Enhancer enhancer = new Enhancer(); // 以下方法生成一个代理对象,它以目标对象的子类的形式存在
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);// 设置当前MethodInterceptor对象为enhancer对象的回调对象,这个步骤至关重要
return enhancer.create();// 调用create()方法生成一个子类(代理类,增强类)。
} /**
* 所有代理类调用的方法都会被这个方法所拦截,前提是该代理类设置的回调对象是该CglibInterceptor对象本身
*
* @param obj
* 代理类,也就是被增强的类
* @param method
* 被拦截的方法
* @param args
* 方法参数
* @param proxy
* 用于调用目标类(也就是父类)的方法
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("do some log");
System.out.println(obj.getClass().getName());
System.out.println(method);// System.out.println("使用MethodProxy对象调用父类的方法");
proxy.invokeSuper(obj, args);
return null;
} }

  Enhancer类可以对目标类进行增强,通过字节码技术生成目标类的一个子类对象,也就是代理类对象。并且指定了Callback对象,CallBack对象一般为MethodInterceptor类对象,在上述代码中,我们设为this。也就是当前CglibInterceptor对象。我们来看源码中关于

Enhancer类的两段描述。

/**
* Generates dynamic subclasses to enable method interception. This
* class started as a substitute for the standard Dynamic Proxy support
* included with JDK 1.3, but one that allowed the proxies to extend a
* concrete base class, in addition to implementing interfaces. The dynamically
* generated subclasses override the non-final methods of the superclass and
* have hooks which callback to user-defined interceptor
* implementations.
* <p>
* The original and most general callback type is the {@link MethodInterceptor}, which
* in AOP terms enables "around advice"--that is, you can invoke custom code both before
* and after the invocation of the "super" method. In addition you can modify the
* arguments before calling the super method, or not call it at all.
* <p>

  大概翻译:该类可以为目标类生成动态的子类,从而进行方法的拦截。它是基于JDK的动态代理的一种补充,弥补了JDK动态代理只能通过实现接口来创建代理类的不足。该动态生成的的子类重写了父类(目标类)的非finla方法(因为final方法是不能重写的),并且,它与用户自定义的拦截器(interceptor)之间通过callback方法产生了关联(hooks )。

  最常用的callback类型是MethodInterceptor类的实例对象,MethodInterceptor对象在AOP语境下可以实现around advice(环绕增强),所谓的环绕增强就是在父类(目标类)方法的调用前后都可以加入自己的代码。

  简而言之:Enhancer通过字节码技术生成目标类的子类(代理类),并且与一个MethodInterceptor对象产生了挂钩。

  那MethodInterceptor对象又是怎么实现方法的增强的呢?我们还是去看看MethodInterceptor类的源码。

  

/**
* General-purpose {@link Enhancer} callback which provides for "around advice".
* @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
* @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
*/
public interface MethodInterceptor
extends Callback
{
/**
* All generated proxied methods call this method instead of the original method.
* The original method may either be invoked by normal reflection using the Method object,
* or by using the MethodProxy (faster).
* @param obj "this", the enhanced object
* @param method intercepted Method
* @param args argument array; primitive types are wrapped
* @param proxy used to invoke super (non-intercepted method); may be called
* as many times as needed
* @throws Throwable any exception may be thrown; if so, super method will not be invoked
* @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
* @see MethodProxy
*/
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable; }

  大概翻译:

  类上的注释:该类主要目的是为Enhancer对象提供一个回调对象,用于方法的增强。这段话几乎是与Enhancer那边的说明遥相呼应的。

  方法上的注释:所有调用代理类的方法都会被替换成调用该intercept方法,其实就是一个拦截的操作。可以通过普通的反射机制(Method方法)来调用原始方法,或者通过MethodProxy对象来调用原始方法,后者速度更快。

  参数:obj:代理类对象,method:被拦截的方法,args:参数,proxy:一个MethodProxy对象,它是为了我们能调用目标类(父类)的方法。

  这些参数都是Enhancer对象传递过来的,这样Enhancer就与MethodInterceptor实现了关联。

  最后,我们为Cglib动态代理写一个调用方法,如下所示。

  

    public static void main(String args[]){
CglibInterceptor interceptor=new CglibInterceptor();
Order order=(Order) interceptor.getProxy(Order.class);//生成一个增强对象,并且已经指定了interceptor为callback对象
order.delivery();
order.confirmRecipet();
}

  上述代码的运行结果如下:

  do some log
  com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
  public void com.xdx.learn.Order.delivery()
  delivering the commodity
  do some log
  com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
  public void com.xdx.learn.Order.confirmRecipet()
  confirmRecipeting the commodity

  可以看到确实实现了增强,也看到了增强后的代理类是com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b。

  三.两种方法的比较

  1.基于JDK的代理依附于接口,所以必须为每个目标类创建接口,这点比较麻烦。

  2.基于CGLIB的动态代理采用子类的方式生成代理,并且它的性能会比基于JDK的代理来得高。但是在生成代理的时候会比较慢,所以如果需要代理的目标类是单例的情况下,推荐这种代理方式。

  3.其实代理类都不真正去调用执行方法,而是交给第三方对象去调用执行方法,并且在执行的过程中织入增强。基于JDK的代理是交给InvocationHandler对象的invoke方法,而基于CGLIB的代理则是交给MethodInterceptor的intercept方法。前提是,二者都要在生成代理类的时候与相应的第三方对象产生关联。

  4.二者都有使用局限,基于JDK的代理方式无法为非public方法,static方法实现增强(因为接口都是public方法,接口中也不能有static方法)。而CGLIb是通过覆盖父类方法来实现代理的,所以一切不能被重写的方法都无法被增强,即final,static,private方法都不能被增强。

java两种动态代理方式的理解的更多相关文章

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

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

  2. 死磕Spring之AOP篇 - 初识JDK、CGLIB两种动态代理

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  3. Java 几种动态代理实现及其性能比较

    原处出之于阿里liangf Interface: package com.sunchao.jdkdyproxy; public interface Subject { void request(); ...

  4. Spring的两种动态代理:Jdk和Cglib 的区别和实现

    这是有意义的一天!自己研究一路畅通的感觉真爽 原理是参考大神的,代码手敲 一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处 ...

  5. java 两种进程创建方式比较

    A extends Thread: 简单 不能再继承其他类了(Java单继承) 同份资源不共享 A implements Runnable:(推荐) 多个线程共享一个目标资源,适合多线程处理同一份资源 ...

  6. MyBatis开发Dao层的两种方式(Mapper动态代理方式)

    MyBatis开发原始Dao层请阅读我的上一篇博客:MyBatis开发Dao层的两种方式(原始Dao层开发) 接上一篇博客继续介绍MyBatis开发Dao层的第二种方式:Mapper动态代理方式 Ma ...

  7. mybatis中两种取值方式?谈谈Spring框架理解?

    1.mybatis中两种取值方式? 回答:Mybatis中取值方式有几种?各自区别是什么? Mybatis取值方式就是说在Mapper文件中获取service传过来的值的方法,总共有两种方式,通过 $ ...

  8. 十分钟理解Java中的动态代理

    十分钟理解 Java 中的动态代理   一.概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道 ...

  9. 牛客网Java刷题知识点之Map的两种取值方式keySet和entrySet、HashMap 、Hashtable、TreeMap、LinkedHashMap、ConcurrentHashMap 、WeakHashMap

    不多说,直接上干货! 这篇我是从整体出发去写的. 牛客网Java刷题知识点之Java 集合框架的构成.集合框架中的迭代器Iterator.集合框架中的集合接口Collection(List和Set). ...

随机推荐

  1. Python模块:paramiko

    paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,值得一说的是,fabric和ansible内部的远程管理就是使用的paramiko来现实. 1.下载安装 Wi ...

  2. [转载] java多线程学习-java.util.concurrent详解(二)Semaphore/FutureTask/Exchanger

    转载自http://janeky.iteye.com/blog/770393 ------------------------------------------------------------- ...

  3. fio——IO基准测试

    简介 fio是IO工具,既可以用于基准测试,也可以用于硬件的压力测试验证(stress/hardware verification). 支持13种不同的IO引擎(sync.mmap.libaio.po ...

  4. Zabbix 3.0 从入门到精通(zabbix使用详解)

    第1章 zabbix监控 1.1 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源   网站/服务器 的可用性 1.1.1 网站可用性 在软件系统的高可靠性(也 ...

  5. c# linq的差集,并集,交集,去重【转】

    using System.Linq;      List<string> ListA = new List<string>(); List<string> List ...

  6. Linux系列教程(二十四)——Linux的系统管理

    上篇博客介绍了Linux的服务管理,不管是以RPM包安装的服务,还是通过源码包安装的服务,万能启动服务的方法都可以通过 /绝对路径/启动脚本名 start .而通过 RPM 包安装的服务还可以通过 s ...

  7. Hive详解

    1.   Hive基本概念 1.1  Hive简介 1.1.1 什么是Hive Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能. 1.1 ...

  8. Thinkphp使用phpexcel导入文件并写入数据库

    现实中,我们往往很多地方都需要这样批量导入数据,除了phpexcel还有csv等方法可以解决 下面详细介绍一下使用方法 首先在官方下载安装包解压到本地,然后复制保存到tp框架下的vendor目录下 h ...

  9. deepin 环境变量配置加载顺序

    加载顺序,默认是没有 .bash_profile 文件 1. non-login 方式 ~$ su deepin run /ect/bash.bashrc run .bashrc 2.login 方式 ...

  10. Web Api 2.0中使用Swagger生成Api文档的2个小Tips

    当Web Api 2.0使用OAuth2授权时,如何在Swagger中添加Authorization请求头? Swagger说明文档支持手动调用Api, 但是当Api使用OAuth2授权时,由于没有地 ...