Java動態代理原理及其簡單應用


概念

代理對象和被代理對象一般實現相同的接口,調用者與代理對象進行交互。代理的存在對於調用者來說是透明的,調用者看到的只是接口。代理對象則可以封裝一些內部的處理邏輯,如訪問控制、遠程通信、日志、緩存等。比如一個對象訪問代理就可以在普通的訪問機制之上添加緩存的支持。這種模式在RMI和EJB中都得到了廣泛的使用。傳統的代理模式的實現,需要在源代碼中添加一些附加的類。這些類一般是手寫或是通過工具來自動生成。
JDK 5引入的動態代理機制,允許開發人員在運行時刻動態的創建出代理類及其對象。在運行時刻,可以動態創建出一個實現了多個接口的代理類。每個代理類的對象都會關聯一個表示內部處理邏輯的InvocationHandler接 口的實現。當使用者調用了代理對象所代理的接口中的方法的時候,這個調用的信息會被傳遞給InvocationHandlerinvoke方法。在 invoke方法的參數中可以獲取到代理對象、方法對應的Method對象和調用的實際參數。invoke方法的返回值被返回給使用者。這種做法實際上相 當於對方法調用進行了攔截。熟悉AOP的人對這種使用模式應該不陌生。但是這種方式不需要依賴AspectJ等AOP框架。

原理

在java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class)
InvocationHandler
每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法:

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

參數的含義:

proxy:   指代我們所代理的那個真實對象
method:  指代的是我們所要調用真實對象的某個方法的Method對象
args:   指代的是調用真實對象某個方法時接受的參數

Proxy
Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:

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

參數的含義:

loader:   一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進行加載
interfaces:  一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
h: 一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上

簡單的例子

首先定義一個Hello接口

public interface Hello {
	public void helloCat();
	public void helloDog(String dog);
}

實現了該接口的實現類HelloImpl,也就是我們的真實對象

public class HelloImpl implements Hello {
	@Override
	public void helloCat() {
		System.out.println("hello Cat !");
	}
	@Override
	public void helloDog(String dog) {
		System.out.println("hello " + dog + "!");
	}
}

定義動態代理類,實現 InvocationHandler 這個接口。

public class DynamicProxy implements InvocationHandler {
	private Hello hello;
	public DynamicProxy(Hello hello){
		this.hello = hello;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//before
		System.out.println("before say hello");
		//當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
		Object object = method.invoke(hello, args);
		//after
		System.out.println("after say hello");
		return object;
	}
}

用代理實現功能

public class Client {
	public static void main(String[] args) {
		//代理對象
		Hello helloImpl = new HelloImpl();
		//將需要代理的對象傳進去,最后是需要該對象調用其方法的
		InvocationHandler handler = new DynamicProxy(helloImpl);
		/*
		* 通過Proxy的newProxyInstance方法來創建我們的代理對象,我們來看看其三個參數
		* 第一個參數 handler.getClass().getClassLoader() ,我們這里使用handler這個類的ClassLoader對象來加載我們的代理對象
		* 第二個參數realSubject.getClass().getInterfaces(),我們這里為代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了
		* 第三個參數handler, 我們這里將這個代理對象關聯到了上方的 InvocationHandler 這個對象上
		*/
		Hello helloProxy = (Hello)Proxy.newProxyInstance(handler.getClass().getClassLoader(), helloImpl
		    .getClass().getInterfaces(), handler);
		//看看這個代理對象的真實面目
		System.out.println(helloProxy.getClass().getName());
		helloProxy.helloCat();
		helloProxy.helloDog("小白");
	}
}

控制台輸出

com.sun.proxy.$Proxy0
before say hello
hello Cat !
after say hello
before say hello
hello 小白!
after say hello

代理的類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次調用 Proxy 的靜態方法創建動態代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。

應用

1.方法性能監測

獲取執行方法前后的系統時間,算出其執行時間。

long startTime = System.currentTimeMillis();
Object obj = method.invoke(proxied, args);
long endTime = System.currentTimeMillis();
System.out.println("Method " + method.getName() + " execution time: " + (endTime - startTime) * 1.0 / 1000 + "s");

2.日志管理

獲取日志需要的方法名和時間,並利用代理寫入。

public String beforeMethod(Method method) {
    return getFormatedTime() + " Method:" + method.getName() + " start running\r\n";
}
 
public String afterMethod(Method method) {
    return getFormatedTime() + " Method:" + method.getName() + " end running\r\n";
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    write(path, beforeMethod(method));
    Object object = method.invoke(proxied, args);
    write(path, afterMethod(method));
    return object;
}
 
public String getFormatedTime() {
    DateFormat formater = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    return formater.format(System.currentTimeMillis());
}
 
public void write(String path, String content) {
    FileWriter writer = null;
    try {
        writer = new FileWriter(new File(path), true);
        writer.write(content);
        writer.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(null != writer) {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.使用動態代理對注解進行處理

在實例中如何使用和處理注解。
假定有一個公司的雇員信息系統,從訪問控制的角度出發,對雇員的工資的更新只能由具有特定角色的用戶才能完成。考慮到訪問控制需求的普遍性,可以定義一個注解來讓開發人員方便的在代碼中聲明訪問控制權限。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles {
    String[] value();
}

下一步則是如何對注解進行處理,這里使用的Java的反射API並結合動態代理。下面是動態代理中的InvocationHandler接口的實現。

public class AccessInvocationHandler<T> implements InvocationHandler {
    final T accessObj;
    public AccessInvocationHandler(T accessObj) {
        this.accessObj = accessObj;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RequiredRoles annotation = method.getAnnotation(RequiredRoles.class); //通過反射API獲取注解
        if (annotation != null) {
            String[] roles = annotation.value();
            String role = AccessControl.getCurrentRole();
            if (!Arrays.asList(roles).contains(role)) {
                throw new AccessControlException("The user is not allowed to invoke this method.");
            }
        }
        return method.invoke(accessObj, args);
    } 
} 

在具體使用的時候,首先要通過Proxy.newProxyInstance方法創建一個EmployeeGateway的接口的代理類,使用該代理類來完成實際的操作。

相當於使用一個AOP切面對所有實現了這個接口(也就是追加了注解的類)實現切面控制。

總結

Java動態代理美中不足的是僅支持 interface 代理。因為那些動態生成的代理類都有一個共同的父類叫Proxy。Java 的繼承機制注定了這些動態代理類們無法實現對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。
雖然有缺點,但是Proxy設計的非常優美是毋庸置疑的,有時間一定要去看看源碼。
Java動態代理機制分析及擴展這篇文章寫得相當好,里面有些東西還沒有完全理解,標記下來,多讀幾遍。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM