通俗易懂講解下代理模式
1.什麼是代理
In computer programming,the proxy pattern is a software design pattern. A proxy,in its most general form,is a class functioning as an interface to something else. The proxy could interface to anything: a network connection,a large object in memory,a file,or some other resource that is expensive or impossible to duplicate.
(所謂的代理者是指一個類別可以作為其它東西的介面。代理者可以作任何東西的介面:網路連線、儲存器中的大物件、檔案或其它昂貴或無法複製的資源。)
—— 引用自維基百科
維基百科所說的優點抽象,這裡我們簡單來講就是通過代理控制物件的訪問,可以詳細訪問某個物件的方法,在這個方法呼叫處理,或呼叫後處理。
2.代理的應用場景和分類
①代理應用場景
- 安全代理 可以遮蔽真實角色
- 遠端代理 遠端呼叫代理類RMI
- 延遲載入 先載入輕量級代理類,真正需要在載入真實
②代理的分類
- 靜態代理(靜態定義代理類)
- 動態代理(動態生成代理類)
- Jdk自帶動態代理
- Cglib 、javaassist(位元組碼操作庫)
3.具體實現步驟
業務場景描述:
某買家去看房,覺得采光和低端都不錯,決定買下這個房子。這個時候買家就找到中介公司,也就是我們的代理者,幫我們的買家處理售前售後工作,並安排好一切交付事項。這裡的一切都圍繞房子所展開,於是我們可以把房源(或者房地產商)當做是我們的介面,然後我們的中介機構代理相關事項,買家談妥後並不需要和房地產商有什麼直接的交涉便可一步到位。
①靜態代理
這裡我們根據房源,分別讓顧客和中介實現相關介面方法,把買家交給中介結構處理相關事項,然後呼叫相關方法即可。
/**
* 真實房源
*/
interface House {
void maifang();
}
/**
* 顧客
*/
class Customer implements House {
@Override
public void maifang() {
System.out.println("我是買家,終於可以買房了");
}
}
/**
* 中介
*/
class Proxy implements House {
// 代理物件
private Customer customer;
public Proxy(Customer customer) {
this.customer = customer;
}
@Override
public void maifang() {
System.out.println("我是中介,你買房開始交給我啦!");
customer.maifang();
System.out.println("我是中介,你買房結束啦...");
}
}
// 靜態代理
public class Simulation {
public static void main(String[] args) {
Customer customer = new Customer();
House house = new Proxy(customer);
house.maifang();
}
}
複製程式碼
輸出結果如下:
我是中介,你買房開始交給我啦!
我是買家,終於可以買房了
我是中介,你買房結束啦...
複製程式碼
②JDK動態代理
動態代理需要實現InvocationHandler介面,相比靜態代理而言有一個好處就是:不需要生成代理類,可擴充套件性強,方便後續的更改和操作。
/**
* 真實房源
*/
interface House {
void maifang();
}
/**
* 顧客
*/
class Customer implements House {
@Override
public void maifang() {
System.out.println("我是買家,終於可以買房了");
}
public void sayHello() {
System.out.println("sayHello");
}
}
/**
* 中介
* 動態代理
*/
class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object obj,Method method,Object[] args) throws Throwable {
System.out.println("動態代理:我是中介,你買房開始交給我啦!");
Object invoke = method.invoke(target,args);
System.out.println("我是中介,你買房結束啦...");
return invoke;
}
}
// 動態代理
public class Simulation {
public static void main(String[] args) {
Customer customer = new Customer();
JdkProxy jdkProxy = new JdkProxy(customer);
House house = (House)Proxy.newProxyInstance(customer.getClass().getClassLoader(),customer.getClass().getInterfaces(),jdkProxy);
house.maifang();
}
}
複製程式碼
輸出結果如下:
動態代理:我是中介,你買房結束啦...
複製程式碼
③CGLIB代理
CGLIB代理的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。除了CGLIB包,指令碼語言例如Groovy和BeanShell,也是使用ASM來生成java的位元組碼。當然不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉。
/**
* 真實房源
*/
interface House {
void maifang();
}
/**
* 顧客
*/
class Customer implements House {
@Override
public void maifang() {
System.out.println("我是買家,終於可以買房了");
}
public void sayHello() {
System.out.println("sayHello");
}
}
/**
* Cglib代理
*/
class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object create() {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(target.getClass().getClassLoader());
enhancer.setInterfaces(target.getClass().getInterfaces());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj,Object[] args,MethodProxy methodProxy) throws Throwable {
System.out.println("我是中介,你買房開始交給我啦!");
Object invokeSuper = methodProxy.invoke(target,你買房結束啦...");
return invokeSuper;
}
}
public class Simulation {
public static void main(String[] args) {
Customer customer = new Customer();
CglibProxy cglibProxy = new CglibProxy(customer);
House o = (House) cglibProxy.create();
o.maifang();
}
}
複製程式碼
輸出結果和上面的效果一樣,這裡不做重複展示,值得一提的CGLIB代理是使用Enhancer來處理相關的類和介面的實現的,這個地方是其獨有的特點。
4.CGLIB代理與JDK動態代理的區別
java動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。 ①如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP ②如果目標物件實現了介面,可以強制使用CGLIB實現AOP ③如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換