现在的位置: 首页 > 综合 > 正文

java学习脚印:反射与动态代理

2014年06月29日 ⁄ 综合 ⁄ 共 4329字 ⁄ 字号 评论关闭

java学习脚印:反射与动态代理

1.代理模式的内涵

代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。(节选自[1])


代理模式的UML类图如下:



图中的角色说明:

Subject: 主体参与者,对proxy和RealSubject参与者一视同仁,定义了统一的接口,这样在任何使用RealSubject的地方都可以使用Proxy.


RealSubject :真实主体参与者,当代理无法处理时,即轮到"本人"亲自上场处理。


Proxy:代理参与者,代理会尽量处理Client参与者的要求,当自己无法单独处理时,Proxy参与者便会把工作交给真实主体自己处理。只有到真正要用上真实主体参与者时,代理才会产生RealSubject对象实例。

更多关于代理模式内容,可以参考我的另一篇博客《java学习脚印: 设计模式之代理模式(Proxy)》。


按照代理的创建时期,代理类可以分为两种。

静态代理:在程序运行之前的class文件之中就已经存在代理类了。
动态代理:利用动态代理可以在运行时创建一个实现了给定接口的类,这种功能只有在编译时无法确定需要实现哪个接口的时才有必要使用。(参考自[2])


2.java动态代理的实现

2.1利用反射实现动态代理

java要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。该方法

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

有三个参数:

1) ClassLoader loader:类加载器

作为java安全模式的一部分,对于系统类和从网上下载的类,可以使用不同的类加载器。

在Java中主要有一下三种类加载器; 
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的; 
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类; 
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。


2) Class<?>[] interfaces:一个Class对象数组,每个元素都是需要实现的接口。


3) InvocationHandler h:这是一个调用处理器。

InvocationHandler接口,包括一个方法:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

有三个参数:
1) Object proxy:指被代理的对象。 
2) Method method:要调用的方法 
3) Object[] args:方法调用时所需要的参数

个人理解,代理的基本思想:通过动态生成一个实现了给定接口的动态类,由此类生成的对象来传递本应该由原对象处理的请求,在将请求真正传递给真正处理请求的对象之前或者之后可以做一些附加操作,例如预处理消息、过滤消息、以及事后处理消息等操作。因为代理类实现了给定接口,所以可以从newProxyInstance返回的对象转换为给定接口引用的对象,从而调用接口中定义的方法。


2.2 示例代码

这里为了便于理解,给出一个最简单的例子,我们使用代理来给除法操作做出一个代理(尽管这个例子使用代理的场景不太适宜,但是便于理解。),该代理将代替真正作除法运算的对象做除法合理性验证,也即是否包含除0操作的过滤。

ProxyDemo1给出使用静态代理方式实现,ProxyDemo2给出动态代理实现方式,两者的输出结果基本相同。


1)静态代理程序ProxyDemo1

清单 IDivide.java

package com.learningjava;

public interface IDivide {
	double divide(int dividend,int divisor);
}


清单 Divide.java

package com.learningjava;

public class Divide implements IDivide {
    
	@Override
	public double divide(int dividend, int divisor) {
		System.out.println("calculating...");
		return dividend/divisor;
	}
}


清单 DivideProxy.java

package com.learningjava;

public class DivideProxy implements IDivide{
	public DivideProxy(Divide realSubject) {
		this.realSubject = realSubject;
	}
	@Override
	public double divide(int dividend, int divisor) {
		if(divisor == 0) {
			throw new IllegalArgumentException("divisor can not be zero.") ;
		}
		else {
			return realSubject.divide(dividend, divisor);
		}
	}
    private Divide realSubject;
}


清单 ProxyDemo1.java

package com.learningjava;
/**
 * simple proxy demo
 * the proxy prevent dividing by zero
 * @author wangdq
 * 2014-2-26
 */
public class ProxyDemo1 {
    public static void main(String[] args) {
        Divide divide = new Divide();
        DivideProxy divideProxy = new DivideProxy(divide);
        System.out.println(divideProxy.divide(5, 2));
        System.out.println(divideProxy.divide(5, 0));
    }
}

运行结果如下:

calculating...
2.0
Exception in thread "main" java.lang.IllegalArgumentException: divisor can not be zero.
    at com.learningjava.DivideProxy.divide(DivideProxy.java:11)
    at com.learningjava.ProxyDemo1.main(ProxyDemo1.java:13)


2)动态代理程序ProxyDemo2

与ProxyDemo1相比,少了DivideProxy类,多了一个DivideProxyHandler。


清单 DivideProxyHandler.java

package com.learningjava;

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

public class DivideProxyHandler implements InvocationHandler{
	
	DivideProxyHandler(Object obj) {
		this.target = obj;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if((int)args[1] == 0) {
			throw new IllegalArgumentException("divisor can not be zero.") ;
		}else {
			return method.invoke(target, args);
		}
	}
    private Object target;
}


清单 ProxyDemo2.java

package com.learningjava;

import java.lang.reflect.Proxy;
/**
 * simple dynamicly proxy demo
 * the proxy prevent dividing by zero 
 * @author wangdq
 * 2014-2-26
 */
public class ProxyDemo2 {
	public static void main(String[] args) {
		IDivide divide = new Divide();
		IDivide dynamicProxy = (IDivide)Proxy.newProxyInstance(IDivide.class.getClassLoader(), 
				new Class[]{IDivide.class}, new DivideProxyHandler(divide));
		System.out.println(dynamicProxy.divide(5, 2));
		dynamicProxy.divide(5, 0);
	}
}


运行结果如下:

calculating...
2.0
Exception in thread "main" java.lang.IllegalArgumentException: divisor can not be zero.
    at com.learningjava.DivideProxyHandler.invoke(DivideProxyHandler.java:16)
    at com.sun.proxy.$Proxy0.divide(Unknown Source)
    at com.learningjava.ProxyDemo2.main(ProxyDemo2.java:16)


对于代理还有一些其他内容,文中没有深入讲解,暂且理解到这一程度,留待以后有必要时深入。

3.参考资料

[1]:博客园   C'est la vie    java动态代理(JDK和cglib)

[2]:《java核心技术 卷一》机械工业出版社 第八版

抱歉!评论已关闭.