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

 

一、概述

1. 什么是代理

我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

2. 静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。 下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:


  1. public interface Sell { void sell(); void ad();
  2. }

Vendor类的定义如下:


  1. public class Vendor implements Sell { public void sell() {
  2. System.out.println("In sell method");
  3. } public void ad() {
  4. System,out.println("ad method")
  5. }
  6. }

代理类BusinessAgent的定义如下:


  1. public class Vendor implements Sell { public void sell() {
  2. System.out.println("In sell method");
  3. } public void ad() {
  4. System,out.println("ad method")
  5. }
  6. }

从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:


  1. public class BusinessAgent implements Sell {
  2. ...
  3. public void sell() {
  4. if (isCollegeStudent()) {
  5. vendor.sell();
  6. }
  7. }
  8. ...
  9. }

这对应着我们上面提到的使用代理的第二个优点:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类,下面我们重点来介绍下运行时生成代理类的动态代理方式。

二、动态代理

1. 什么是动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。 这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的。

现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。我们还是以上面例子中的Vendor类作为委托类,BusinessAgent类作为代理类来进行介绍。首先我们来使用静态代理来实现这一需求,相关代码如下:


  1. public class BusinessAgent implements Sell {
  2. private Vendor mVendor;
  3. public BusinessAgent(Vendor vendor) {
  4. this.mVendor = vendor;
  5. }
  6. public void sell() {
  7. System.out.println("before");
  8. mVendor.sell();
  9. System.out.println("after");
  10. }
  11. public void ad() {
  12. System.out.println("before");
  13. mVendor.ad();
  14. System.out.println("after");
  15. }
  16. }

从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

2. 使用动态代理

(1)InvocationHandler接口

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:


  1. public interface InvocationHandler {
  2. Object invoke(Object proxy, Method method, Object[] args);
  3. }

从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出“before”,然后调用委托类的invoke方法,再输出“after”。下面我们来一步一步具体实现它。

(2)委托类的定义

动态代理方式下,要求委托类必须实现某个接口,这里我们实现的是Sell接口。委托类Vendor类的定义如下:


  1. public class Vendor implements Sell {
  2. public void sell() {
  3. System.out.println("In sell method");
  4. }
  5. public void ad() {
  6. System,out.println("ad method")
  7. }
  8. }

(3)中介类

上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:


  1. public class DynamicProxy implements InvocationHandler {
  2. private Object obj; //obj为委托类对象;
  3. public DynamicProxy(Object obj) {
  4. this.obj = obj;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("before");
  9. Object result = method.invoke(obj, args);
  10. System.out.println("after");
  11. return result;
  12. }
  13. }

从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法(第11行),看到这里是不是觉得似曾相识?通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类; 代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

(4)动态生成代理类

动态生成代理类的相关代码如下:


  1. public class Main {
  2. public static void main(String[] args) {
  3. //创建中介类实例
  4. DynamicProxy inter = new DynamicProxy(new Vendor());
  5. //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
  6. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
  7. //获取代理类实例sell
  8. Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
  9. //通过代理类对象调用代理类方法,实际上会转到invoke方法调用
  10. sell.sell();
  11. sell.ad();
  12. }
  13. }

在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:


  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

方法的三个参数含义分别如下:

loader:定义了代理类的ClassLoder;

interfaces:代理类实现的接口列表

h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例

我们运行一下,看看我们的动态代理是否能正常工作。我这里运行后的输出为:

说明我们的动态代理确实奏效了。

上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。

先看一下代理模式,这个应该是设计模式中最简单的一个了,类图

代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操作

Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理,这样说的比较笼统,举个简单的例子

比如我们在应用中有这样一个需求,在对某个类的一个方法的调用前和调用后都要做一下日志操作,

一个普通的接口

  1. public interface AppService {
  2. public boolean createApp(String name);
  3. }

该接口的默认实现类

  1. public class AppServiceImpl implements AppService {
  2. public boolean createApp(String name) {
  3. System.out.println("App["+name+"] has been created.");
  4. return true;
  5. }
  6. }

日志处理器

  1. public class LoggerInterceptor implements InvocationHandler {//注意实现这个Handler接口
  2. private Object target;//目标对象的引用,这里设计成Object类型,更具通用性
  3. public LoggerInterceptor(Object target){
  4. this.target = target;
  5. }
  6. public Object invoke(Object proxy, Method method, Object[] arg)
  7. throws Throwable {
  8. System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");
  9. Object result = method.invoke(target, arg);//调用目标对象的方法
  10. System.out.println("Before return:"+result);
  11. return result;
  12. }
  13. }

外部调用

  1. public class Main {
  2. public static void main(String[] args) {
  3. AppService target = new AppServiceImpl();//生成目标对象
  4. //接下来创建代理对象
  5. AppService proxy = (AppService) Proxy.newProxyInstance(
  6. target.getClass().getClassLoader(),
  7. target.getClass().getInterfaces(), new LoggerInterceptor(target));
  8. proxy.createApp("Kevin Test");
  9. }
  10. }

from https://blog.csdn.net/andyzhaojianhui/article/details/72833114

十分钟理解Java中的动态代理的更多相关文章

  1. 使用Java中的动态代理实现数据库连接池

    2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的 ...

  2. java中的动态代理机制

    java中的动态代理机制 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现 ...

  3. 深度剖析java中JDK动态代理机制

    https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...

  4. 一文读懂Java中的动态代理

    从代理模式说起 回顾前文: 设计模式系列之代理模式(Proxy Pattern) 要读懂动态代理,应从代理模式说起.而实现代理模式,常见有下面两种实现: (1) 代理类关联目标对象,实现目标对象实现的 ...

  5. 10分钟了解 代理模式与java中的动态代理

    前言    代理模式又分为静态代理与动态代理,其中动态代理是Java各大框架中运用的最为广泛的一种模式之一,下面就用简单的例子来说明静态代理与动态代理. 场景    李雷是一个唱片公司的大老板,很忙, ...

  6. java中的动态代理Proxy

    动态代理是java语言的一个神奇的地方,不是很好理解,下面来看看关键的地方. InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHa ...

  7. 使用 JAVA 中的动态代理实现数据库连接池

    数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈.我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共 ...

  8. java中的动态代理

    1.动态代理的定义:为其他对象提供一个代理以控制对这个对象的访问 2.通过api看下proxy生成代理类的2中写法: 创建某一接口 Foo 的代理: InvocationHandler handler ...

  9. 代理模式与java中的动态代理

    前言    代理模式又分为静态代理与动态代理,其中动态代理是Java各大框架中运用的最为广泛的一种模式之一,下面就用简单的例子来说明静态代理与动态代理. 场景    李雷是一个唱片公司的大老板,很忙, ...

随机推荐

  1. css3实现的鼠标经过按钮特效

    今天要为各网友再带来一款css实现的鼠标经过按钮的特效.按钮初始时,边框为断开的按钮,当鼠标经过按钮时动画变为封闭的边框.效果图如下: 在线预览   源码下载 我们一起看下实现的代码: html代码: ...

  2. resource文件

    Resources的详情见http://www.csharpwin.com/dotnetspace/10957r3991.shtml 1.create public static void Main( ...

  3. centsos7修改主机名 [root@st152 ~]# cat /etc/hostname

    CentOS7修改服务器主机名方法CentOS7下修改主机名第一种:hostname 主机名01.hostname 主机名称 <pre name="code" class=& ...

  4. JSON教程

    一.什么是JSON 1.JSON指的是JavaScript对象表示法(JavaScript Object Notation). 2.JSON是轻量级的文本数据交换格式,比XML更小.更快.更易解析. ...

  5. Ajax-ajax实例2-根据邮政编码获取地区信息

    项目结构 运行效果: 数据库: /* SQLyog Ultimate v12.09 (64 bit) MySQL - 5.5.53 : Database - ajaxexample_2 ******* ...

  6. rails局部模板 render

    <%= render partial: 'file' %> file是以_开头命名的文件,比如_cart.html.erb 这样就可以用render来调用了 还可以传参数 比如 rails ...

  7. 修改jvm内存大小

  8. 获取touchstart,touchmove,touchend 坐标

    简单说下如何用jQuery 和 js原生代码获取touchstart,touchmove,touchend 坐标值: jQuery 代码: $('#id').on('touchstart',funct ...

  9. Android修改默认SharedPreferences文件的路径,SharedPreferences常用工具类

    import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; ...

  10. 在jsp中,page指令的()属性用来引入需要的包或类。

    在jsp中,page指令的()属性用来引入需要的包或类. A.extends B.import C.language D.contentType 解答:B