博客
关于我
十分钟理解Java之动态代理
阅读量:202 次
发布时间:2019-02-28

本文共 7837 字,大约阅读时间需要 26 分钟。

本篇文章会从代理的概念出发,介绍Java中动态代理技术的使用,并进一步探索它的实现原理。由于个人水平有限,叙述中难免出现不清晰或是不准确的地方,希望大家可以指正,谢谢大家:)

概述

什么是代理

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

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

静态代理

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

 

public interface Sell { void sell(); void ad();}

Vendor类的定义如下:

 

public class Vendor implements Sell {   public void sell() {     System.out.println("In sell method");   }   public void ad() {     System.out.println("ad method")   }}

代理类BusinessAgent的定义如下:

 

public class BusinessAgent implements Sell {  private Vendor mVendor;   public BusinessAgent(Vendor vendor) {    mVendor = vendor;  }  public void sell() {    mVendor.sell();  } public void ad() {    mVendor.ad();  }}

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

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

 

public class BusinessAgent implements Sell {   ...   public void sell() {     if (isCollegeStudent()) {       vendor.sell();     }   }   ...}

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

动态代理

什么是动态代理

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

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

 

public class BusinessAgent implements Sell {   private Vendor mVendor;   public BusinessAgent(Vendor vendor) {     this.mVendor = vendor;   }   public void sell() {     System.out.println("before");     mVendor.sell();     System.out.println("after");   }   public void ad() {     System.out.println("before");     mVendor.ad();     System.out.println("after");   }}

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

使用动态代理

InvocationHandler接口

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

 

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

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

委托类的定义

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

 

public class Vendor implements Sell {   public void sell() {     System.out.println("In sell method");   }   public void ad() {     System.out.println("ad method")   }}

中介类

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

 

public class DynamicProxy implements InvocationHandler {  private Object obj; //obj为委托类对象  public DynamicProxy(Object obj) {    this.obj = obj;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    System.out.println("before");    Object result = method.invoke(obj, args);    System.out.println("after");    return result;  }}

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

动态生成代理类

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

 

public class Main { public static void main(String[] args) {   DynamicProxy inter = new DynamicProxy(new Vendor());   //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");   //获取代理类实例sell   Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));   sell.sell();   sell.ad();   }}

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

 

public static Object newProxyInstance(ClassLoader loader,     Class
[] interfaces, InvocationHandler h) throws IllegalArgumentException

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

  • loader:定义了代理类的ClassLoder;

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

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

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

     

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

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

动态代理类的源码分析

通过运行Main,我们会得到一个名为“$Proxy”的class文件,这个文件即为动态生成的代理类,我们通过反编译来查看下这个代理类的源代码:

 

package com.sun.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Sell {   //这5个Method对象分别代表equals()、toString()、ad()、sell()、hashCode()方法   private static Method m1;   private static Method m2;   private static Method m4;   private static Method m3;   private static Method m0;   //构造方法接收一个InvocationHandler对象为参数,这个对象就是代理类的“直接委托类”(真正的委托类可以看做代理类的“间接委托类”)  public $Proxy0(InvocationHandler var1) throws {     super(var1);   }   //对equals方法的调用实际上转为对super.h.invoke方法的调用,父类中的h即为我们在构造方法中传入的InvocationHandler对象,以下的toString()、sell()、ad()、hashCode()等方法同理   public final boolean equals(Object var1) throws {     try {       return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();     } catch (RuntimeException | Error var3) {       throw var3;     } catch (Throwable var4) {       throw new UndeclaredThrowableException(var4);     }   }   public final String toString() throws {     try {       return (String)super.h.invoke(this, m2, (Object[])null);     } catch (RuntimeException | Error var2) {       throw var2;     } catch (Throwable var3) {       throw new UndeclaredThrowableException(var3);     }   }   public final void ad() throws {     try {       super.h.invoke(this, m4, (Object[])null);     } catch (RuntimeException | Error var2) {       throw var2;     } catch (Throwable var3) {       throw new UndeclaredThrowableException(var3);     }   }   public final void sell() throws {     try {       super.h.invoke(this, m3, (Object[])null);     } catch (RuntimeException | Error var2) {       throw var2;     } catch (Throwable var3) {       throw new UndeclaredThrowableException(var3);     }   }   public final int hashCode() throws {     try {       return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();     } catch (RuntimeException | Error var2) {       throw var2;     } catch (Throwable var3) {       throw new UndeclaredThrowableException(var3);     }   }   //这里完成Method对象的初始化(通过反射在运行时获得Method对象)   static {     try {       m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});       m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);       m4 = Class.forName("Sell").getMethod("ad", new Class[0]);       m3 = Class.forName("Sell").getMethod("sell", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);     } catch (NoSuchMethodException var2) {       throw new NoSuchMethodError(var2.getMessage());     } catch (ClassNotFoundException var3) {       throw new NoClassDefFoundError(var3.getMessage());     }   }}

我们可以看到,以上代码的逻辑十分简单,我们在注释中也做出了相关的说明。(以上代码中涉及到反射的使用,对于反射还不是很熟悉的小伙伴可以参考这里:)

现在,我们已经了解了动态代理的使用,也搞清楚了它的实现原理,更进一步的话我们可以去了解动态代理类的生成过程,只需要去阅读newProxyInstance方法的源码即可,这个方法的逻辑也没有复杂的地方,这里就不展开了。感兴趣的小伙伴可以自己去跟一下,也可以参考这篇文章:

转载地址:http://duhs.baihongyu.com/

你可能感兴趣的文章
mysql 主键重复则覆盖_数据库主键不能重复
查看>>
Mysql 事务知识点与优化建议
查看>>
Mysql 优化 or
查看>>
mysql 优化器 key_mysql – 选择*和查询优化器
查看>>
MySQL 优化:Explain 执行计划详解
查看>>
Mysql 会导致锁表的语法
查看>>
mysql 使用sql文件恢复数据库
查看>>
mysql 修改默认字符集为utf8
查看>>
Mysql 共享锁
查看>>
MySQL 内核深度优化
查看>>
mysql 内连接、自然连接、外连接的区别
查看>>
mysql 写入慢优化
查看>>
mysql 分组统计SQL语句
查看>>
Mysql 分页
查看>>
Mysql 分页语句 Limit原理
查看>>
MySql 创建函数 Error Code : 1418
查看>>
MySQL 创建新用户及授予权限的完整流程
查看>>
mysql 创建表,不能包含关键字values 以及 表id自增问题
查看>>
mysql 删除日志文件详解
查看>>
mysql 判断表字段是否存在,然后修改
查看>>