首页 > Java > 高新技术(八) 动态代理技术
2014
06-06

高新技术(八) 动态代理技术

① 代理类

关键词:目标类 系统功能 代理类

问题:要为已存在的(多个具有相同接口的)目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

解决方法:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码,如下图

proxy and target

② 扩展:AOP (面向切面编程)

系统中各个模块存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下系统中的StudentService、CourseService、TeacherService等模块均实现了安全、事务、日志等功能:

aop01

下图用具体的程序代码描述了交叉业务(一个交叉业务就是要切入到系统中的一个方面,切面):

 aop02

交叉业务的编程问题即为面向切面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

aop03

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术

扩展:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

③、静态代理技术

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

|— 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

|— 动态代理:在程序运行时,运用反射机制动态创建而成。

第一个例子:包含Count接口,Count的两个实现:CountImpl(目标类)、CountProxy(代理类),以及测试类CountTest。代码如下:

public interface Count {
	//查看账户
	void queryCount();
	//修改账户
	void updateCount();
}

/**
 * 目标类---> 包含实际业务逻辑
 */
public class CountImpl implements Count {
	public void queryCount() {
		System.out.println("正在查看账户");
	}

	public void updateCount() {
		System.out.println("正在修改账户");
	}

}

/**
 * 代理类 ---> 增强的CountImpl,添加系统功能
 */
public class CountProxy implements Count{
	private CountImpl countImpl;

	public CountProxy(CountImpl countImpl){
		this.countImpl = countImpl;
	}

	public void queryCount(){
		System.out.println("准备查看");
		countImpl.queryCount();
		System.out.println("结束查看");
	}

	public void updateCount(){
		System.out.println("准备修改");
		countImpl.updateCount();
		System.out.println("结束修改");
	}
}

public class CountTest {

	public static void main(String[] args) {
		CountImpl ci = new CountImpl();
		CountProxy cp = new CountProxy(ci);

		cp.queryCount();
		cp.updateCount();
	}

}

可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,这个可以使用动态代理完成。

④、动态代理技术(Proxy类、InvocationHandler接口)

JDK动态代理API包含一个类和一个接口:

☆ Proxy类:专门完成代理的操作类,提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

A. getProxyClass()方法:返回代理类的 java.lang.Class 对象,声明如下:

static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)

B. newProxyInstance()方法:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序,声明如下:

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

☆ InvocationHandler接口:InvocationHandler 是代理实例的调用处理程序实现的接口。每个代理实例都有一个关联的调用处理程序,调用代理实例的方法时,将对方法的调用指派到它的调用处理程序的invoke()方法。接口声明如下

public interface InvocationHandler {

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

}

小练习一、创建Collection接口的代理类和查看其名称:Proxy.getProxyClass()

Class proxyClazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(proxyClazz.getName());
//输出com.sun.proxy.$Proxy0

小练习二、编码列出代理类中的所有构造方法和参数签名

//获取代理类的构造方法
Constructor[] constructors = proxyClazz.getConstructors();
for(Constructor constructor : constructors){
	StringBuilder sb = new StringBuilder(constructor.getName());

	//对于遍历的每个构造方法,依次获取其参数类型
	Class[] paramTypes = constructor.getParameterTypes();
	sb.append("(");
	for(Class paramType : paramTypes){
		sb.append(paramType).append(",");
	}
	sb.deleteCharAt(sb.length()-1);
	sb.append(")");
	System.out.println(sb);
}
//输出com.sun.proxy.$Proxy0(interface java.lang.reflect.InvocationHandler)

小练习三、创建动态代理类的实例(步骤:代理类 — Constructor对象 — 代理实例)

Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
//本程序直接在方法中使用了匿名内部类,也可以单独定义一个InvocationHandler的子类。
Collection collectionProxy = (Collection)constructor.newInstance(new InvocationHandler(){
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		return null;
	}
});
System.out.println(collectionProxy.toString());//输出null
collectionProxy.clear();
collectionProxy.size();//会报空指针异常,因为此时invoke()返回值为null

小练习四、创建动态代理类的实例(步骤:通过Proxy.newProxyInstance()直接获取代理实例)

Collection collectionProxy = (Collection)Proxy.newProxyInstance(
		Collection.class.getClassLoader(),
		new Class[]{Collection.class},
		new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
		});

System.out.println(collectionProxy.toString());//输出null
collectionProxy.clear();
collectionProxy.size();//会报空指针异常,因为此时invoke()返回值为null

通过上面的四个小练习,动态生成了实现Collection接口的代理类(也可以实现若干接口),生成的代理类有Collection接口中的所有方法(练习中演示)和一个接受InvocationHandler参数的构造方法。【InvocationHandler对象:调用处理程序】

思考:上面生成的动态代理类$Proxy0中的内部代码是什么样的?

class $Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
	//生成的Collection接口中的方法的运行原理
	int size()
	{
		return handler.invoke(this,this.getClass().getMethod("size"),null);
	}

	void clear(){
	handler.invoke(this,this.getClass().getMethod("clear"),null);
	}
	boolean add(Object obj){
		handler.invoke(this,this.getClass().getMethod("add"),obj);
	}
}

练习五、完成InvocationHandler对象的内部功能

Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
//本程序直接在方法中使用了匿名内部类,也可以单独定义一个InvocationHandler的子类。
Collection collectionProxy = (Collection)constructor.newInstance(new InvocationHandler(){
	ArrayList target = new ArrayList();
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		System.out.print("before "+method.getName()+">>>");
		Object obj = method.invoke(target, args);
		System.out.println("<<<after "+method.getName());
		return obj;
	}
});
collectionProxy.add("abc");
collectionProxy.add("123");//打印before add>>><<<after add
System.out.println(collectionProxy.toString());//输出before toString>>><<<after toString (换行) [abc, 123]
System.out.println(collectionProxy.size());//输出before size>>><<<after size (换行) 2

⑤ 动态代理的工作原理图

dynamic proxy priciple

练习六、将目标类和系统功能(切面代码)作为一个参数传递给InvocationHandler对象(本练习核心是getProxy())。

定义如下三个类:

Advice接口:约定有哪些系统功能,但不作具体实现

MyAdvice类:Advice的具体实现

ProxyTest类:测试类。(为了方便getProxy()方法也放在其中)

public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
	//还可以定义其他系统功能,这里从略
}
public class MyAdvice implements Advice {
	private long startTime;

	public void beforeMethod(Method method) {
		startTime = System.currentTimeMillis();
	}

	public void afterMethod(Method method) {
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName()+"'s run time:"+(endTime - startTime));
	}

}
public class ProxyTest {
	public static void main(String[] args) throws Exception{
		Collection collectionProxy = (Collection)getProxy(new ArrayList(),new MyAdvice());
		collectionProxy.add("abc");//add's run time:0
		collectionProxy.add("123");//add's run time:0
		System.out.println(collectionProxy);//toString's run time:0 (换行) [abc, 123]
		System.out.println(collectionProxy.size());//size's run time:0 (换行) 2
	}

	public static Object getProxy(final ArrayList target,final Advice advice) {
		Object collectionProxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler(){
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						advice.beforeMethod(method);
						Object obj = method.invoke(target, args);
						advice.afterMethod(method);
						return obj;
					}
				});
		return collectionProxy;
	}
}

留下一个回复

你的email不会被公开。