天天看點

Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

目錄

一、代理模式簡介

1、代理模式的定義

2、組成

3、優缺點

二、靜态代理

1、靜态代理的執行個體

 2、靜态代理的優缺點

三、動态代理

(一)jdk動态代理模式

>1)模式要點分析

>2)反射執行個體

>3)動态代理源碼分析

>4)動态代理執行個體

>5)InvocationHandler分析

>6)總結下動态代理的流程:

(二)cglib的動态代理

       執行個體1:

       執行個體2:

最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

一、代理模式簡介

1、代理模式的定義

為其他對象提供一種代理以控制對這個對象的通路。在某些情況下,一個對象不适合或者不能直接引用另一個對象,而代理對象可以在用戶端和目标對象之間起到中介的作用。

2、組成

抽象角色:通過接口或抽象類聲明真實角色實作的業務方法。

代理角色:實作抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實作抽象方法,并可以附加自己的操作。

真實角色:實作抽象角色,定義真實角色所要實作的業務邏輯,供代理角色調用。

3、優缺點

優點: 1、職責清晰。 2、高擴充性。 3、智能化。

缺點: 1、由于在用戶端和真實主題之間增加了代理對象,是以有些類型的代理模式可能會造成請求的處理速度變慢。 2、實作代理模式需要額外的工作,有些代理模式的實作非常複雜。

使用場景:按職責來劃分,通常有以下使用場景: 1、遠端代理。 2、虛拟代理。 3、Copy-on-Write 代理。 4、保護(Protect or Access)代理。 5、Cache代理。 6、防火牆(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事項: 1、和擴充卡模式的差別:擴充卡模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口。 2、和裝飾者模式的差別:裝飾者模式為了增強功能,而代理模式是為了加以控制。

二、靜态代理

靜态代理是由程式員建立或工具生成代理類的源碼,再編譯代理類。

所謂靜态也就是在程式運作前就已經存在代理類的位元組碼檔案,代理類和委托類的關系在運作前就确定了。

 一句話,自己手寫代理類就是靜态代理。

--. 靜态代理模式類圖如下:

Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

被代理對象RealSubject負責執行相關業務邏輯,不同的被代理對象處理不同的業務,而代理對象隻需要通路這些業務方法就行,不需要處理具體業務邏輯,但是針對某一類共同問題的的處理,就可以移到代理對象裡來實作對方法的增強,由于代理模式功能增強這一塊在實際開發中尤為突出,是以後面的例子也會稍微針對一下。

1、靜态代理的執行個體

  • Subject:定義一個普通的接口方法
public interface Moveable {
    void move();
}
           
  • RealSubject:實作該接口方法
public class Car implements Moveable {

    @Override
    public void move() {
        System.out.println("汽車行駛中....");
    }

}
           
  • ProxySubject: 調用RealSubject的方法,并在調用前後進行增強處理。
public class CarProxy implements Moveable{
    private Moveable move;
    
    @Override
    public void move() {
        if(move==null){
            move = new Car();
        }
        System.out.println("開始記錄日志:");
        move.move();
        System.out.println("記錄日志結束!");
         
    }
}
           
  • 測試方法:實際上是調用ProxySubject的move()方法。
public static void main(String[] args) throws Exception {
        Moveable m =new CarProxy();
        m.move();
    }
           

 2、靜态代理的優缺點

優點:

業務類隻需要關注業務邏輯本身,保證了業務類的重用性。這是代理模式的共有優點。

缺點:

1)代理對象的一個接口隻服務于一種類型的對象,如果要代理的類型很多,勢必要為每一種類型的方法都進行代理,靜态代理在程式規模稍大時就無法勝任了。

  • 比如Car類的move()方法需要記錄日志,如果還有汽車,火車,自行車類的move()方法也需要記錄日志,我們都要一個個的去為它們生成代理類,太麻煩了。

2)如果接口增加一個方法,除了所有實作類需要實作這個方法外,所有代理類也需要實作此方法。顯而易見,增加了代碼維護的複雜度。 

三、動态代理

動态代理有jdk動态代理模式和cglib代理模式。JDK的動态代理隻能代理實作了接口的類, 沒有實作接口的類不能實作動态代理;而cglib的動态代理是通過繼承實作的,是以如果某個類被标記為final,那麼它是無法使用CGLIB做動态代理的。

(如果讀者隻想知道動态代理如何使用,可直接閱讀 >4)動态代理執行個體)

(一)jdk動态代理模式

》備注:源碼分析版:https://www.jianshu.com/p/5478f170d9ee     

》簡單執行個體版:https://www.cnblogs.com/yueshutong/p/9500632.html

動态代理:在程式運作時,通過反射機制動态地建立一個代理類。

>1)模式要點分析

1,動态的展現:程式開始執行時是沒有代理類的,在程式運作時,java利用反射機制動态生成代理類的執行個體

2,jdk技術支援:java在java.lang.reflect包裡提供兩個類/接口幫助我們建立和使用代理類執行個體,一個是Proxy類,一個是InvocationHandler接口。

3,兩種代理類的模式差別:靜态模式要求為程式中所有需要被通路的目标建立代理類,如果有10種代理目标,我們就得建立10個代理類,讓代理類直接通路目标對象,下面是其簡易關系圖。

Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

動态代理則是在程式調用某個目标類時才通過反射建立代理類的執行個體,建立過程并不是在開發層面可見的,又加之常常需要在代理類中進行一些額外操作來加強目标類的功能,如果代理類不可見我們就不能達到這個目的,是以這時在代理類和被代理類之間就需要一個第三者,幫助我們完成功能增強,它就是InvocationHandler。下面給出動态代理簡易關系圖。

Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

對動态代理有初步了解後,我們下面通過源碼和執行個體來分析建立和使用動态代理類的過程。

>2)反射執行個體

學習動态代理前,我們來先看一個反射的例子,對動态代理的過程了解很有幫助

  • 建立一個被反射的對象,它包含一個String和int類型的構造,和一個getName方法
public class Student {

    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
}
           

在MainActivity裡使用反射擷取Student對象的執行個體,并調用getName方法

public void reflect() throws Exception {

        //通過反射擷取類
        Class<Student> studentClass = Student.class;

        //擷取構造時,需要傳入的該構造的參數類型
        Class[] classes = new Class[]{String.class, int.class};

        //擷取類的參數為String和int類型的public構造函數
        Constructor constructor = studentClass.getConstructor(classes);

        //通過構造參數建立一個Student類的執行個體
        Student zhangsan = (Student) constructor.newInstance("張三", 18);

        //擷取Student對象裡名字為“getName”的方法
        Method method = studentClass.getMethod("getName", new Class[]{});

        //執行方法  執行個體zhangsan的"getName"方法
        String name = (String) method.invoke(zhangsan, new Object[]{});

        //列印我們建立執行個體對象時傳入的name,看是否能通過"getName"方法擷取到
        Log.e("miss08", "name = " + name);

    }
           
  • 看最後的列印
04-27 10:37:15.796 31008-31008/com.example.pver.proxydesign E/miss08: name = 張三
           
  • 通過反射擷取Student類,擷取其有參構造,通過構造擷取執行個體,再調用反射類裡方法,請大緻記住上面反射的流程,接下來我們通過源碼分析動态代理的建立過程

>3)動态代理源碼分析

  • 先給出代碼流程圖(流程圖後面還會給出,先大緻看看)
Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!
  • 動态代理需要JVM幫我們建立代理類,就需要jdk提供一些類來支援建立工作,這些類大多存在于java.lang.reflect包裡,負責建立工作的類是Proxy,下面我們看看Proxy這個類:
public class Proxy implements Serializable {
    
    //被代理類構造函數的參數類型,即InvocationHandler
    private static final Class<?>[] constructorParams =
            { InvocationHandler.class };

    //代理對象的建立方法,通過Proxy.newProxyInstance(...)生成代理對象
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        
        ...省去一些方法,隻顯示核心代碼

        final Class<?>[] intfs = interfaces.clone();
        //1,通過類加載器和接口建立代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        //2,通過反射擷取代理類中public類型的構造,且參數類型為InvocationHandler
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //3,通過構造,建立代理類的執行個體對象,并傳回
        return cons.newInstance(new Object[]{h});
     }

    //通過類加載器和接口,動态建立代理類。
    public static Class<?> getProxyClass0(ClassLoader loader, Class... interfaces)
    {...}
 
}
           

 通過代理類的建立方法newProxyInstance,我們可以看到一個代理對象的建立需要三個步驟和三個核心參數,它們分别為:

三個步驟為:

1,通過類加載器和接口建立代理類,方法為getProxyClass0(loader,interfaces)

2,通過反射擷取代理類的構造,且構參為InvocationHandler類型。

3,通過構造函數生成代理對象的執行個體。

三個參數為:

1,類加載器:ClassLoader 将代理類的位元組碼轉換成class類的對象

2,被代理類的接口清單:Class<?>[] interfaces,通過接口動态生成代理類位元組碼

3,InvocationHandler接口:被代理類的直接調用者

代理類執行個體建立的步驟最重要的是第一步代理類的建立,後面兩步是最基本的反射,我們就結合後面執行個體來分析,下面我們先來分析第一步代理類的建立,在分析的過程中順帶說說三個參數。

1、代理類Proxy的建立

  • getProxyClass0()方法建立了代理類,我們先來分析它的兩個參數,然後再通過源碼分析方法。
  • ClassLoader類加載器:将類的二進制資料(Class檔案)讀到記憶體,然後在堆區建立這個類的Class對象。看看它常用的方法,截取源碼如下:
public abstract class ClassLoader {

    //類加載器作用就是加載位元組碼,并轉換為java.lang.Class類的一個執行個體,即一個java類
    protected final Class<?> defineClass(byte[] b, int off, int len) ...{
        throw new RuntimeException("Stub!");
    }

    protected final Class<?> defineClass(String name, byte[] b, int off, int len){
        throw new RuntimeException("Stub!");
    }
}
           
  • 被代理類接口:代理類要實作與被代理對象相同的方法,在面向對象程式設計裡,要麼采用繼承要麼通過實作同一功能接口來完成,在後面的分析我們會知道代理類會繼承Proxy,由于對象的單繼承性,是以jdk動态代理就必須通過接口的形式來完成。在Proxy類裡getProxyClass0方法通過類加載器和接口對象建立出代理類。
  • 接下來看看getProxyClass0的源碼:
/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        //從緩存擷取代理類,如果緩存沒有,就通過ProxyClassFactory去建立
        return proxyClassCache.get(loader, interfaces);
    }
           

getProxyClass0方法裡首先做接口長度驗證,接着會用類加載器和接口從proxyClassCache緩存裡去取代理類,如果緩存裡有代理類的class對象,就直接傳回,不用再做後續操作,提高了建立效率。如果緩存裡沒有就用ProxyClassFactory去建立代理類。我們先看看proxyClassCache,它是一個WeakCache緩存類。

/**
     * a cache of proxy classes
     * KeyFactory:key和parameters映射sub-key
     * ProxyClassFactory:key和parameters映射proxy
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
       proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

   WeakCache<K,P,V> ---> 分别代表ClassLoader,interfaces 和 proxy
           

WeakCache對象在建立時,會傳入兩個映射接口,一個是key和parameters映射sub-key的KeyFactory,另一個是以key和parameters映射proxy的valueFactory,下面就看看這兩個映射在proxyClassCache的get方法裡的展現。

public V get(K key, P parameter) {
        
         //使用WeakReference引用key,當key為null時能立馬回收
         Object cacheKey = CacheKey.valueOf(key, refQueue);

        // map是一個ConcurrentMap,用cacheKey為key來緩存代理類,cacheKey支援為null
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);

        //如果在ConcurrentMap裡沒找到key對應的值,則建立一個添加到集合裡
        if (valuesMap == null) {

            //ConcurrentMap并發線程安全,putIfAbsent方法有傳回值,需要對傳回值進行非空處理
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                    valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

       //建立subKey,保證subkey對應的代理類存在于緩存裡
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        //如果有值就直接傳回supplier.get()
        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }
           

上面分析proxyClassCache.get(...)方法,知道了如何從緩存裡取到代理類,但是當緩存裡沒有值時,會通過ProxyClassFactory建立代理類,我們找到ProxyClassFactory,它是一個靜态内部類,分析請看裡面的注釋:

//隻展示核心代碼
private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
     
     //所有建立的代理類的名稱的字首必須是"$Proxy"
     private static final String proxyClassNamePrefix = "$Proxy"

     //完成一些驗證工作,如類加載器可用于加載接口類型等
     @override
     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

       Map<Class<?>, Boolean> interfaceSet 
                 = new IdentityHashMap<>(interfaces.length);
       for (Class<?> intf : interfaces) {
           /*
            * Verify that the class loader resolves the name of this
            * interface to the same Class object.
            */
           Class<?> interfaceClass = null;
           try {
               interfaceClass = Class.forName(intf.getName(), false, loader);
           } catch (ClassNotFoundException e) {
           }
           if (interfaceClass != intf) {
               throw new IllegalArgumentException(
                       intf + " is not visible from class loader");
           }
           /*
            * Verify that the Class object actually represents an
            * interface.
            */
           if (!interfaceClass.isInterface()) {
               throw new IllegalArgumentException(
                       interfaceClass.getName() + " is not an interface");
           }
           /*
            * Verify that this interface is not a duplicate.
            */
           if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
               throw new IllegalArgumentException(
                       "repeated interface: " + interfaceClass.getName());
           }
     }

     //通過代理類名稱和接口動态生成生成代理類位元組碼
     byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
     try {
          //傳回代理類,這裡沒找到本地實作
          return defineClass0(loader, proxyName,
                proxyClassFile, 0, proxyClassFile.length);
       } catch (ClassFormatError e) {
           throw new IllegalArgumentException(e.toString());
     }
}
           

 從上面方法裡我們找到了生成代理類位元組碼和生成代理類的方法:

1,通過代理類名字和接口生成代理類位元組碼 : ProxyGenerator.generateProxyClass(...)

2,使用類加載器和代理類位元組碼等,生成代理類:defineClass0(...)

我們先看看生成Prox位元組碼的方法,這個類ProxyGenerator存在sun包裡,這個包不是開源包,在文章結尾我會上傳這個反編譯的包,下面我們就來看看這個方法:

public static byte[] generateProxyClass(final String name,Class[] interfaces) {
   
    //通過代理類類名和接口生成位元組碼。
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    final byte[] classFile = gen.generateClassFile();

    //saveGeneratedFiles标記位,判斷是否把這個位元組碼檔案寫入到某個class檔案
    if (saveGeneratedFiles) {
        java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    public Object run() {
                        try {
                            FileOutputStream file =
                                new FileOutputStream(dotToSlash(name) + ".class");
                            file.write(classFile);
                            file.close();
                            return null;
                        } catch (IOException e) {
                            throw new InternalError(
                                    "I/O exception saving generated file: " + e);
                        }
                    }
           });
    }
}
           

generateProxyClass方法生成位元組碼并且會把位元組碼檔案存儲,這裡我們可以在本地建立一個代理類檔案用來存儲位元組碼,反編譯檢視位元組碼裡的資訊。defineClass0生成代理類的方法這裡就不分析了。接下來看看第三個參數InvocationHandler。

  • InvocationHandler:這是jdk為動态代理提供的第二個核心類/接口,首先還是看看它的源碼:
    public interface InvocationHandler {
    
        /**
        *  proxy:代理類
        *  method:調用被代理對象的某個方法的Method對象
        *  args:調用被代理對象的方法需要的參數
        */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
               

InvocationHandler裡隻用一個invoke方法,從上面分析我們知道,通過反射擷取代理類的構造時會傳入構造參數InvocationHandler,InvocationHandler作為實際的被代理類的控制者,不管使用者想要調用哪個被代理類的哪個方法,我們隻需要通過代理類執行個體調用InvocationHandler的invoke方法,傳入方法名和參數即可,要分析InvocationHandler在動态代理裡起的作用,就需要先拿到代理類的位元組碼,下面就通過具體案例來說明。

>4)動态代理執行個體

JDK動态代理實作步驟:

  1. 建立被代理的類以及實作的接口;
  2. 建立一個實作接口InvocationHandler的類,它必須實作invoke方法;
  3. 調用Proxy的newProxyInstance靜态方法,建立一個代理類。
  4. 通過代理對象調用目标方法。
  • 1 建立一個Subject接口,裡面有三個方法
public interface Subject {

    String speak();
}
           
  • 2  建立一個目标對象實作接口
public class TargetSubject implements Subject {

    @Override
    public String speak() {
        System.out.println("speak方法執行");
        return "結束";
    }
}
           
  • 3  建立handler,實作InvocationHandler,Proxy建立代理類建時需要這個handler參數。在handler的invoke方法裡,執行被代理對象中參數為args的method方法,method是在代理類中通過反射獲得,傳入handler的,這個後面會說明。
public class DynamicInvocationHandler implements InvocationHandler {

    private Subject subject;

    public DynamicInvocationHandler(Subject subject) {
        this.subject = subject;
    }

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

        //增強處理
        System.out.println("增強處理--前");
    
       
        //通過反射擷取到接口的method,執行subject目标對象中帶參數args的method方法
        String response = (String) method.invoke(subject, args);

        
        //增強處理
        System.out.println("增強處理--後");

        return response;
    }
}
           
  • 4  在Main方法中建立代理類,測試代理類建立流程
Subject targetSubject = new TargetSubject();
    InvocationHandler handler = new DynamicInvocationHandler(targetSubject);
  
    //建立代理類的執行個體
    Subject proxySubject = (Subject) Proxy.newProxyInstance(targetSubject.getClass()
            .getClassLoader()
            , targetSubject.getClass().getInterfaces()
            , handler);

      //調用被代理類的方法
    proxySubject.speak();
           
  • 檢視結果:
Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

增強處理--前

speak方法執行

增強處理--後

>5)InvocationHandler分析

接下來看看代理類的位元組碼檔案裡的資訊,看看代理類是如何通過InvocationHandler來控制目标對象的。

  • 擷取位元組碼檔案的方法:上面的分析,通過ProxyGenerator.generateProxyClass裡可以代理類的位元組碼,通過輸出流可以寫到本地檔案如$Proxy0.class,然後直接通過反編譯檢視。
public final class $Proxy0
extends Proxy
implements Subject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    // 反射時擷取這個構參的構造函數來建立代理類的執行個體對象
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
  
    //代理類實作方法,會執行InvocationHandler的invoke方法
    public final String getResponse(String string) {
        try {
            return (String)this.h.invoke((Object)this, m3, new Object[]{string});
        }
        catch (Error | RuntimeException v0) {
            throw v0;
        }
        catch (Throwable var2_2) {
            throw new UndeclaredThrowableException(var2_2);
        }
    }

    //靜态代碼塊,反射得到我通過代理類調用的方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName
                 ("java.lang.Object"));
            m3 = Class.forName("com.example.pver.proxydesign.dynamicproxy.Subject")
                .getMethod("getResponse", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object")
                .getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object")
                .getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException var1) {
            throw new NoSuchMethodError(var1.getMessage());
        }
        catch (ClassNotFoundException var1_1) {
            throw new NoClassDefFoundError(var1_1.getMessage());
        }
    }
           

位元組碼檔案分析:

1,通過Proxy.newProxyInstance生成的代理類,執行接口裡的方法,實際上是調用了InvocationHandler 的

invoke方法(參考上面直接碼裡的getResponse方法),參數分别為目前代理類、使用者執行的方法和我們調用該方法時傳入的參數。

2,InvocationHandler需要使用者自定義,在invoke方法裡執行method.invoke(subject, args),用來執行目标對象裡參數為args的方法method,故InvocationHandler才是真正的被代理類的直接通路者。

3,代理類繼承了Proxy,由于對象的單繼承性質,是以代理類不能再繼承其他類,是以JDK的動态代理隻支援接口的方式代理,并不能支援具體實作類的代理。

通過上面的分析,給出動态代理的詳細類圖:

Java設計模式——04代理模式一、代理模式簡介二、靜态代理三、動态代理最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

>6)總結下動态代理的流程:

  • 1,jdk擷取RealSubject上的所有接口清單,确定要生成的代理類類名。
  • 2,jdk根據接口資訊和類名動态建立代理類的位元組碼。
  • 3,通過類加載器将位元組碼轉換成代理類class對象。
  • 4,建立InvocationHandler的執行個體handler,用來處理對被代理類的所有方法的通路。
  • 5,通過反射擷取代理類中以handler類為參數的構造。
  • 6,使用構造建立代理類的執行個體。
  • 7,使用者通過代理類的執行個體,調用接口裡的方法,将方法參數和該方法傳入handler的invoke方法。
  • 8,handler的invoke方法裡,調用method.invoke(subject, args),用來執行目标對象subject裡參數為args的方法method。
  • 9,目标對象RealSubject執行具體業務邏輯,如果有傳回值,就将傳回傳值回給handler。

(二)cglib的動态代理

       執行個體1:

引用cglib的依賴包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2</version>
</dependency>
           

為了友善思考它的原理,我把執行步驟按順序寫下

public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //設定父類,被代理類(這裡是Car.class)
        enhancer.setSuperclass(Car.class);
        //設定回調函數
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                //增強處理...
                Object o= proxy.invokeSuper(obj, args);//代理類調用父類的方法
                //增強處理...
                return o;
            }
        });
        //建立代理類并使用回調(用父類Car去引用)
        Car car = (Car) enhancer.create();
        //執行目标方法
        System.out.println(car.move());
    }
           

方法攔截器

實作MethodInterceptor接口的intercept方法後,所有生成的代理方法都調用這個方法。

intercept方法的具體參數有

  1. obj 目标類的執行個體
  2. method 目标方法執行個體(通過反射擷取的目标方法執行個體)
  3. args 目标方法的參數
  4. proxy 代理類的執行個體

該方法的傳回值就是目标方法的傳回值。

特點

  1. cglib的動态代理是針對類來實作代理。
  2. 對指定目标類産生一個子類,通過方法攔截技術攔截所有父類方法的調用。
  3. 因為是通過繼承實作,final類無法使用。

       執行個體2:

靜态代理和動态代理模式都是要求目标對象是實作一個接口的目标對象,但是有時候目标對象隻是一個單獨的對象,并沒有實作任何的接口,這個時候就可以使用以目标對象子類的方式類實作代理,這種方法就叫做:Cglib代理

Cglib代理,也叫作子類代理,它是在記憶體中建構一個子類對象進而實作對目标對象功能的擴充.

  • JDK的動态代理有一個限制,就是使用動态代理的對象必須實作一個或多個接口,如果想代理沒有實作接口的類,就可以使用Cglib實作.
  • Cglib是一個強大的高性能的代碼生成包,它可以在運作期擴充java類與實作java接口.它廣泛的被許多AOP的架構使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
  • Cglib包的底層是通過使用一個小而塊的位元組碼處理架構ASM來轉換位元組碼并生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM内部結構包括class檔案的格式和指令集都很熟悉.

Cglib子類代理實作方法: 

1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,是以直接引入

pring-core-3.2.5.jar

即可.

2.引入功能包後,就可以在記憶體中動态建構子類

3.代理的類不能為final,否則報錯

4.目标對象的方法如果為final/static,那麼就不會被攔截,即不會執行目标對象額外的業務方法.

代碼示例:

目标對象類:UserDao.java

/**
 * 目标對象,沒有實作任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已經儲存資料!----");
    }
}
           

Cglib代理工廠:ProxyFactory.java

/**
 * Cglib子類代理工廠
 * 對UserDao在記憶體中動态建構一個子類對象
 */
public class ProxyFactory implements MethodInterceptor{
    //維護目标對象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //給目标對象建立一個代理對象
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設定父類
        en.setSuperclass(target.getClass());
        //3.設定回調函數
        en.setCallback(this);
        //4.建立子類(代理對象)
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開始事務...");

        //執行目标對象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("送出事務...");

        return returnValue;
    }
}
           

測試類:

/**
 * 測試類
 */
public class App {

    @Test
    public void test(){
        //目标對象
        UserDao target = new UserDao();

        //代理對象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //執行代理對象的方法
        proxy.save();
    }
}
           

在Spring的AOP程式設計中:

如果加入容器的目标對象有實作接口,用JDK代理

如果目标對象沒有實作接口,用Cglib代理

最後,作為一個搬磚者,再次感謝各位大神的分享,好人一生平安!

參考文檔1:https://www.jianshu.com/p/5478f170d9ee(重要參考文獻)

參考文檔2:https://www.cnblogs.com/yueshutong/p/9500632.html(次重點)

參考文檔3:https://www.cnblogs.com/cenyu/p/6289209.html(cglib參考文獻)

繼續閱讀