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垃圾回收的通俗理解,關於類加載器和雙親委派機制講的也比較詳細。
-
動態代理的好處
- 可以使真實角色的操作更純粹!不用去關注一些公共的業務。
- 公共業務就交給代理角色,實現了業務的分工。
- 公共業務發生擴展時,方便集中的管理。
- 一個動態代理類代理的是一個接口,一般就是對應一類業務。
- 一個動態代理類可以代理多個類,只要是實現了同一個接口即可。
以上
感謝您花時間閱讀我的博客,以上就是我對代理模式的一些理解,若有不對之處,還望指正,期待與您交流。
本篇博文系原創,僅用於個人學習,轉載請注明出處。