23天設計模式之代理模式
文章簡介
《23天設計模式之代理模式》是在最近學習反射與註解時,在反射中有關Proxy類的知識,也就順帶複習一下代理模式,總結部落格。
代理模式
-
為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。
-
組成:
-
抽象角色:通過介面或抽象類宣告真實角色實現的業務方法。
-
代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。
-
真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色呼叫。
-
-
舉個例子:
- 比如 “租房子”、“租客”、“中介”就分別代表 “抽象角色”、“真實角色”、“代理角色”。
- “租客”與“中介”都要實現“租房子”介面。
- 將“租客”注入到“中介”中,由“中介”呼叫“租客”的實現方法。
-
代理分為靜態代理和動態代理。
- 靜態代理:手動生產代理類。
- 動態代理:自動生成代理類。
靜態代理
直接上程式碼:
- 抽象角色
// 租房子 - 抽象角色,用介面或抽象類表示,裡面放真實角色要實現的業務方法
public interface Rent {
void rent();
}
- 真實角色
// 租客 - 真實角色
public class Tenant implements Rent {
private String name;
public Tenant(String name) {
this.name = name;
}
@Override
public void rent() {
System.out.println("租客:" + name + " 租到房子了");
}
}
- 代理角色
// 靜態代理類 - 代理角色
public class StaticProxy implements Rent{
// 將被代理角色注入進來
private Tenant tenant;
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public void beforeRent() {
System.out.println("租房前,中介帶租客看房");
}
public void afterRent() {
System.out.println("租房後,中介收取中介費和房租");
}
@Override
public void rent() {
beforeRent();
tenant.rent(); // 執行真實角色的代理方法,租房前後可以插入其它業務程式碼。
afterRent();
}
}
- 測試
public class Test {
public static void main(String[] args) {
Tenant tenant = new Tenant("孤影");
StaticProxy proxy = new StaticProxy();
proxy.setTenant(tenant);
proxy.rent(); // 可以發現,在這裡已經是代理類在執行代理方法, 而不是Tenant類在執行
}
}
// 輸出
租房前,中介帶租客看房
租客:孤影 租到房子了
租房後,中介收取中介費和房租
動態代理
- 動態代理分為兩大類:基於介面的動態代理,基於類的動態代理。
- 基於介面:jdk動態代理。
- 基於類:cglib。
- java位元組碼實現:javassist。
- 這裡我們主要了解jdk動態代理。
- 面試時問道我們動態代理是怎麼實現的?我們通常只是回答通過反射實現的,但是具體的卻說不出東西來,因此一定了解Proxy類和InvocationHandler介面。
接下來上程式碼:
- 抽象角色和真實角色與上文相同。
- 定義一個InvocationHandler,以此來呼叫代理的方法。
// 動態代理類處理程式 - 通過此類獲取代理角色,得到代理類後,使用代理類執行代理方法
public class DynamicProxyHandler implements InvocationHandler {
// 注入被代理物件
private Object target;
private String before;
private String after;
public void setTarget(Object target) {
this.target = target;
}
public DynamicProxyHandler(String before, String after) {
this.before = before;
this.after = after;
}
// 生成代理角色
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
public void beforeInvoke(String s) {
System.out.println(s);
}
public void afterInvoke(String s) {
System.out.println(s);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke(before);
Object result = method.invoke(target, args);// 通過反射執行代理方法,前後可插入其它業務程式碼
afterInvoke(after);
return result;
}
}
- 測試
public class Test {
public static void main(String[] args) {
Tenant tenant = new Tenant("孤影");
DynamicProxyHandler handler = new DynamicProxyHandler("租房前,中介帶租客看房", "租房後,中介收取中介費和房租");
handler.setTarget(tenant);
Object proxy = handler.getProxy();
if (proxy instanceof Rent) { // 可以看到,生成的代理類仍然是抽象角色的一個例項
((Rent) proxy).rent();
}
}
}
// 輸出
租房前,中介帶租客看房
租客:孤影 租到房子了
租房後,中介收取中介費和房租
-
疑惑:
-
有同學可能會疑惑生成代理角色時使用的
this.getClass().getClassLoader()
,為什麼不是target.getClass().getClassLoader()
? -
其實二者獲取的類載入器都是同一個AppClassLoader。
-
通過列印測試確實是同一個類載入器:
// 生成代理角色 public Object getProxy() { System.out.println(this.getClass().getClassLoader()); System.out.println(target.getClass().getClassLoader()); return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } // 輸出 sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2
-
關於類載入器要了解更多請閱讀我的另一篇文章 - 簡單談談對GC垃圾回收的通俗理解,關於類載入器和雙親委派機制講的也比較詳細。
-
動態代理的好處
- 可以使真實角色的操作更純粹!不用去關注一些公共的業務。
- 公共業務就交給代理角色,實現了業務的分工。
- 公共業務發生擴充套件時,方便集中的管理。
- 一個動態代理類代理的是一個介面,一般就是對應一類業務。
- 一個動態代理類可以代理多個類,只要是實現了同一個介面即可。
以上
感謝您花時間閱讀我的部落格,以上就是我對代理模式的一些理解,若有不對之處,還望指正,期待與您交流。
本篇博文系原創,僅用於個人學習,轉載請註明出處。