Java設計模式:Proxy(代理)模式

概念定義

代理模式是一種使用代理對象來執行目標對象的方法並在代理對象中加強目標對象方法的一種設計模式。html

使用代理模式的緣由有:java

  • 中介隔離做用:在某些狀況下,一個客戶類不想或者不能直接引用一個委託對象,而代理對象能夠在客戶類和委託對象之間起到中介的做用(代理類和委託類實現相同的接口)。以現實生活爲例,經紀人就是明星的代理,外界能夠經過聯繫經紀人來間接與明星溝通。
  • 開放封閉原則:能夠經過給代理類增長額外的功能來擴展委託類的功能,這樣只須要修改代理類而不須要再修改委託類,符合開閉原則。代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及過後對返回結果的處理等。代理類自己並不真正實現服務,而是同過調用委託類的相關方法,來提供特定的服務。使用代理模式,能夠在調用委託類業務功能的先後加入一些公共的服務(例如鑑權、計時、緩存、日誌、事務處理等),甚至修改委託類的業務功能。

代理能夠分爲靜態代理和動態代理,前者更接近代理模式的本質。程序員

  • 靜態代理是由程序員編寫代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就已肯定。
  • 動態代理是代理類的源碼是在程序運行期間由編譯器動態的生成(如JVM根據反射等機制生成代理類)。代理類和委託類的關係在程序運行時肯定。

應用場景

  • 須要修改或屏蔽一個或若干類的部分功能,複用另一部分功能,可以使用靜態代理。
  • 須要攔截一批類中的某些方法,在方法的先後插入一些公共的操做,可以使用動態代理。

示例代碼

本節根據場景和實現的不一樣,依次介紹靜態代理、JDK動態代理、Cglib動態代理(也稱子類代理)以及Spring AOP代理。spring

靜態代理

靜態代理:在程序運行前就已經存在代理類的字節碼文件。數據庫

示例代碼以下:apache

// 共同接口 //
public interface ISubject {
    void doAction();
    void byebye();
}

// 真實對象(委託類) //
public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
    @Override
    public void byebye() { System.out.println("Wave goodbye!"); }
}

// 代理對象(代理類) //
public class SubjectProxy implements ISubject {
    private ISubject subject;

    public SubjectProxy() {
        // RealSubject實例可根據環境變量、配置等建立不一樣類型的實例(多態)
        subject = new RealSubject(); // 此處僅簡單地new實例
    }

    @Override
    public void doAction() {
        System.out.println(">> doWhatever start"); // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        subject.doAction();
        System.out.println("doWhatever end <<");   // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
    }
    @Override
    public void byebye() {
        System.out.println("Say goodbye"); // 改變委託類行爲(例如實現數據庫鏈接池時避免close關閉鏈接)
    }
}

// 驗證代碼 //
public class StaticProxyDemo {
    public static void main(String[] args) {
        SubjectProxy subject = new SubjectProxy();
        subject.doAction();
        subject.byebye();
    }
}

執行結果以下:設計模式

>> doWhatever start
Real Action Here!
doWhatever end <<
Say goodbye

靜態代理的特色以下:緩存

  • 使用靜態代理時,一般客戶類不須要感知RealSubject。
  • 靜態代理的缺點:代理對象須要與目標對象實現同樣的接口,所以接口較多時須要定義和維護大量的代理類代碼。
  • 與適配器的差別:適配器一般考慮改變接口形態,而代理則不會也不能改變接口形態。
  • 與裝飾器的差別:被代理對象由代理對象建立,客戶端甚至不須要知道被代理類的存在;被裝飾對象則由客戶端建立並傳給裝飾對象,能夠層層嵌套,層層裝飾。代理模式經常使用於控制被代理對象的訪問,而裝飾模式是增長被裝飾者的功能。

JDK動態代理

JDK動態代理所用到的代理對象,在程序運行階段調用到代理類對象時才由JVM真正建立。JVM根據傳進來的業務實現類對象及方法名,在內存中動態地建立一個代理類的class文件並被字節碼引擎執行,而後經過該代理類對象進行方法調用。具體而言,每一個代理類的對象都會關聯一個表示內部處理邏輯的InvocationHandler接口的實現。當使用者調用代理對象所代理的接口中的方法時,這個調用的信息會被傳遞給InvocationHandler的invoke方法。mybatis

示例代碼以下:app

// 業務接口 //
public interface ISubject {
    void doAction();
}

// 業務實現類 //
public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
}
public class RealSubject2 implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action2 Here!"); }
}

// 動態代理類 //
public class SubjectJdkProxyHandler implements InvocationHandler {
    private Object realSubject;

    public SubjectJdkProxyHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">> doWhatever start"); // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        Object result = method.invoke(realSubject, args); // 執行目標對象方法
        System.out.println("doWhatever end <<");   // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        return result;
    }
}

// 驗證代碼 //
public class JdkProxyDemo {
    public static void main(String[] args) {
        ISubject subject = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {ISubject.class}, // 或RealSubject.class.getInterfaces()
                new SubjectJdkProxyHandler(new RealSubject())); // RealSubject必須實現Subject接口,不然沒法強轉後調用業務方法
        subject.doAction();

        // 使用同一個SubjectProxyHandler類,可代理不一樣的類型
        ISubject subject2 = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {ISubject.class}, new SubjectJdkProxyHandler(new RealSubject2())); // 可以使用工廠模式建立代理對象
        subject2.doAction();
    }
}

執行結果以下:

>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action2 Here!
doWhatever end <<

JDK動態代理的特色以下:

  • 經過實現InvocationHandler接口完成代理邏輯。
  • 經過反射代理方法,比較消耗系統性能,但能夠減小代理類的數量,使用更靈活。
  • 代理類必須實現接口

Cglib動態代理

Cglib(Code Generation Library)是個功能強大、高性能、開源的代碼生成包,它能夠爲沒有實現接口的類提供代理。具體而言,Cglib繼承被代理的類,覆寫其業務方法來實現代理。由於採用繼承機制,因此不能對final修飾的類進行代理。

示例代碼以下:

// 業務實現類 //
public class RealSubject {
    public void doAction() { System.out.println("Real Action Here!"); }
}

// 動態代理類(實現方法攔截器接口) //
// MethodInterceptor接口來自net.sf.cglib.proxy.MethodInterceptor或org.springframework.cglib.proxy.MethodInterceptor
public class SubjectMethodInterceptor implements MethodInterceptor {
    public Object createCglibProxy(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();  // 建立加強器,用來建立動態代理類
        enhancer.setSuperclass(targetClass); // 爲加強器指定要代理的業務類,即爲生成的代理類指定父類
        enhancer.setCallback(this);          // 設置回調(對於代理類上全部方法的調用,都會調用CallBack)
        return enhancer.create(); // 建立動態代理類對象並返回

        // 以上語句可簡化爲:return Enhancer.create(targetClass, this); //
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(">> doWhatever start"); // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("doWhatever end <<");   // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        return result;
    }
}

// 驗證代碼 //
public class CglibProxyDemo {
    // 還可以使用CGLib + JDK InvocationHandler接口實現動態代理
    public static Object createCglibProxyWithHandler(Class<?> targetClass) {
        MethodInterceptor interceptor = null;
        try {
            InvocationHandler invocationHandler = new SubjectJdkProxyHandler(targetClass.newInstance());
            interceptor = (Object o, Method method, Object[] objects,
                           MethodProxy methodProxy) -> invocationHandler.invoke(o, method, objects);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return Enhancer.create(targetClass, interceptor);
    }

    public static void main(String[] args) {
        RealSubject subject = (RealSubject) new SubjectMethodInterceptor().createCglibProxy(RealSubject.class);
        subject.doAction();

        RealSubject subject2 = (RealSubject) createCglibProxyWithHandler(RealSubject.class);
        subject2.doAction();
    }
}

執行結果以下:

>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action Here!
doWhatever end <<

Cglib動態代理的特色以下:

  • 須要引入Cglib的jar文件,但Spring的核心包內已包含Cglib功能,因此直接引入spring-core-xxx.jar便可。
  • Cglib動態代理雖然不須要接口信息,可是它攔截幷包裝被代理類的全部方法。
  • 委託類不能爲final,不然報錯java.lang.IllegalArgumentException: Cannot subclass final class xxx。
  • 不會攔截委託類中沒法重載的final/static方法,而是跳過此類方法只代理其餘方法。

Spring AOP動態代理

示例代碼以下:

public interface ISubject {
    void doAction();
}

public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
}

public class SubjectSpringAopInvoker implements MethodInterceptor { // 來自org.aopalliance.intercept.MethodInterceptor
    private RealSubject target;

    public SubjectSpringAopInvoker(RealSubject realSubject) {
        this.target = realSubject;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println(">> doWhatever start"); // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        Object result = methodInvocation.getMethod().invoke(this.target, methodInvocation.getArguments());
        System.out.println("doWhatever end <<");   // 擴展進行額外的功能操做(如鑑權、計時、日誌等)
        return result;
    }
}

public class SpringAopProxyDemo {
    public static void main(String[] args) {
        ISubject proxy = ProxyFactory.getProxy(ISubject.class, new SubjectSpringAopInvoker(new RealSubject()));
        proxy.doAction();
    }
}

執行結果以下:

>> doWhatever start
Real Action Here!
doWhatever end <<

關於Spring實現動態代理的詳細介紹,可參考《Spring學習總結(二)——靜態代理、JDK與CGLIB動態代理、AOP+IoC》一文。

業界實踐

  • org.apache.ibatis.binding.MapperProxy(mybatis-3.4.6.jar)
  • org.springframework.aop.framework.ProxyFactoryBean
相關文章
相關標籤/搜索