1. 程式人生 > >Spring AOP的實現——動態代理機制

Spring AOP的實現——動態代理機制

在java的動態代理中,有兩個重要的類或者介面,一個是InvocationHandler(Interface)、另一個是Proxy(Class),這一個類和介面是實現動態代理所必須的。

InvocationHandler:每一個動態代理類都必須實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的invoke方法來進行呼叫。

我們來看看invoke這個方法:

<span style="font-size:14px;">Object invoke(Object proxy,Method method,Object[] args)</span>
proxy:指代我們所代理的真實物件

method:指代的是我們所要呼叫真實物件的某個方法的Method物件

args:指代的是呼叫真實物件某個方法時接收的引數

Proxy:這個類的作用就是用來動態的建立一個代理物件的類,其接受三個引數

<span style="font-size:14px;">public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException</span>
loader:一個ClassLoader物件,定義了由哪個ClassLoader物件來生成的代理物件進行載入

interfaces:一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了。

h:一個InvocationHandler物件,表示的是當我這個動態代理在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上

下面我們通過例項看看動態代理模式是什麼:

首先,定義一個Subject介面,並且定義兩個方法:

<span style="font-size:14px;">public interface Subject {

	public void rent();
	public void hello(String str);
}
</span>
接著,定義一個類來實現上面的介面,這個類就是我們的真實物件:
<span style="font-size:14px;">public class RealSubject implements Subject{

	@Override
	public void rent() {
		System.out.println("I want rent my house");
	}

	@Override
	public void hello(String str) {
		System.out.println("hello:"+str);
	}

}
</span>
下一步,定義動態代理類:
<span style="font-size:14px;">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Dynamicproxy implements InvocationHandler{
	
	//這就是我們代理的真實物件
	private Object subject;
	
	//構造方法,給我們要代理的真實物件賦值
	public Dynamicproxy(Object subject){
		this.subject = subject;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//在代理真實物件前我們可以新增一些自己的操作
		System.out.println("before rent house");
		System.out.println("Method:"+method);
		
		//當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
		method.invoke(subject, args);
		
		//在代理真實物件後我們可以新增一些自己的操作
		System.out.println("after rent house");
		return null;
	}

}</span>
最後,是Client類:
<span style="font-size:14px;">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {

	public static void main(String[] args) {
	
		//我們要代理的真實物件
		Subject realSubject = new RealSubject();
		
		//我們要代理哪個真實物件,就將該物件傳進去,最後通過該真實的物件呼叫該方法
		InvocationHandler handler = new Dynamicproxy(realSubject);
		
		Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
		System.out.println(subject.getClass().getName());
		subject.rent();
		subject.hello("world");
	}
}
</span>
控制檯輸出如下:

com.sun.proxy.$Proxy0
before rent house
Method:public abstract void com.by.proxyTest.Subject.rent()
I want rent my house
after rent house
before rent house
Method:public abstract void com.by.proxyTest.Subject.hello(java.lang.String)
hello:world
after rent house

首先,我們看看這條語句:

<span style="font-size:14px;">Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler); 
</span>
為什麼我們這裡可以將其轉化為Subject型別的物件?

原因在於newProxyInstance這個方法的第二個引數上,我們給代理物件提供什麼介面,那麼我們這個代理物件就會實現這個介面,這個時候我們可以將這個代理物件強制轉換成Subject型別。

使用Proxy.newProxyInstance()建立的代理物件是在JVM上執行時動態產生的一個物件,它並不是InvocationHandler型別,也不是我們定義的那組介面的型別,而是在執行時動態產生的一個物件。並且命名方式是以$開頭的,proxy中最後一個數字表示物件的標號。

subject.rent();
subject.hello("world");
這裡是通過物件來呼叫實現的那種介面中的方法,這個時候程式就會跳轉到由這個代理物件關聯到的handler中的invoke方法去執行,而我們這個handler物件又接受了一個RealSubject型別的引數,表示我要代理的就是這個真實物件,所以此時就會呼叫handler中的invoke方法去執行:
<span style="font-size:14px;">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Dynamicproxy implements InvocationHandler{
	
	//這就是我們代理的真實物件
	private Object subject;
	
	//構造方法,給我們要代理的真實物件賦值
	public Dynamicproxy(Object subject){
		this.subject = subject;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//在代理真實物件前我們可以新增一些自己的操作
		System.out.println("before rent house");
		System.out.println("Method:"+method);
		
		//當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫(通過煩反射執行某個類的某方法)
		method.invoke(subject, args);
		
		//在代理真實物件後我們可以新增一些自己的操作
		System.out.println("after rent house");
		return null;
	}

}
</span>
我們看到,在真正通過代理物件來呼叫真實物件的方法的時候,我們可以在該方法前後新增自己的一些操作,同時我們看到我們這個method物件是這樣:
<span style="font-family:Microsoft YaHei;font-size:14px;">public abstact void com.xiaoluo.dynamicproxy.Subject.rent()
public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)</span>
正好就是我們的Subject介面中的兩個方法,這也就證明了當我通過代理物件來呼叫方法的時候,起實際就是委託由其關聯到的handler物件的invoke方法中來呼叫,並不是自己來真實呼叫,而是通過代理的方式來呼叫。

動態代理的作用:

主要用來做方法的增強,讓你可以在不修改原始碼的情況下,增強一些方法,在方法執行前後做任何你想做的事情(甚至根本不去執行這個方法),因為在InvocationHandler的invoke這個方法中,你可以直接獲取正在呼叫方法對應的Method物件,具體應用,比如,新增日誌,做事物控制等。