背景
java 中的代理模式分為靜態(tài)代理、動態(tài)代理,今天 我們再次重溫下 定義及基本代碼邏輯,并加入實際業(yè)務中的一種應用場景。
靜態(tài)動態(tài)概念何來?從對象生成的時機而來,編譯器生成即靜態(tài)代理,運行期動態(tài)生成即動態(tài)代理。
1、靜態(tài)代理:編譯期間生成委托別人實現(xiàn)的代碼。
我們以老板和秘書的關(guān)系為例,整一段代碼如下:
#主題類
public interface Subject {
void meet();
}
#老板類
public class Boss implements Subject {
@Override
public void meet() {
System.out.println("Boss is meeting……");
}
}
#秘書類
public class Secretary implements Subject {
private Subject boss;
public Secretary(Boss boss) {
this.boss = boss;
}
@Override
public void meet() {
System.out.println("Before meeting you should make an appointment");
boss.meet();
System.out.println("After meeting you should not stay any longer");
}
}
#客戶端調(diào)用
public class Client {
public static void main(String[] args) {
Boss boss = new Boss();
Secretary secretary = new Secretary(boss);
secretary.meet();
}
}
秘書代理了老板的之前和之后的所有工作。老板只負責開會的時候過去就好了??赡苌罘矫嬉藏撠熈恕?/p>
所有的類 都是編譯期間生成,從上述例子可以看出,代理模式可以通過不修改被代理對象的前提下,實現(xiàn)其功能的增加和擴展。同時注意代理類跟被代理類需要實現(xiàn)同一個接口或者繼承同一個父類。對應上述例子可以理解為 老板和秘書必須在同一家公司,否則沒有什么權(quán)利和義務做這些。
2、動態(tài)代理:運行期間動態(tài)生成委托別人實現(xiàn)的代碼
與靜態(tài)代理的區(qū)別就是,秘書類 不再實現(xiàn)共同的接口,而是實現(xiàn)了一個java 反射包中的 InvocationHandler 類。代碼如下:
#Subject
public interface Subject {
void meet();
}
#Boss
public class Boss implements Subject {
@Override
public void meet() {
System.out.println("Boss is meeting now, please do not disturb");
}
}
#Secretary
public class Secretary implements InvocationHandler {
private Boss boss;
public Secretary(Boss boss) {
this.boss = boss;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before meeting you should make an appointment");
method.invoke(boss, args);
System.out.println("After meeting you should not stay any longer");
return null;
}
}
#Client
public class Client {
public static void main(String[] args) {
Boss boss = new Boss();
InvocationHandler handler = new Secretary(boss);
Subject dynamicProxy = (Subject) Proxy.newProxyInstance(Boss.class.getClassLoader(), Boss.class.getInterfaces(),
handler);
dynamicProxy.meet();
}
}
動態(tài)代理涉及到一個非常重要的類 Proxy ,正是通過 Proxy 的靜態(tài)方法 newProxyInstance 才會動態(tài)創(chuàng)建代理。
Proxy
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)下面講解它的 3 個參數(shù)意義。
- loader 自然是類加載器
- interfaces 代碼要用來代理的接口
- h 一個 InvocationHandler 對象
其中 interfaces 通過 實現(xiàn)類的 class.getInterfaces() 方法獲得,也可以直接 new class<?>[]{Interface.class},這點很重要?。。。。『竺鏁玫?/p>
InvocationHandler
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }InvocationHandler 是一個接口,官方文檔解釋說,每個代理的實例都有一個與之關(guān)聯(lián)的 InvocationHandler 實現(xiàn)類,如果代理的方法被調(diào)用,那么代理便會通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實現(xiàn)類,由它決定處理。
需要深究其中代碼的 可以查看 jdk 源碼。
3、代理的作用
日志記錄,性能統(tǒng)計,安全控制,事務處理,異常處理等等。
4、作者實際項目中的一種應用
業(yè)務場景:
項目中的 RPC 調(diào)用,需要通過 HTTP 方式調(diào)用,通過 代理 引入的接口,來反射出 方法的 參數(shù)類型及 實際入?yún)?,從?支持 http 的調(diào)用。
怎么會有這么操蛋的方式,還是 由于公司技術(shù)棧太亂,太爛,各種惡心的方式調(diào)用,哎,不討論原因 既然出現(xiàn)這種情況,就想辦法實現(xiàn)把。
對于不同類型的業(yè)務功能做代理 其實 InvocationHandler 中 invoke 的實現(xiàn)都不相同,但是都是為了完成某一類型的業(yè)務功能。
HTTP 對遠程方法的調(diào)用,只要反射出 方法簽名以及方法調(diào)用的實際參數(shù)就可以了。
talk is cheep , show me the code !!!
public class ProxyHandler implements InvocationHandler {
private Class<?> proxyInterface;
public ProxyHandler(Class<?> clazz) {
this.proxyInterface = clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法 : " + method.getName());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
for (Object arg : args) {
System.out.println(arg);
}
//component request method and params
//invoke remote service......
return null;
}
}
public class ProxyFactory {
private ProxyFactory() {
}
public static <T> T getProxy(Class<?> clazz) {
ProxyHandler handler = new ProxyHandler(clazz);
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, handler);
}
}
public class Client {
public static void main(String[] args) {
PolicyService service = (PolicyService) ProxyFactory.getProxy(PolicyService.class);
service.savePolicy("88888", "simon");
UserService userService = (UserService) ProxyFactory.getProxy(UserService.class);
userService.save("candy", 9999L);
userService.save("xiaolong", 888);
}
}
接口定義如下:
public interface UserService {
void save(String userName, Long userId);
void save(String userName, Integer userId);
}
public interface PolicyService {
void savePolicy(String policyNo, String insurantName);
}
整體思路就是:通過代理 來實現(xiàn)接口的實例化,然后在 invoke 方法中 對方法名稱 方法參數(shù)進行攔截,最后實現(xiàn)遠程的 HTTP 調(diào)用。
附:代理模式姐妹篇