ProxyPattern- 代理模式

語言: CN / TW / HK

代理模式

代理模式(Proxy Pattern):一般是指一個物件為另一個物件提供一種代理,從而通過代理物件來控制非代理物件的訪問,代理物件在客戶端和目標物件之間起到中介作用。

代理模式屬於結構型設計模式,可以分為靜態代理和動態代理兩種型別,而動態代理中又分為 JDK 動態代理和 CGLIB 代理兩種。

1. 靜態代理模式

標準的靜態代理模式需要定義一個介面,然後代理物件與被代理物件都需要實現標準介面,並重寫標準介面中定義的方法,被代理物件本身需要實現真正的業務邏輯,而代理物件中一般是在呼叫被代理物件的前後新增一些其它邏輯處理。

示例:我們以買火車票來舉例說明。

  • 標準介面 Travel.java

package cn.liangyy.proxy.staticproxy;
/** * 標準介面 */public interface Travel { /** * 買火車票 */ void buyTrainticket();}

複製程式碼

  • 被代理物件 TravelPerson.java

package cn.liangyy.proxy.staticproxy;
/** * 被代理物件 */public class TravelPerson implements Travel { /** * 買火車票 */ @Override public void buyTrainticket() { System.out.println("西安到成都"); System.out.println("早上10:00出發"); }}

複製程式碼

  • 代理類 TravelAgency.java

package cn.liangyy.proxy.staticproxy;
/** * 代理類 */public class TravelAgency implements Travel { private TravelPerson travelPerson; //被代理物件
public TravelAgency(TravelPerson travelPerson) { this.travelPerson = travelPerson; }
/** * 買火車票 */ @Override public void buyTrainticket() { before(); //呼叫被代理物件的原方法 this.travelPerson.buyTrainticket(); after(); }
private void before(){ System.out.println("付定金"); }
private void after(){ System.out.println("付尾款"); }}

複製程式碼

  • 測試 TestStaticProxy.java

package cn.liangyy.proxy.staticproxy;
/** * 靜態代理模式-測試 */public class TestStaticProxy { public static void main(String[] args) { TravelAgency travelAgency = new TravelAgency(new TravelPerson()); travelAgency.buyTrainticket(); }}

複製程式碼

這就是一個靜態代理的實現方式,可以看到還是非常方便的,但是靜態代理卻有其侷限性:

代理物件需要顯示的宣告被代理物件,如果說後面想要修改代理物件,則需要修改原始碼,不符合開閉原則。被代理物件如果新增了其它方法(介面新增了方法),那麼代理物件也需要同步修改,不便於後期維護。所以為了解決靜態代理的侷限性,就有了動態代理。

2. 動態代理模式

2.1 JDK 動態代理

JDK 動態代理是 JDK 內建的一種動態代理的實現方式,使用 JDK 動態代理必須滿足兩個條件:

  • 定義的代理物件必須要實現 java.lang.reflect.InvocationHandle 介面

  • 被代理物件必須要顯示的實現至少一個介面

示例:

  • 標準介面 Travel.java

package cn.liangyy.proxy.jdkproxy;
/** * 標準介面 */public interface Travel { /** * 買火車票 */ void buyTrainticket();}

複製程式碼

  • 被代理物件 JdkTravelPerson.java

package cn.liangyy.proxy.jdkproxy;
/** * 被代理類 */public class JdkTravelPerson implements Travel { /** * 買火車票 */ @Override public void buyTrainticket() { System.out.println("西安到成都"); System.out.println("早上10:00出發"); }}

複製程式碼

  • 代理類 JdkTravelAgency.java

package cn.liangyy.proxy.jdkproxy;
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
/** * 代理類 */public class JdkTravelAgency implements InvocationHandler { //被代理物件,即示例中的 JdkTravelPerson private Object target;
//動態獲取代理物件 public Object getInstance(Object target){ //target就是被代理物件 this.target = target; Class<?> clazz = target.getClass(); //建立並返回代理物件 return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target,args); after(); return obj; }
private void before(){ System.out.println("付定金"); }
private void after(){ System.out.println("付尾款"); }}

複製程式碼

  • 測試 TestJdkProxy.java

package cn.liangyy.proxy.jdkproxy;
/** * JDK動態代理模式-測試 */public class TestJdkProxy { public static void main(String[] args) { Travel travel = (Travel) new JdkTravelAgency().getInstance(new JdkTravelPerson()); travel.buyTrainticket(); }}

複製程式碼

2.2 CGLIB 動態代理

CGLIB 是通過繼承被代理物件來實現,和 JDK 動態代理需要實現 InvocationHandler 介面一樣,CGLIB 也要求代理物件必須要實現 MethodInterceptor 介面,並重寫其唯一的方法 intercept。

CGLIB jar包下載地址

  • 這裡注意:我們建議需要下載 cglib-nodep-3.3.0.jar ,然後將此 jar 包匯入專案中就可以了。如果下載的是 cglib-3.3.0.jar ,那麼還需要下載 asm.jar ,因為 CGLIB 在執行期間是使用 ASM 框架寫 Class 位元組碼的。

示例:

  • 被代理物件 CglibTravelPerson.java

package cn.liangyy.proxy.cglibproxy;
/** * 被代理物件類 */public class CglibTravelPerson { public void buyTrainticket() { System.out.println("cglib:西安到成都"); System.out.println("cglib:早上9:00出發"); }}

複製程式碼

  • 代理物件 CglibTravelAgency.java ,實現 MethodInterceptor 介面

package cn.liangyy.proxy.cglibproxy;
import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * 代理物件 */public class CglibTravelAgency implements MethodInterceptor { public Object getInstance(Class<?> clazz){ Enhancer enhancer = new Enhancer();//相當於 JDK 動態代理中的 Proxy 類 enhancer.setSuperclass(clazz);//設定為即將生成的代理類的父類 enhancer.setCallback(this);//設定回撥物件 return enhancer.create();//相當於JDK動態代理的 Proxy.newProxyInstance 方法,生成新的位元組碼檔案,並載入到 JVM 中 }

/** * * @param o - CBLIG 生成的代理物件 * @param method - 被代理物件中被攔截的方法 * @param objects - 方法中的引數 * @param methodProxy - 代理物件中對應的方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o,objects); after(); return obj; }
private void before() { System.out.println("付定金"); }
private void after() { System.out.println("付尾款"); }}

複製程式碼

  • 測試 TestCglibProxy.java

package cn.liangyy.proxy.cglibproxy;
/** * cglib動態代理模式-測試 */public class TestCglibProxy { public static void main(String[] args){ CglibTravelPerson cglibTravelPerson = (CglibTravelPerson) new CglibTravelAgency().getInstance(CglibTravelPerson.class); cglibTravelPerson.buyTrainticket(); }}

複製程式碼

JDK 和 CGLIB 動態代理對比

  • JDK 動態代理是通過介面來實現,CGLIB 則是通過繼承被代理物件來實現,也就是說如果使用 JDK 動態代理,則要求被代理物件一定要有介面;而如果使用 CGLIB 動態代理,因為是通過繼承來實現,所以無法代理 final 和 private 修飾的方法。

  • JDK 和 CGLIB 都是在執行期生成位元組碼,JDK 是直接寫 Class 位元組碼,CGLib 使用 ASM 框架寫 Class 位元組碼,CGLIB 代理實現更復雜,生成代理類的效率比 JDK 代理低,但是因為 JDK 呼叫代理方法,是通過反射機制呼叫,CGLib 是通過 FastClass 機制直接呼叫方法,故而 CGLib 執行效率更高。

代理模式優點

  • 代理模式能將代理物件與真實被呼叫的目標物件分離,降低了系統的耦合度,所以擴充套件性比較好。可以通過代理來增強被代理物件的功能。

代理模式缺點

  • 設計模式通用缺點,代理模式也會造成系統設計中類的數量增加。

  • 通過代理來呼叫時,請求速度會變慢,對系統性能有一定影響。