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 執行效率更高。

代理模式優點

  • 代理模式能將代理對象與真實被調用的目標對象分離,降低了系統的耦合度,所以擴展性比較好。可以通過代理來增強被代理對象的功能。

代理模式缺點

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

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