java靜態代理與動態代理

        假設有一個User類,裏面有一個獲取用戶信息的方法,接口和實現如下:

//接口定義
public interface IUserService {
	void getUserInfo();
}

//接口實現
public class UserService implements IUserService{

	@Override
	public void getUserInfo() {
        //這個等待是後面測試用的,可忽略
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
		}
		System.out.println("獲取User信息");
	}

}

        當我們正常調用時,只需要實現這個類調用即可。這裏並沒有使用代理。

IUserService user=new UserService();
user.getUserInfo();

        假如現在有一個新需求,需要統計一下這個方法的運行時間。最簡單的辦法當然是直接在getUserInfo這個方法中增加時間統計代碼即可,但是如果是如下情況,該怎麼辦(不僅指這個小需求):

1、這個類被封在了一個jar包中(靜態代理)

2、不允許修改源代碼,保持原代碼的既有功能不變(靜態代理,裝飾器模式)

3、舊版代碼和新版代碼結構差異太大(靜態代理,適配器模式)

4、對所有代碼增加時間統計(動態代理)

        括號裏已標註了對應的解決方案。其實在學習設計模式的時候,就感覺裝飾器模式和適配器模式很類似,學完代理模式以後,感覺三個類似。。。

        從網上找了代理模式的含義:其目的就是爲其他對象提供一個代理以控制對某個對象的訪問。代理類負責爲委託類預處理消息,過濾消息並轉發消息,以及進行消息被委託類執行後的後續處理。

        個人理解:在不改變源文件的情況下,通過對原有功能的重寫(適配器)、修改(裝飾器)、增強(AOP)等方式實現新的目的。

        從上面列出的四點就可以看出,代理模式基本上就是要新寫一個類來代替這個類執行新功能了。還是從代碼看起。

public class UserServiceProxy implements IUserService{

	private IUserService user;
	
	public UserServiceProxy(IUserService user) {
		this.user=user;
	}
	@Override
	public void getUserInfo() {
		System.out.println("begin");
		long begin=System.currentTimeMillis();
		user.getUserInfo();
		System.out.println("end");
		System.out.println("一共運行了:"+(System.currentTimeMillis()-begin)+"ms");
		
	}

}

        只需要新建一個代理類實現同一個接口,把原始類傳進來作爲成員變量即可,調用方法如下:

IUserService user=new UserServiceProxy(new UserService());
user.getUserInfo();

        這樣就實現了不改變源文件的情況下,增強了原有功能。

        其實從這裏就能看出來,爲什麼說代理模式和裝飾器模式很像,JDK中一個很著名的裝飾器模式,就是IO流的處理了,我們經常是這麼用的,比如new BufferedReader(new FileReader(file));就是一層一層的封裝,每次的封裝都是支持前一層操作,然後再增加新的功能,這行代碼就是對文件讀取支持的基礎上,增加了緩衝區。

        但是這種方式很笨重,如果再來一個Company類,需要打印時間,那麼我們就需要再寫一個CompanyProxy類去實現,一定程度上,增加了代碼冗餘,並且效果還不如直接修改源代碼的好。所以就有了動態代理的概念,即在運行時動態獲取代理類對象,並對該對象進行增強。

        JDK動態代理的實現很簡單,實現InvocationHandler 接口,並重寫invoke方法,裏面的核心語句是 method.invoke(target, args);這句話就是在調用源代碼的方法。然後我們就可以在這句話前後,增加我們想要的內容,比如計算代碼執行時間等。

public class InvocationHandlerTest<T> implements InvocationHandler {
	T target;
	public InvocationHandlerTest(T target) {
		this.target=target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("begin");
		long begin=System.currentTimeMillis();
		Object result = method.invoke(target, args);
		System.out.println("一共運行了:"+(System.currentTimeMillis()-begin)+"ms");
		System.out.println("end");
		return result;
	}
}

        調用方法如下:

IUserService user=new UserService();
InvocationHandlerTest<IUserService> userInvocationHandler = new InvocationHandlerTest<IUserService>(user);
IUserService userProxy=(IUserService)Proxy.newProxyInstance(IUserService.class.getClassLoader(), user.getClass().getInterfaces(), userInvocationHandler);
userProxy.getUserInfo();

        核心是Proxy.newProxyInstance(IUserService.class.getClassLoader(), user.getClass().getInterfaces(), userInvocationHandler);三個參數分別是被代理對象的classloader,接口和代理對象的invocation。

        這樣就實現了一個動態代理,我們只需要修改這三個參數,就可以將所有的類進行動態代理了。這裏要注意的是,jdk的動態代理必須要求有接口,因爲它是基於接口實現的動態代理。如果想要不依賴接口實現動態代理,那麼可以採用cglib技術實現。

        至於Proxy.newProxyInstance是怎麼實現動態代理的,容我讀幾天源碼再寫吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章