ITEEDU

动态代理

在 JDK  1.3 之后加入了可协助开发动态代理功能的 API 等相关类别,您不必为特定物件与方法撰 写特定的代理物件,使用动态代理,可以使得一个处理者 (Handler)服务于各个物件,首先, 一个处理者的类别设计必须实作 java.lang.reflect.InvocationHandler  介面, 以实例来进行说明, 例如设计一个 LogHandler 类别:

•      LogHandler.java
package onlyfun.caterpillar;
import java.util.logging.*;

import java.lang.reflect.*;
public class LogHandler implements InvocationHandler {
	private Logger logger = Logger.getLogger(this.getClass().getName());
	private Object delegate;
	public Object bind(Object delegate) {
		this.delegate = delegate;
		return Proxy.newProxyInstance( delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		try {
			log("method starts..." + method);
			result = method.invoke(delegate, args);
			logger.log(Level.INFO, "method ends..." + method);
		} catch (Exception e){
			log(e.toString());
		}

		return result;
	}

	private void log(String message) {
		logger.log(Level.INFO, message);
	}
}

主要的概念是使用 Proxy.newProxyInstance()静态方法建立一个代理物件,建立代理物件时必须告 知所要代理的介面,之后您可以操作所 建立的代理物件,在每次操作时会呼叫 InvocationHandler 的 invoke()方法,invoke()方法会传入被代理物件的方法名称与执行 参数,实际上要执行的方法 交由 method.invoke(),您在 method.invoke()前后加上记录动作,method.invoke()传 回的物件是 实际方法执行过后的回传结果。

要实现动态代理,同样必须定义所要代理的介面,例如:

•      IHello.java
package onlyfun.caterpillar;
public interface IHello {
	public void hello(String name);
}

然后让实现商务逻辑的 HelloSpeaker 类别要实现 IHello 介面,例如:

•      HelloSpeaker.java

package onlyfun.caterpillar;

public class HelloSpeaker implements IHello {
	public void hello(String name) { 
    System.out.println("Hello, " + name);
	}
}

眼尖的您或许发现到了,这跟之前 从代理机制初探 AOP 中的IHello介面、HelloSpeaker是相同 的内容,在这边撰写出来是为了范例的完整呈现。接下来撰写一个测试的程式,您要使用 LogHandler的bind()方法来绑定被代理物件,如下所示:

•      ProxyDemo.java
package onlyfun.caterpillar;
public class ProxyDemo {
	public static void main(String[] args) { LogHandler logHandler?= new LogHandler();
		IHello helloProxy =
		(IHello) logHandler.bind(new HelloSpeaker());
		helloProxy.hello("Justin");
	}
}

回到 AOP 的议题上,这个例子与 AOP 有何关系?

如以上的例子中示范的,HelloSpeaker 本身的职责是显示招呼文字,却必须插入日志(Log)动作, 这使得 HelloSpeaker 的职责加重,在 AOP 的术语来说,日志的程式码横切(Cross‐cutting)入 HelloSpeaker 的程式执行流程中,日志这样的动作在 AOP 中称之为横切关切点(Cross‐cutting concern)。

使用代理物件将记录等与商务逻辑无关的动作或务提取出来,设计为为一个服务物件,像是之前 范例中示范的 HelloProxy 或是 LogHandler,这样的物件称之为切面(Aspect)。

AOP 中的 Aspect 所指的可以是像日志等这类的动作或服务,您将这些动作(Cross‐cutting concerns) 设计为通用、不介入特定业务物件的一个职责清楚的 Aspect 物件,这就是所谓的 Aspect‐oriented programming,缩写名词即为 AOP。

在好的设计之下,Aspect 可以独立于应用程式之外,在必要的时候,可以介入应用程式之中提供 服务,而不需要相关服务的时候,又可以将这些 Aspect 直接从应用程式中脱离,而您的应用程 式本身不需修改任何一行程式码。