7

Java中的动态代理

 3 years ago
source link: http://yuanfentiank789.github.io/2016/07/26/proxy/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、代理概念

为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

image

从图中可以看出,代理接口(Subject)、代理类(ProxySubject)、委托类(RealSubject)形成一个“品”字结构。 根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种。

下面以一个模拟需求说明静态代理和动态代理:委托类要处理一项耗时较长的任务,客户类需要打印出执行任务消耗的时间。解决这个问题需要记录任务执行前时间和任务执行后时间,两个时间差就是任务执行消耗的时间。

二、 静态代理

由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

清单1:代理接口

/**  
 * 代理接口。处理给定名字的任务。 
 */  
public interface Subject {  
  /** 
   * 执行给定名字的任务。 
    * @param taskName 任务名 
   */  
   public void dealTask(String taskName);   
}  

清单2:委托类,具体处理业务。

/** 
 * 真正执行任务的类,实现了代理接口。 
 */  
public class RealSubject implements Subject {  
  
 /** 
  * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间 
  * @param taskName  
  */  
   @Override  
   public void dealTask(String taskName) {  
      System.out.println("正在执行任务:"+taskName);  
      try {  
         Thread.sleep(500);  
      } catch (InterruptedException e) {  
         e.printStackTrace();  
      }  
   }  
}  

清单3:静态代理类

/** 
 * 代理类,实现了代理接口。 
 */  
public class ProxySubject implements Subject {  
 //代理类持有一个委托类的对象引用  
 private Subject delegate;  
   
 public ProxySubject(Subject delegate) {  
  this.delegate = delegate;  
 }  
  
 /** 
  * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 
  *  
  * @param taskName 
  */  
 @Override  
 public void dealTask(String taskName) {  
  long stime = System.currentTimeMillis();   
  //将请求分派给委托类处理  
  delegate.dealTask(taskName);  
  long ftime = System.currentTimeMillis();   
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    
 }  
}  

清单4:生成静态代理类工厂

public class SubjectStaticFactory {  
 //客户类调用此工厂方法获得代理对象。  
 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
 public static Subject getInstance(){   
  return new ProxySubject(new RealSubject());  
 }  
}  

清单5:客户类

public class Client1 {  
  
 public static void main(String[] args) {  
  Subject proxy = SubjectStaticFactory.getInstance();  
  proxy.dealTask("DBQueryTask");  
 }   
  
}  

静态代理类优缺点

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

三 动态代理

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

动态代理的最大特点是内存会产生新的class字节码,但业务类必须基于接口,涉及到5个class,UML图如下:

image

1、先看看与动态代理紧密关联的Java API。

1)java.lang.reflect.Proxy 这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

清单6:Proxy类的静态方法

// 方法 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 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

清单7:InvocationHandler的核心方法

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

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

2、动态代理实现步骤

具体步骤是:

a. 实现InvocationHandler接口创建自己的调用处理器

b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类

c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数

d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

清单8:分步骤实现动态代理

// 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对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。

清单9:简化后的动态代理实现

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

3、动态代理实例

(1)BusinessInterface 业务类的接口;

//业务接口BusinessInterface
public interface BusinessInterface
{
	public void doSomething();
	public void doSomething2();
	public void doSomething3(String input);
}

(2)BusinessClass 真正的业务类


package com.java.proxy.dynamic;

/**
 * 真正的业务类 
 */
public class BusinessClass implements BusinessInterface
{
	public static final String	TAG	= "BfdUtils";
	public void doSomething()
	{
		System.out.println("业务组件BusinessClass方法调用:doSomething()");
		doSomething2();// 在这里调用不会被拦截
	}
	public void doSomething2()
	{
		System.out.println("业务组件BusinessClass方法调用:doSomething()=====" + TAG);
	}
	@Override
	public void doSomething3(String input)
	{
		System.out.println(input);

	}
}

(3)DynamicProxyHandler 实现一个InvocationHandler的实现类,通过bind方法返回代理对象,也可以直接在外部调用

package com.java.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * User: leizhimin Date: 2008-3-20 23:24:10 Company:
 * LavaSoft(http://lavasoft.blog.51cto.com/) 动态代理处理器工具
 */
public class DynamicProxyHandler implements InvocationHandler
{
	private Object				business;								// 被代理对象
	private InterceptorClass	interceptor	= new InterceptorClass();	// 拦截器

	/**
	 * 动态生成一个代理类对象,并绑定被代理类和代理处理器
	 * 
	 * @param business
	 * @return 代理类对象
	 */
	public Object bind(Object business)
	{
		this.business = business;
		return Proxy.newProxyInstance(
				// 被代理类的ClassLoader

				business.getClass().getClassLoader(),
				// 要被代理的接口,本方法返回对象会自动声称实现了这些接口

				business.getClass().getInterfaces(),
				// 代理处理器对象

				this);
	}

	/**
	 * 代理要调用的方法,并在方法调用前后调用连接器的方法.
	 * 
	 * @param proxy
	 *            代理类对象
	 * @param method
	 *            被代理的接口方法
	 * @param args
	 *            被代理接口方法的参数
	 * @return 方法调用返回的结果
	 * @throws Throwable
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
	{
		Object result = null;
		interceptor.before();
		result = method.invoke(business, args);
		interceptor.after();
		return result; // To change body of implemented methods use File |
						// Settings | File Templates.

	}
}

(4)Proxy 用于返回新产生类的实例对象,通过newProxyInstance方法把classloader,接口class和handler(自身)传递进去

Proxy.newProxyInstance(
				// 被代理类的ClassLoader

				business.getClass().getClassLoader(),
				// 要被代理的接口,本方法返回对象会自动实现这些接口

				business.getClass().getInterfaces(),
				// 代理处理器对象

				this);

(5)测试代码如下:

package com.java.proxy.dynamic;

import dynamic.proxy.ProxyGeneratorUtils;

public class Client
{
public static void main(String args[])
{
	DynamicProxyHandler handler = new DynamicProxyHandler();
	BusinessInterface business = new BusinessClass();
	BusinessInterface businessProxy = (BusinessInterface) handler.bind(business);
	ProxyGeneratorUtils.writeProxyClassToHardDisk("/Users/apple/$Proxy11.class");
	businessProxy.doSomething();//只有直接执行businessProxy的方法才会被拦截
	System.out.println("===========================");
	BusinessInterface2 business2 = new BusinessClass2();
	BusinessInterface2 businessProxy2 = (BusinessInterface2) handler.bind(business2);
	businessProxy2.doSomething();
}
}

4、动态代理原理

Proxy.newProxyInstance()源码如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
    // 检查 h 不为空,否则抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 获得与指定类装载器和一组接口相关的代理类类型对象
    Class cl = getProxyClass(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    try { 
        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()); 
    } 
}

类Proxy的getProxyClass方法调用ProxyGenerator的 generateProxyClass方法产生ProxySubject.class的二进制数据

public static byte[] generateProxyClass(final String name, Class[] interfaces)

现在的思路是把动态产生的class字节码保存成文件,然后反编译这个class,就可以大概知道实现原理了。 网上流传两种保存动态代理class的方法,封装成工具类如下: ProxyGeneratorUtils代码如下:

/**
 * 代理类的生成工具
 * @author zyb
 * @since 2012-8-9
 */
public class ProxyGeneratorUtils {

/**
 * 把代理类的字节码写到硬盘上
 * @param path 保存路径
 */
public static void writeProxyClassToHardDisk(String path) {
	// 第一种方法,这种方式在刚才分析ProxyGenerator时已经知道了
	// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
	
	// 第二种方法
	
	// 获取代理类的字节码
	byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", BusinessClass.class.getInterfaces());
	
	FileOutputStream out = null;
	
	try {
		out = new FileOutputStream(path);
		out.write(classFile);
		out.flush();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		try {
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
}

完成后,反编译这个class文件,得到如下代码:

$Proxy11.class 产生的class,实现了BusinessInterface接口(含有所有业务方法),是Proxy的子类(构造函数可接受InvocationHandler参数),产生后位于内存中,可输出到文件查看,反编译结果如下:

import com.java.proxy.dynamic.BusinessInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class Proxy11 extends Proxy
  implements BusinessInterface
{
  private static Method m1;
  private static Method m5;
  private static Method m0;
  private static Method m3;
  private static Method m4;
  private static Method m2;

  public Proxy11()
    throws 
  {
    super(paramInvocationHandler);//调用父类Proxy的构造函数为handler赋值,用于函数调用
  }

  public final boolean equals()
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void doSomething3()
    throws 
  {
    try
    {
      this.h.invoke(this, m5, new Object[] { paramString });
      return;
    }
    catch (Error 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 localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

//调用代理对象的doSomething方法时,调用handler的invoke方法,
  public final void doSomething()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void doSomething2()
    throws 
  {
    try
    {
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error 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 localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static//类加载后,得到被代理接口的所有Method对象
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m5 = Class.forName("com.java.proxy.dynamic.BusinessInterface").getMethod("doSomething3", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.java.proxy.dynamic.BusinessInterface").getMethod("doSomething", new Class[0]);
      m4 = Class.forName("com.java.proxy.dynamic.BusinessInterface").getMethod("doSomething2", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

观察生成的class,动态代理的原理就很清楚了。

5 动态代理特点

首先是动态生成的代理类本身的一些特点。

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)类继承关系:该类的继承关系如图:

image

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

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

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

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

6 动态代理的优点和不足

优点: 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。

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

有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口或者想要代理的方法不属于接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK