Java动态代理

代理是基本的设计模式之一,它在调用者与真实的被调用对象之间增加一个代理层,调用者通过代理层来操作真实的对象,即调用者发送指令给代理层,然后由代理层调用真实对象,调用完成之后,代理层再将执行的结果返回给调用者。代理模式的调用过程如下图所示:


使用代理模式,在调用者上只需要持有一个代理层的对象,由代理层的对象来完成具体的对象调用操作,屏蔽具体的细节,让调用者可以专注于自身的业务,而不用去处理调用真实对象的细节问题。

基本代理模式

代理的实现,一般要先定义一个公共接口,代理类和真实类都实现这个接口,并且代理类持有一个真实类的引用,这样,调用者每次调用代理类时,代理类就将调用方法转发到真实类的对象上,实现真实的调用,由于代理类和真实类实现了同样的接口,所以它们可以有相同的行为,从而对于调用者来说,使用代理类时与使用真实类无差别。下面的例子有一个接口Interface,代理类SimpleProxy,真实类RealObject和一个调用者SimpleProxyDemo类,代码如下:

接口Interface

package test.proxy;

public interface Interface {
	void doSomething();
	void somethingElse(String arg);
}
代理类SimpleProxy
package test.proxy;

public class SimpleProxy implements Interface {

	private Interface proxied;
	
	public SimpleProxy(Interface proxied) {
		this.proxied = proxied;
	}
	
	@Override
	public void doSomething() {
		System.out.println("SimpleProxy doSomething");
		proxied.doSomething();
	}

	@Override
	public void somethingElse(String arg) {
		System.out.println("SimpleProxy somethingElse " + arg);
		proxied.somethingElse(arg);
	}

}
真实类RealObject
package test.proxy;

public class RealObject implements Interface {

	@Override
	public void doSomething() {
		System.out.println("doSomething");
	}

	@Override
	public void somethingElse(String arg) {
		System.out.println("somethingElse " + arg);
	}

}
调用者SimpleProxyDemo
package test.proxy;

public class SimpleProxyDemo {
	
	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("run");
	}
	
	public static void main(String[] args) {
		consumer(new RealObject());
		consumer(new SimpleProxy(new RealObject()));
	}
}
上面的例子实现了一个简单的代理模式,调用者SimpleProxyDemo调用的代理类SimpleProxy的方法,代理类将调用者的调用方法转发给真实类RealObject,从而实现类代理,这样调用者就可以专注与具体业务的开发,使用代理类屏蔽调用真实类的具体细节,比如调用者要调用远程机器上的一个方法,那么就可以实现一个代理类处理复杂的远程方法调用,而调用者只需要关心如何实现具体的业务逻辑。

Java动态代理

Java的动态代理的思想比基本代理更进一步,因为它可以动态地创建代理并动态的处理对所代理方法的调用,Java中的动态代理会将所有调用重定向到一个调用处理器上,然后利用反射机制来调用具体方法。使用Java动态代理的方式重写上面的示例,代码如下:

实现了Java动态代理要求的InvocationHandler接口的类,作为代理类的处理中心,所有的方法将会在invoke()方法中处理,这样的实现机制大大简化了编程,比如在方法调用完之后,统一记录日志,如果不使用代理,那么必须要手动在每次调用完之后记录。

package test.proxy;

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

public class DynamicProxyHandler implements InvocationHandler {

	private Object proxied;
	
	public DynamicProxyHandler(Object proxied) {
		this.proxied = proxied;
	}
	
	@Overrid
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("*********proxy: " + proxy.getClass()  + ",method:" + method);
		return method.invoke(proxied, args);
	}

}
调用客户端:

package test.proxy;

import java.lang.reflect.Proxy;

public class SimpleDynamicProxy {

	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("run");
	}
	
	public static void main(String[] args) {
		
		RealObject real = new RealObject();
		consumer(real);
		
		Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real));
		consumer(proxy);
		
	}

}
上面的代码演示Java动态代理基本流程,Java动态代理需要通过Proxy.newProxyInstance()方法创建一个代理对象的引用,这个代理对象的类在运行过程中生成,上面的代码中,proxy对象就指向这个动态代理对象。使用Java动态代理,必须实现一个调用处理器,这个处理器实现了InvocationHandler接口,这是Java动态代理机制所要求实现的接口,所有代理对象调用的方法都会转发到这个调用处理器理进行方法调用。

利用Java代理可以在运行时创建一个实现了一组给定接口的新类,上面的Java动态代理示例中这个接口就是Interface接口,这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用,上面的代码为了简化Java动态代理的示例,直接在客户端中给出了真实的类型。假设有一个表示接口的Class对象,她的确切类型在编译时无法知道,要想构造一个实现了这个接口的类,就需要使用newInstance方法或者反射找出这个类的构造器。但是不能实例化一个接口,需要在程序处于运行状态时定义一个新类。

Java动态代理机制可以在运行时加载一个全新的类,这个类实现类指定的接口。无论何时调用代理对象的方法,调度处理器的invoke方法都会被调用,并向其传递Method对象和原始调用参数,要想创建一个代理对象,需要使用Proxy的newProxyInstance方法,这三个方法有三个参数:
    一个类加载器(class loader),作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器,null表示使用默认的类加载器。
    一个Class对象数组,每个元素都是需要实现的接口,真实对象所实现的接口

    一个调用处理器,上面示例中的DynamicProxyHandler类

在DynamicProxyHandler类的invoke()方法中有一个Object proxy参数,这个参数并没有使用到,那么到底放在这里有什么用呢?在invoke方法中设置一个断点,观察proxy变量在运行时的值,如下:

从上图可以看出proxy是$Proxy0类型,这是运行时自动生成的临时类型,proxy的toString()方法返回的值是test.proxy.RealObject@193b604,所以proxy内部应该有一个RealObject类型的引用,具体这个对象在invoke()方法有什么作用以我现在的水平暂时还不知道,或许是为了在invoke()方法中执行一些检查吧!因为具体使用的时候也没有用到这个对象。

Java动态代理的特性

1.代理类是在程序运行过程中创建的,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别
2.所有的代理类都扩展与Proxy类,一个代理类只有一个实例域--调用处理器,它定义在Proxy超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。代理Comparable对象时,TraceHandler包装了实际的对象
3.所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有代理方法一样,这些方法仅仅调用了调用处理器的invoke方法。Object中的其他方法(如clone和getClass)没有被重新定义。
4.没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以$Proxy开头的类名。
5.对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说如果使用同一个类加载器和接口数组调用两次newProxyInstance方法,那么只能得到同一个类的两个对象,也可用getProxyClass方法获得这个类。
6.代理类一定时public和final的,如果代理类实现的所有接口都时public,代理类就不属于某个特定的包,否则,所有非共有接口都必须属于同一个包,同时,代理类也属于这个包。

7.可以通过Proxy类的isProxyClass方法检测一个特定的Class对象是否代表一个特定的代理类

下面再给出一个Java动态代理的示例,这个示例主要是在一个集合中使用二分法查找一个元素是否在集合中,示例中元素类型是整数,由于整数类型Integer实现了Comparable接口,所以在Proxy.newProxyInstance()方法中会传递一个包含Comparable接口类的数组,Arrays.binarySearch()方法使用了Integer类型的compareTo方法来比较整数的大小,所以对Comparable接口的compareTo()方法调用都会转发到调用处理器的TraceHandler的invoke()方法中进行处理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Random;


public class TraceHandler implements InvocationHandler {


 Object target;
 
 public TraceHandler(Object t) {
 this.target = t;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args)
 throws Throwable {
 System.out.println("method:" + method.getName());
 return method.invoke(target, args);
 }
 
 public static void main(String[] args) {
 Class[] interfaces = new Class[] {Comparable.class};
 Object[] elements = new Object[1000];
 for(int i = 0; i < elements.length; i++) {
 InvocationHandler handler = new TraceHandler(i+1);
 elements[i] = Proxy.newProxyInstance(null, interfaces, handler);
 }
 
 Integer key = new Random().nextInt(elements.length) + 1;
 Arrays.binarySearch(elements, key);
 
 }
}

Reference

《Java编程思想(第四版)》

《Java核心技术(第二版)》

《Hadoop技术内幕:深入解析Hadoop Common和HDFS架构设计与实现原理》

http://www.iteye.com/problems/14790

http://paddy-w.iteye.com/blog/841798

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值