Java設計模式之代理模式(靜態代理和JDK、CGLib動態代理)以及應用場景


我做了個例子 ,需要可以下載源碼: 代理模式 

 1、前言:

Spring 的AOP 面向切面編程,是通過動態代理實現的, 由兩部分組成:(a) 如果有接口的話 通過 JDK 接口級別的代理 (b) 如果沒有接口的話,通過方法級別的代理 CGLib代理實現。 

2、靜態代理

所謂靜態就是在程序運行前就已經存在代理類的字節碼文件,代理類和委托類的關系在運行前就確定了。 

通過上面的代理模式描述我們可以知道,其目的就是為了控制對象引用,生活場景中我們以買車為例,如果我們要買一輛轎車必須通過汽車4S店,汽車4s店就是充當代理角色,其目的就是控制買車客戶的買車行為,必須通過汽車4S店才能從汽車廠商買一輛車。

1.)首先新建一個買車的接口

public interface IBuyCar {
    //買車
    void buyCar();
}

2.)聲明一個要買車的客戶,實現買車接口

public class Customer implements IBuyCar {

    private int cash;//購車款

    public int getCash() {
        return cash;
    }

    public void setCash(int cash) {
        this.cash = cash;
    }

    @Override
    public void buyCar() {
        Log.e("buyCar", "買一輛車花費了-->" + cash + "元");
    }
}

 

3.)聲明一個買車代理汽車4S店,同樣也實現買車接口,必須接受客戶下單

public class BuyCarProxy implements IBuyCar{
    private Customer customer;//接收買車客戶

    public BuyCarProxy(Customer customer){
        this.customer=customer;//接收買車客戶
    }

    @Override
    public void buyCar() {//實現為客戶買車
        customer.buyCar();
    }
}

 4.) 創建一個客戶端,模擬一次買車

  Customer customer=new Customer();
  customer.setCash(120000);
  BuyCarProxy buyCarProxy=new BuyCarProxy(customer);
  buyCarProxy.buyCar();

 

代理類和委托類要實現相同的接口,因為代理真正調用的還是委托類的方法。

5.)通過代理模式實現權限控制

  通過上面的例子,我們可能有個疑問,難道就不能直接去廠家買車嗎?當然可以,如果在使用場景中實現類能滿足要求時,我們當然可以直接實現類,但當實現類不能滿足要求,要擴展需求,根據開閉原則你又不能修改實現類代碼,這時你就用代理類。比如購買一輛車我們要對客戶進行一個購車款審核,如果符合條件就買車,不符合要求我們就告知客戶購車款不足。

 @Override
    public void buyCar() {//實現為客戶買車
        int cash=customer.getCash();
        if(cash<100000){
            Log.e("buyCar","你的錢不夠買一輛車");
            return;
        }
        customer.buyCar();
    }

 實現場景

Customer customer=new Customer();
customer.setCash(120000);
BuyCarProxy buyCarProxy=new BuyCarProxy(customer);
buyCarProxy.buyCar();

Customer customer1 =new Customer();
customer1.setCash(90000);
BuyCarProxy buyCarProxy1 =new BuyCarProxy(customer1);
buyCarProxy1.buyCar();

靜態代理優點:

  客戶端不必知道實現類(委托類)如何如何,只需要調用代理類即可。

缺點:

  • 代理類和委托類實現了相同的接口,代理類通過委托類實現了相同的方法。但這樣出現了大量的代碼重復。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也要實現這個方法。這顯然增加了代碼的復雜度。
  • 代理對象只服務於一種類型的對象,如果要服務多類型的對象,那就要對每種對象都進行代理。靜態代理子啊程序規模稍大是就無法勝任了。

3、動態代理機制:

以上講的都是代理模式的靜態實現,所謂靜態代理就是自己要為要代理的類寫一個代理類,或者用工具為其生成的代理類,總之,就是程序運行前就已經存在的編譯好的代理類,這樣有時候會覺得非常麻煩,也導致非常的不靈活,相比靜態代理,動態代理具有更強的靈活性,因為它不用在我們設計實現的時候就指定某一個代理類來代理哪一個被代理對象,我們可以把這種指定延遲到程序運行時由JVM來實現。

3.1 JDK代理——接口級別代理 

package Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//Created by zhengbinMac on 2017/2/19.
public class DynamicProxy implements InvocationHandler{
    private Object target;
    public DynamicProxy(Object target) { this.target = target; }
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        sayBefore();
        Object result = method.invoke(target, args);
        sayAfter();
        return result;
    }
    private void sayBefore() { System.out.println("before..."); }
    private void sayAfter() { System.out.println("after..."); }
}

JDK 提供的 Proxy 類的工廠方法 newProxyInstance 去動態地創建一個 Hello 接口的代理類。

Proxy.newProxyInstance:

參數:

  • loader - 定義代理類的類加載器
  • interfaces - 代理類要實現的接口列表
  • h - 指派方法調用的調用處理程序(每個代理實例都具有一個關聯的調用處理程序,調用代理實例的方法時,將對方法的調用指派到它的調用處理程序的 invoke 方法)

返回:

一個帶有代理類的指定調用處理程序的代理實例,它由指定類加載器定義,並實現指定的接口。

package Proxy;
//Created by zhengbinMac on 2017/2/19.
public class Test {
    public static void main(String[] args) {
        DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
        Hello hello = dynamicProxy.getProxy();
        hello.sayHello("JDK");
    }
}

 

再舉例:還是接着上面的例子

1.)首先我們要聲明一個動態代理類,實現InvocationHandler接口

public class DynamicProxy implements InvocationHandler {

    // 被代理類的實例
    Object obj;

    // 將被代理者的實例傳進動態代理類的構造函數中
    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    /**
     * 覆蓋InvocationHandler接口中的invoke()方法
     * 更重要的是,動態代理模式可以使得我們在不改變原來已有的代碼結構
     * 的情況下,對原來的“真實方法”進行擴展、增強其功能,並且可以達到
     * 控制被代理對象的行為,下面的before、after就是我們可以進行特殊
     * 代碼切入的擴展點了。
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        /*
         * before :doSomething();
         */
        Object result = method.invoke(this.obj, args);

        /*
         * after : doSomething();
         */
        return result;
    }
}

2.)具體實現 

 //我們要代理的真實對象
        Customer customer = new Customer();
        //我們要代理哪個真實對象,就將該對象傳進去,最后是通過該真實對象來調用其方法的
        InvocationHandler handler = new DynamicProxy(customer);

        /*
         * 通過Proxy的newProxyInstance方法來創建我們的代理對象,我們來看看其三個參數
         * 第一個參數 handler.getClass().getClassLoader() ,我們這里使用handler這個類的ClassLoader對象來加載我們的代理對象
         * 第二個參數customer.getClass().getInterfaces(),我們這里為代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了
         * 第三個參數handler, 我們這里將這個代理對象關聯到了上方的 InvocationHandler 這個對象上
         */
        IBuyCar buyCar = (IBuyCar) Proxy.newProxyInstance(handler.getClass().getClassLoader(), customer.getClass().getInterfaces(), handler);
        buyCar.buyCar();

 

動態態代理相比靜態代理,接口變了,動態代理類不需要改變,而靜態代理類不僅需要改變實現類,代理類也需要修改。

但如果要代理一個沒有接口的類,JDK 動態代理就用不上了,這就引出了 CGLib 代理。 

CGLib代理——方法級別代理

Spring、Hibernate 框架都是用了它,當然,spring兩個都用到了 (a) 用到了JDK的 接口動態代理 (b) 也用到了 CGLib代理,它是一個在運行期間動態生成字節碼的工具,也就是動態生成代理類。

package Proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//Created by zhengbinMac on 2017/2/19.
public class CGLibProxy implements MethodInterceptor {
    // 單例模式
    private static CGLibProxy instance = new CGLibProxy();
    private CGLibProxy() {}
    public static CGLibProxy getInstance () { return instance; }
    public <T> T getProxy(Class<T> cls) {
        return (T) Enhancer.create(cls, this);
    }
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        sayBefore();
        Object result = methodProxy.invokeSuper(obj, objects);
        sayAfter();
        return result;
    }
    private void sayBefore() { System.out.println("before..."); }
    private void sayAfter() { System.out.println("after..."); }
}

測試類

package Proxy;
//Created by zhengbinMac on 2017/2/19.
public class Test {
    public static void main(String[] args) {
        Hello helloCGLib = CGLibProxy.getInstance().getProxy(HelloImpl.class);
        helloCGLib.sayHello("CGLib");
    }
}

 

3.)動態代理好處

使用Java動態代理機制的好處:

1、減少編程的工作量:假如需要實現多種代理處理邏輯,只要寫多個代理處理器就可以了,無需每種方式都寫一個代理類。

2、系統擴展性和維護性增強,程序修改起來也方便多了(一般只要改代理處理器類就行了)。

代理模式的好處:

(1).職責清晰
真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過后期的代理完成一件完成事務,附帶的結果就是編程簡潔清晰。
(2).代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了中介的作用和保護了目標對象的作用。
(3).高擴展性

 

總結: 

Spring提供的實現動態代理有兩種方式,一個是被代理對象需要實現JDK提供的動態代理接口。通過cglib的jar包實現動態代理,該方法只需要對目標對象繼承即可

spring支持兩種方法,那么我們在使用spring進行動態代理時究竟使用的哪一種方法呢?spring優先支持實現接口的方式,如果沒有接口則使用cglib方式。

下面我們看一看這兩種方法有什么區別。 下面我們通過JDK實現動態代理,需求是如下:有一個服務層對象ServiceImpl 如下

public class ServiceImpl implements Service {

    @Override
    public void add() {
    System.out.println("add");
    }

    @Override
    public void delete() {
        System.out.println("delete");

    }

    @Override
    public void update() {
        System.out.println("update");

    }
}

 

他們有三個方法,增刪改。我們知道執行增刪改操作時需要開啟事務,提交事務。接下來我們通過動態代理的方式為這三個方法織如事務控制。以偽代碼的形式。因為JDK動態代理需要使用接口,那我們為他創建一個接口。

public interface Service {

    void add();

    void delete();

    void update();
}

 接下來我們使用ServiceImpl的代理工廠生成代理對象,創建代理工廠類ServiceProxyFactory如下

@Data
public
class ServiceProxyFactory implements InvocationHandler { private ServiceImpl si; public Service getInstance(){ Service newProxyInstance =(Service) Proxy.newProxyInstance(ServiceProxyFactory.class.getClassLoader(), ServiceImpl.class.getInterfaces(),this); return newProxyInstance; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("打開事務"); Object invoke =method.invoke(si,args); System.out.println("提交事務"); return invoke; } }

 

第一眼看上去可能有點混亂,一步一步來,既然代理工廠生成代理對象,我們首先創建一個getInstance方法返回代理對象。JDK提供了一個Proxy下的一個靜態方法newProxyInstance(),幫助我們生成代理對象。該方法需要傳入三個參數,

第一個參數為的類加載器, Java有三種類加載器,這里不做重點描述,提供任意一個類的class對象有一個方法getClassLoader()返回一個classLoader對象,源碼中該對象是被final修飾的,防止黑客修改類加載以達到不可告人的目的,sun公司在jvm中有着大量的代碼維護反射和類加載器,防止被壞人利用。第二個參數為Class<?>[] 類型數組,什么意思呢,這里可以傳入任意的Class類,那么如何獲取Class類數組呢,通過任意的class對象getInterfaces()方法可以獲得Class[],但是這里我們必須傳入需要被代理對象的class對象。為什么呢,前面說過需要的參數為Class<?>[],由於Class類實現了Type接口,在生成代理對象的過程中Class數組的泛型類型?會被替換成傳入的class類型的類型,就是說我傳入一個A.class.getInterfaces(),進入到底層時會變成Class<A>[]。因為我們這里必須傳入被代理對象的class的Class[]。第三個參數需要傳入一個執行器對象,該執行器必須符合InvocationHandler接口規范,就是實現這個接口的invoke方法。該方法同樣要求傳入三個參數,

參數一:局部變量:被代理對象實例參數二:Method類型對象,該參數有JDK的Proxy負責傳入,method為我們被代理對象的執行方法對象。參數三:Object[],它是被執行方法參數,在方法執行中參數由Proxy負責傳入至Object[]中。

那么第3個參數我們實質只需要傳入被代理對象實例即可。那么傳入被代理方法對象有兩種方法,一個是構造器傳入,另一個是set方法傳入,也可以使用spring注入。我們使用method對象調用invoke()方法,該方法需要兩個參數,第一個參數:全局變量的被代理對象實例,這里與全局變量被代理對象實例做一個區分幫助大家更清晰的理解。看圖中,被代理對象實例由set方法傳入全局變量si,然后si作為InvocationHandler的實現類下的invoke方法的參數method對象下的invoke方法參數傳入。 這個參數也就是invoke的調用對象。第二個參數就是被代理對象實例調用的方法參數,也就是上方的參數三Object[]。該invoke方法返回一個Object對象,因為我使用的自動生成變量所以這個Object對象名也是invoke,不好意思!最后我們將該對象invoke返回即可。這樣就可以創建一個代理對象了,我們使用動態代理的目的是在原編碼不變的基礎上對方法進行增強,請看下圖

  @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("打開事務");
        Object invoke =method.invoke(si,args);
        System.out.println("提交事務");
        return invoke;
    }

 該方法相當於對圖中三行代碼進行一個封裝,封裝成了一個方法。執行順序自上而下。下面看一下如果使用代理對象調用被代理對象的方法

public class mainTest {

    public static void main(String[] args) {     

        ServiceImpl serviceImpl=new ServiceImpl();

        ServiceProxyFactory spf=new ServiceProxyFactory();
        spf.setSi(serviceImpl);
        Service instance =spf.getInstance();
        instance.add();
    }
}

1先創建一個被代理對象實例,用於傳入代理對象工廠。

2然后創建代理對象工廠實例,為內部的被代理對象賦值。

3代理對象工廠實例調用getInstance方法生成代理對象

4代理對象調用增強后的方法

下面請看執行結果

打開事務
add
提交事務

 執行的是我們增強后的方法,這就說明代理對象的作用已經達到了。這就是JDK提供的動態代理。下面我們看看由第三方人員提供的拓展類庫cglib是如何實現動態代理的。

這就是cglib創建動態代理的代碼,具體就不詳細介紹了,只需了解即可。下面看執行代碼

結果還需要看嗎?

可以看到cglib的代理方式不需要傳入被代理對象實例即可完成動態代理。cglib和jdk代理有什么區別呢?jdk的代理對象與被代理對象實現了同一接口,底層通過clone()方法,將實現類的方法復制過來在按照invoke中的配置對方法進行增強。而cglib代理則是代理對象繼承了被代理對象的方法,然后自身進行增強。這兩種代理思想在外來的自動生成代碼方向會經常被使用,實現完全體代碼的復用性。以上就是springAOP的實現原理了。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM