JDK+CGLIB動態代理過程詳細分析(源碼分析和調用過程分析)

關於java的動態代理,首先我們需要了解與之相匹配的設計模式—代理模式。而對於創建代理類的時間點,又可以分爲靜態代理和動態代理。

一、代理模式

代理模式是常用的java設計模式,它的特徵是代理類與委託類有同樣的接口,代理類負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類並不真正實現服務,而是具有委託類的實例對象,通過委託類對應的實例對象調用委託類的相關方法,來提供特定的服務。通俗的說,代理類就相當於一箇中間商,負責對兩邊的對象的操作進行響應。

在這裏插入圖片描述

二、靜態代理

  • 靜態代理是在程序員編程時由程序員創建,也就是在編譯時就已經把代理類、接口等確定下來,在程序運行,代理類的.class文件已經生成。

下面舉個簡單的例子,Person實現輸出一句話,而我們通過代理類的方法來調用對應Person類的方法,達到輸出結果。

爲什麼不直接訪問Person類的方法,還需要多次一舉的建一個代理類呢?因爲這種間接性可以帶來其他用途,我們可以在調用對應方法之前或之後處理一些需要操作,與之聯繫的就是AOP編程,我們能在一個切點之前或之後執行一些操作,切點就是方法。

Person 接口:

public interface Person {
    void sayPerson();
}

PersonImpl 實現類:

public class PersonImpl implements Person{
    @Override
    public void sayPerson() {
        System.out.println("這是Person類的實現。。。");
    }
}

Person靜態代理類:

public class StaticProxy implements Person{

    Person person;

    public StaticProxy(Person person) {
        this.person = person;
    }

    @Override
    public void sayPerson() {
        System.out.println("代理類...");
        person.sayPerson();
    }
}

Main方法:

public class ProxyMain {
    public static void main(String[] args) {
        Person person = new PersonImpl();

        //將person類傳到靜態代理類中
        StaticProxy staticProxy = new StaticProxy(person);

        //通過訪問靜態代理類的sayPerson方法調用person類的方法
        staticProxy.sayPerson();
    }
}

運行結果:
在這裏插入圖片描述

三、JDK Proxy動態代理

  • 動態代理是在運行時進行創建,和靜態代理不同,動態代理創建的代理類存在於java虛擬機中,即在內存中。上面的靜態代理例子中,代理類是自己定義的,在程序運行之前已經編譯完成,我們可以到具體的編譯輸出目錄查找到.class文件。然而動態代理,代理類不是在代碼中定義好的,而是在運行時動態生成的。
  • 相比於靜態代理,動態代理的優勢在於很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。
  • 動態代理我們需要使用到java.lang.reflect.Proxy類 和 InvocationHandler接口,通過這個類和接口可以生成JDK動態代理類和代理對象。

下面舉個簡單例子:
Person 接口:

public interface Person {
    void sayPerson();
}

Student類,實現Person接口:

public class Student implements Person{
    @Override
    public void sayPerson() {
        System.out.println("我是學生,實現了Person類。。。");
    }
}

自定義一個類實現InvocationHandler接口,重寫invoke方法,invoke方法中定義我們具體需要的操作和代理方法的調用。

public class MyInvocationHandler<T> implements InvocationHandler {
    T target;

    public MyInvocationHandler(T target) {
        this.target = target;
    }

    /**
    *
    * @param proxy 代表動態代理對象
    * @param method 代表執行的方法
    * @param args   代表執行方法的參數
    * @return java.lang.Object
    * @exception
    **/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行"+method.getName()+"方法");
        Object result = method.invoke(target, args);
        return result;
    }
}

Main方法:定義代理對象,並將該對象傳入自定義的invocationHandler構造器,創建一個與代理對象相關聯的invocationHandler,接下來使用Proxy類,創建一個代理對象。

public class DynamicMain {
    public static void main(String[] args) {
        //創建一個學生實例
        Person student = new Student();

        //創建一個與代理對象相關聯的invocationHandler
        MyInvocationHandler<Person> myInvocationHandler = new MyInvocationHandler<>(student);

        //Proxy創建一個代理對象來代理student,代理對象的每個執行方法都會替換執行invocationHandler中的invoke方法
        Person o = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, myInvocationHandler);

        o.sayPerson();
    }
}

運行結果:
在這裏插入圖片描述

四、JDK Proxy動態代理原理分析

上述寫了一個簡單的動態代理例子,下面進行原理分析,首先從main方法的創建代理對象開始入手。代碼中是利用Proxy.newProxyInstance來進行創建代理對象,我們查看該方法的源碼。

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

       final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            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;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

其中,重要的關鍵步驟有如下幾步,通過main方法中對應newProxyInstance方法的傳入參數(代理類的類加載器、代理對象需要實現的接口、InvocationHandler),在運行時動態生成代理對象。

//接口類的克隆
final Class<?>[] intfs = interfaces.clone();

/*
* 創建和查找代理類
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);

//獲得代理類的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);

//通過代理類的構造器創建實例對象
return cons.newInstance(new Object[]{h});

因爲是運行時動態生成的,.class文件緩存在java虛擬機中,這個文件中的內容就很重要,負責代理我們定義好的內容。具體查看的方法,可以通過ProxyGenerator.generateProxyClass打印到文件中查看。另外,我再下面cglib分析中介紹另外一種方法,具體的分析過程也類似。

注意:newProxyInstance中的Class<?>[] interfaces參數必須是接口,如果寫成類,將出現如下錯誤,並且我們從這個參數名上就可以看出,它需要傳入的是接口數組

Exception in thread "main" java.lang.IllegalArgumentException: com.hcx.dynamic.Student is not an interface
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:590)
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557) 

五、cglib 動態代理

正是因爲jdk 的Proxy方式代理的參數中必須是實現對應接口,這樣沒有實現接口的類就無法使用JDK Proxy實現代理,所以另外一種代理出現了,cglib。

cglib(Code Generation Library)是一個基於ASM的字節碼生成庫。它允許我們在運行時對字節碼進行修改和動態生成。cglib是通過繼承來實現代理,具體後面會通過生成的代理類class文件分析。

下面也是通過一個例子來開始:
直接定義一個Person類,不定義接口

public class Person {

    public void sayStudent(){
        System.out.println("這是Person類中的學生...");
    }

    public void sayTeacher(){
        System.out.println("這是Person類中的老師...");
    }
}

自定義MethodInceptor方法,實現MethodInterceptor接口,重寫其中的intercept方法,方法中是具體代理的操作。intercept方法的四個參數:

  • o 表示增強型的對象,即表示cglib代理的子類對象。不懂沒關係,繼續看後面的,看完回頭想一下就明白了。(後續會說到增強型和不增強的區別和用途)
  • method 表示要攔截的方法;
  • objects 表示被攔截方法的參數;
  • methodProxy 表示要觸發父類的方法對象。
    methodProxy.invokeSuper(o,objects)調用對應的目標方法。
public class MyMethodInceptor implements MethodInterceptor {
    @Override    
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("動態代理方法進入。。。");
        return methodProxy.invokeSuper(o,objects);
    }
}

Main方法:

public class CglibMain {
    public static void main(String[] args) {
    	//創建Enhancer對象
        Enhancer enhancer = new Enhancer();
        //設置父類,即要代理的類
        enhancer.setSuperclass(Person.class);
        //設置回調對象
        enhancer.setCallback(new MyMethodInceptor());
        //創建代理類
        Person student = (Person) enhancer.create();
        //通過代理類調用目標方法
        student.sayStudent();
    }
}

運行結果:代理類完成目標方法的代理,並在調用之前完成其他內容的操作,從這我們也可以看到AOP的操作。
在這裏插入圖片描述

六、cglib 動態代理原理分析

下面我們查看源碼,分析cglib具體的實現步驟。
首先是Enhancer類,cglib的Enhancer類用來指定要代理的目標對象(上述的Person類)和實際處理操作的對象(上述的MyMethodInceptor類),最後調用create()方法創建代理對象,對這個對象所以非final方法的調用都會轉發到intercept方法中。
下面的create方法主要完成的是對createHelper的調用。

public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

查看createHelper方法,代碼如下:

private Object createHelper() {
        preValidate();
        Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                ReflectUtils.getNames(interfaces),
                filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
                callbackTypes,
                useFactory,
                interceptDuringConstruction,
                serialVersionUID);
        this.currentKey = key;
        Object result = super.create(key);
        return result;
    }

preValidate方法進行校驗;
KEY_FACTORY.newInstance 創建EnhancerKey對象,作爲super.create(key)參數,創建代理對象的參數。

查看super.create(key) 的 create方法,代碼如下:

protected Object create(Object key) {
        try {
            ClassLoader loader = getClassLoader();
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            ClassLoaderData data = cache.get(loader);
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        data = new ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
            this.key = key;
            Object obj = data.get(this, getUseCache());
            if (obj instanceof Class) {
                return firstInstance((Class) obj);
            }
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }

創建代理對象在nextInstance方法中,該方法是AbstractClassGenerator.java類的一個方法,具體實現是在子類Enhancer中。子類中方法源碼如下:

protected Object nextInstance(Object instance) {
        EnhancerFactoryData data = (EnhancerFactoryData) instance;

        if (classOnly) {
            return data.generatedClass;
        }

        Class[] argumentTypes = this.argumentTypes;
        Object[] arguments = this.arguments;
        if (argumentTypes == null) {
            argumentTypes = Constants.EMPTY_CLASS_ARRAY;
            arguments = null;
        }
        return data.newInstance(argumentTypes, arguments, callbacks);
    }
  • argumentTypes 是代理對象的構造器類型
  • arguments 是代理對象的構造方法參數
  • callbacks 是對應回調對象

data.newInstance的方法源碼如下,通過ReflectUtils反射生成代理對象,往下查看源碼,最後會調用到Unsafe類的分配對象方法,最後將生成的代理對象返回,通過強轉轉換爲我們定義的Person代理對象。
return UnsafeFieldAccessorImpl.unsafe.allocateInstance(this.constructor.getDeclaringClass());
public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
            setThreadCallbacks(callbacks);
            try {
                // Explicit reference equality is added here just in case Arrays.equals does not have one
                if (primaryConstructorArgTypes == argumentTypes ||
                        Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
                    // If we have relevant Constructor instance at hand, just call it
                    // This skips "get constructors" machinery
                    return ReflectUtils.newInstance(primaryConstructor, arguments);
                }
                // Take a slow path if observing unexpected argument types
                return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
            } finally {
                // clear thread callbacks to allow them to be gc'd
                setThreadCallbacks(null);
            }

        }

七、代理對象調用過程分析

上面我們對創建代理對象過程進行了分析,下面我們就對緩存在java虛擬機中的代理類調用過程進行分析,即我們調用具體目標方法是怎麼一個過程,還有就是上文留下來的問題,增強型對象和不增強的用途。

要分析調用過程,我們首先要知道在內存中的代理類中具體是什麼內容,我們需要看到裏面的內容,可以使用如下方法。

(1)進入我們上面cglib例子的工程目錄,打開cmd,輸入命令:(注意java環境變量和自己安裝jdk的位置)
java -classpath "C:\Program Files\Java\jdk1.8.0_231\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB
(2)輸入完後,會跳出一個java工具,選擇File ----> Attach to HotSpot process 會要求輸入進程號,這時候我們再開一個cmd命令框,輸入jps -l 顯示在運行的java進程, 我們可以在剛纔cglib程序斷點進入調試狀態,不讓它運行完,運行完就沒有進程id了
在這裏插入圖片描述

(3)點ok後,我們點Tool ---->Class Browser,在輸入框輸入上文的Person類,可以看到具體下面搜索出來的內存中的代理類。點擊具體的類後單獨創建.class文件或點擊Create .class for all classes便可以在改工程目錄下創建.class文件。創建完成後,可以使用反編譯工具查看裏面的具體內容(工具有JD-GUIluyten等)
在這裏插入圖片描述
在這裏插入圖片描述
(4)可以從下圖看到,main方法進入後,student對象的類名是com.hcx.com.hcx.cglib.Person$$EnhancerByCGLIB$$fa76b0ae@4667ae56,並且CGLIB$CALLBACK_0值爲我們上文自定義的MyMethodInceptor類。

(5)知道這兩個後,再結合(3)這步生成的class文件,我們查看com.hcx.com.hcx.cglib.Person$$EnhancerByCGLIB$$fa76b0ae@4667ae56這個class文件。可以看到上文我們說到的cglib是通過繼承的方式實現代理的,繼承我們定義的類,從而可以利用java繼承的特性完成目標方法的調用。因爲main方法中調用的是sayStudent方法,所以看下面的圖。看到步驟(4)中說到的參數CGLIB$CALLBACK_0,如果這個參數不爲null,就調用MyMethodInceptor的intercept方法,如果沒有設置這個回調參數,則直接調用父類的目標方法。
在這裏插入圖片描述

(6)因爲我們在main方法中setCallback設置了回調對象,所以接下來走到MyMethodInceptor的intercept方法中,在該方法中又調用了invokeSuper方法。那麼繼續走,走到invokeSuper中,如下圖,可以看到obj是我們剛纔調用目標方法的類名,經過init初始化後,fci.f2是Person$$EnhancerByCGLIB$$fa76b0ae$$FastClassByCGLIB$$這個類,fci.i2值爲15。
在這裏插入圖片描述

(7)上一步驟知道後,我們可以在(3)步驟中找到並編譯對應的class文件,如下圖。因爲上一步調用的是fci.f2.invoke方法,所以在Person$$EnhancerByCGLIB$$fa76b0ae$$FastClassByCGLIB$$類中找到invoke方法,根據上一步傳參可以知道fci.i2就是這裏的n,值爲15,根據n的值匹配,調用person$$EnhancerByCGLIB$$fa76b0ae.CGLIB$sayStudent$0();,該方法是調用父類,也就是Person類的目標方法,具體的代碼實現如下兩圖中所示。
在這裏插入圖片描述
在這裏插入圖片描述

最後,返回結果,調用過程結束。

八、增強型和不增強的區別和用途

接下來,我們再繼續一個例子,如果我在sayStudent方法中調用一個sayTeacher方法,只需更改一下Person類中方法,改爲如下代碼:加一行方法調用。

public void sayStudent(){
        System.out.println("這是Person類中的學生...");
        sayTeacher();
    }

運行結果:
在這裏插入圖片描述
運行結果顯示進入了兩次intercept方法,輸出了兩次intercept方法中內容。接下來繼續調試可以看到,走到Person類中,this所指向的是子類,也就是存在於內存中的代理類,所以這時調用的sayTeacher方法,其實是子類代理類中的方法,那麼根據上文判斷是否有回調對象,便會再進入intercept方法(這就是增強型對象的作用),然後再調用父類的目標方法輸出,所以結果打印如上圖所示。
在這裏插入圖片描述
這樣的場景在有時候是這樣需要的,但是有時候我們不需要方法調用都走一遍intercept,重複輸出一些共用的內容。那麼我們希望只走代理方法一遍,Invoke the original method, on a different object of the same type.在相同類型的不同對象上調用原始方法。

這樣我們就要修改代碼,就不適用invokeSuper,改爲invoke,並且需要類似jdk 代理,在設置回調對象時,新建一個被代理對象以參數傳入,之後調用的方法也是調用的該對象的。注意invoke方法傳入的對象時入參新建的被代理對象,不能是增強型對象,否則會死循環,導致棧溢出
MyMethodIntercept代碼修改:

public class MyMethodInceptor implements MethodInterceptor {
    private Person person;

    public MyMethodInceptor(Person person) {
        this.person = person;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("動態代理方法進入。。。");
        return methodProxy.invoke(person,objects);
    }
}

Main方法修改:新建一個被代理類,傳入MyMethodInceptor構造方法。

public class CglibMain {
    public static void main(String[] args) {
		Person person = new Person();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new MyMethodInceptor(person));
        Person student = (Person) enhancer.create();
        student.sayStudent();
        }
}

運行結果:
在這裏插入圖片描述

8.1、非增強調用過程

(1)下面我們調試,並結合代理類的class文件分析調用過程。進入invoke方法,如下圖,我們可以看到fci.f1指向Person$$FastClassByCGLIB$$8d8414de類,fci.i1的值爲0,obj是我們傳入的在main方法中創建的person對象。
在這裏插入圖片描述
(2)接下來我們去查看生成的代理類class文件,傳入的n,即0,匹配的是person.sayStudent,接下來的sayTeacher也是相同的對象,所以進入intercept方法就一次。
在這裏插入圖片描述
在這裏插入圖片描述

(3)我們再結合下圖可以再一次確認,此時的this對象就是在main方法中創建的被代理的對象。是非增強型,所以我們在調用同一個類下的其他方法時,是不會進入intercept方法中。
在這裏插入圖片描述

九、總結

  • 靜態代理,class文件在文件系統可見,在編譯時。
  • jdk 動態代理,class文件緩存在內存,在運行時動態生成代理類,只能基於接口進行代理。
  • cglib 動態代理,通過繼承代理,可以代理類。

有了以上做基礎,我們在學習和用到AOP編程時就可以很快入門了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章