動態代理


在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直接從應用程式中脫離,而您的應用程式本身不需修改任何一行程式碼。