引入
代理模式的概念:
使用代理模式創建代理對象,讓代理對象控制目標對象的訪問,並且可以在不改變目標對象的情況下添加一些其他功能。
爲了更好的實現 高內聚,低耦合,我們通常不願意去修改已經寫好的類或方法,而有時候需要加上一些通用的功能,比如 打印日誌之類的。
簡單案例
實現用戶登錄時,打印出用戶登錄的時間(日誌)
靜態代理
目標類UserServiceImpl
public class UserServiceImpl implements UserService{
public boolean login(String userName,String password) {
if(userName.equals("sky") && password.equals("123")) {
System.out.println("sky登錄成功");
return true;
}else {
return false;
}
}
}
接口UserService
public interface UserService {
public boolean login(String userName, String password);
}
代理類UserProxy
public class UserProxy implements UserService{
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public boolean login(String userName, String password) {
// TODO Auto-generated method stub
System.out.println("sky"+new Date().toLocaleString()+"登錄");
return this.userService.login(userName, password);
}
}
測試類Test
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
UserProxy userProxy = new UserProxy();
//注入目標類
userProxy.setUserService(new UserServiceImpl());
userProxy.login("sky", "123");
}
}
運行結果
sky2020-4-10 21:15:01登錄
sky登錄成功
可見我們並沒有修改UserServiceImpl裏的login方法,但仍然實現了日誌的打印。
這裏從測試類簡單回溯一下:
首先我們new了一個代理對象userProxy,然後我們又new了一個目標對象並把它注入給了代理對象。
然後執行了代理對象的login方法。
再看代理類,他和目標類都實現了相同的接口,並且代理類在重寫login方法的時候return了目標類的login方法
,這就好比同一個方法重寫了兩次,一次是目標方法的實現,一個則是代理方法的“錦上添花”。
這樣既沒有破壞原方法,也給其添加了一些新功能。
但靜態代理也有很大的侷限性,就是目標函數太多時,要給每一個目標類都配上代理類,顯然是個大工程
此時動態代理就顯得方便很多
動態代理
動態代理是根據目標對象動態的生成代理對象。
目標類(UserServiceImpl)和接口(UserService)都和上述一樣
這裏直接給出動態代理對象生成器類LoggerHandler
其實就是日誌處理器,這樣一來,所有想實現日誌打印的都可以藉助此類動態生成自己的代理對象,並實現功能。
public class LoggerHandler implements InvocationHandler{
//可以接收對象(目標)
private Object delegate;
//代理對象的創建
public Object bind(Object delegate) {//獲得目標對象並給其創建對應的代理對象
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
}
/**
* method 你要調用的方法
* args 方法對應的參數
* resul 該方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
Object result = null;
System.out.println("方法名是:"+method.getName());
result = method.invoke(delegate, args);
//在代理類裏增加功能,,此處增加的 日誌 功能
System.out.println("日誌 : " + args[0] + " at " + new Date().toLocaleString() + "登錄");
return result;
}
}
測試類Test
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
LoggerHandler loggerHandler = new LoggerHandler();
//傳入目標對象,,要用共同的接口
UserService userService = (UserService) loggerHandler.bind(new UserServicelmpl());
userService.login("sky", "123");
}
}
運行結果
方法名是:login
sky登錄成功!
日誌 : sky at 2020-4-10 21:33:49登錄
同樣簡單回溯一下:
首先測試類(Test)還是先把日誌生成器(LoggerHandler)new出來一個對象。
然後還是類似地將目標對象注入(bind方法,可以說是綁定),但對應的代理對象是用接口(User Service)接收的。
再看LoggerHandler類
首先他實現了InvocationHandler接口
JDK1.3之後加入了實現動態代理的API
InvocationHandler接口
使用靜態方法Proxy.newProxyInstance()建立一個代理對象
利用invoke()方法來操作代理方法
這裏針對一下Proxy.newProxyInstance()方法
Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
先說第二個參數:目標對象.獲得類.接口加載器
看到這就不難理解爲什麼要用 接口 接收對應的代理對象了,我猜這裏面可能利用了反射機制(瞎猜的)
再看第二個參數:目標對象.獲得類.類加載器,,
接着看invoke方法裏的第二個參數 Method,,
Method不是反射裏常見的方法嗎?這難道?
於是點進了InvocationHandler接口,然後又懵逼的出來了。
然後在newProxyInstance方法裏找了一個間接的證據
菜雞(我):說實話,這真的沒搞懂,希望有會的大佬能告訴我一下,感謝。
關於反射的一些簡單理解可以參考一下這篇文章:結合實例理解反射
再看newProxyInstance的第三個參數,這裏是把自身傳過去了。
鑑於剛纔點進了InvocationHandler接口,所以有了自己的一點理解。
接口裏很空,很多註釋,還有一個空方法!(invoke)
因爲動態代理要服務於不同的目標對象,每個目標對象各有不同,也會出現不同的代理對象,
所以這裏的this應該是幫助目標對象更好的找到自己的代理對象吧。(猜測)
最後總結一下動態代理和靜態代理的優缺點:
動態代理解決了靜態代理難以複用的缺點,但在性能上有所折扣。