1. 程式人生 > 程式設計 >設計模式 - 動態代理原理及模仿JDK Proxy 寫一個屬於自己的動態代理

設計模式 - 動態代理原理及模仿JDK Proxy 寫一個屬於自己的動態代理

本篇文章程式碼內容較多,講的可能會有些粗糙,大家可以選擇性閱讀。

本篇文章的目的是簡單的分析動態代理的原理及模仿JDK Proxy手寫一個動態代理以及對幾種代理做一個總結。

對於代理模式的介紹和講解,網上已經有很多優質的文章,我這裡就不會再過多的介紹了,這裡推薦幾篇優質的文章作為參考:

  1. 給女朋友講解什麼是代理模式
  2. 輕鬆學,Java 中的代理模式及動態代理

另外,我的 github 倉庫對應目錄中也有相關的基礎示例程式碼:github.com/eamonzzz/ja…

JDK Proxy 動態代理

動態代理的概念這裡就不再闡述了;動態代理相對於靜態代理來說,它的功能更加強大,隨著業務的擴充套件,適應性更強。

在說動態代理原理之前,我們還是來看看動態代理的一般使用。

使用

本篇文章的使用示例,是以一個最為簡單的代理模式的程式碼為例,相信大家在學習或瞭解代理模式的時候都有看到或者接觸過這些程式碼。

  1. 先建立一個Subject主體抽象介面:
/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:06
 */
public interface Subject {
    void request();
}複製程式碼

  1. 再建立一個真實的主體RealSubject來處理我們的真實的邏輯:
/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:06
 */
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真實處理邏輯!");
    }
}複製程式碼

  1. 在不修改RealSubject類的情況下,如果我們要實現在執行RealSubject類中request()方法之前或之後執行一段邏輯的話,該怎麼實現呢?這就得建立一個代理類,來達到增強原有程式碼的目的。所以現在建立一個 JDK 動態代理類 RealSubjectJDKDynamicProxy
/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:08
 */
public class RealSubjectJDKDynamicProxy implements InvocationHandler {
    // 被代理物件的引用
    private Object target;
    // 通過構造器傳入物件引用
    public RealSubjectJDKDynamicProxy(Object target) {
        this.target = target;
    }
    // 獲得 JDK 動態代理建立的代理物件
    public Object getInstance() {
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object o,Method method,Object[] objects) throws Throwable {
        before();
        // 代理執行被代理物件的相應方法
        Object invoke = method.invoke(target,objects);
        after();
        return invoke;
    }

    private void before() {
        System.out.println("前置增強!");
    }

    private void after() {
        System.out.println("後置增強!");
    }
}複製程式碼

  1. 測試程式碼:
@Test
public void test(){
    Subject realSubject = new RealSubject();
    RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);
    Subject instance = (Subject) proxy.getInstance();
    instance.request();
    System.out.println(realSubject.getClass());
    System.out.println(instance.getClass());
}複製程式碼

  1. 測試結果
前置增強!
真實處理邏輯!
後置增強!
class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject
class com.sun.proxy.$Proxy8複製程式碼

從結果來看,上面的程式碼已經達到了我們的增強的目的。

原理分析

不知道大家有沒有注意到上面的測試程式碼中,最後兩行我將代理之前和代理之後的class物件給列印了出來;並且發現,這兩個物件並非同一個,最重要的是,經過代理之後的物件的Subjectcom.sun.proxy.$Proxy8而不是com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject或者com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject,那麼這個instance到底是從哪裡來?帶著這個疑問,我們來通過 JDK Proxy 原始碼來分析一下:

我們跟進RealSubjectJDKDynamicProxy類中的Proxy.newProxyInstance(clazz.getClassLoader(),this);方法:

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 {
        ...
        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);
    }
   ...
}複製程式碼

發現在newProxyInstance方法中呼叫了getProxyClass0(loader,intfs)方法,我們跟進去這個方法看一下:

/**
 * 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");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists,this will simply return the cached copy;
    // otherwise,it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader,interfaces);
}複製程式碼

程式碼邏輯很簡單,做了兩個事情:

  1. 檢查類的介面數量是否超過65535,介面個數用 2 個 byte 儲存,最大支援 65535 個。
  2. proxyClassCache 快取中去取,從註釋中可知,如果快取沒有就會呼叫ProxyClassFactory去建立。

我們現在就來簡單分析一下proxyClassCache.get(loader,interfaces)裡面的邏輯:

public V get(K key,P parameter) {
    Objects.requireNonNull(parameter);

    expungeStaleEntries();

    Object cacheKey = CacheKey.valueOf(key,refQueue);

    // lazily install the 2nd level valuesMap for the particular cacheKey
    ConcurrentMap<Object,Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object,Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }

    // create subKey and retrieve the possible Supplier<V> stored by that
    // subKey from valuesMap
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key,parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;
    // 這裡是一個 while(true)
    while (true) {
        // 如果建立 factory(這裡指ProxyClassFactory) 成功,就呼叫 factory.get()方法
        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);
            }
        }
    }
}複製程式碼

程式碼可能有點長,其實邏輯就是為了呼叫ProxyClassFactory.apply()去生成代理類。我們從while(true)處將程式碼分割成兩個部分來看:

  1. 前半部分,是從快取中去取ProxyClassFactory,如果建立成功了,則可以取到(快取中的 key 這裡不分析了)
  2. 然後看 while(true) 程式碼塊中的邏輯,if (supplier != null)這個判斷,如果快取中建立了ProxyClassFactory就會執行supplier.get()並且終止迴圈;如果沒有,則會執行new Factory(key,valuesMap);去建立factory,然後將其放入快取supplier中,然後繼續迴圈,這個時候就會執行if (supplier != null)程式碼塊中的邏輯,我們再來分析一下這個程式碼塊裡面的程式碼:
if (supplier != null) {
    // supplier might be a Factory or a CacheValue<V> instance
    V value = supplier.get();
    if (value != null) {
        return value;
    }
}複製程式碼

跟進 supplier.get()方法去看一下,我們從上面的分析可以知道這裡的supplier其實就是一個Factory,所以我們看Factory的實現,重點看get()方法:

private final class Factory implements Supplier<V> {
       ...
        @Override
        public synchronized V get() { // serialize access
            ...
            // create new value
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key,parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey,this);
                }
            }
            // the only path to reach here is with non-null value
            assert value != null;

            // wrap value with CacheValue (WeakReference)
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // put into reverseMap
            reverseMap.put(cacheValue,Boolean.TRUE);

            // try replacing us with CacheValue (this should always succeed)
            if (!valuesMap.replace(subKey,this,cacheValue)) {
                throw new AssertionError("Should not reach here");
            }

            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            return value;
        }
    }複製程式碼

我們注意到,程式碼中的重點是在Objects.requireNonNull(valueFactory.apply(key,parameter));,那這個程式碼中的valueFactory是什麼呢?我們在Proxy中,來看一下proxyClassCache的定義

 private static final WeakCache<ClassLoader,Class<?>[],Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory());複製程式碼

WeakCache中第二個引數是new ProxyClassFactory() ,再來看一下對應的構造器:

public WeakCache(BiFunction<K,P,?> subKeyFactory,BiFunction<K,V> valueFactory) {
    this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
    this.valueFactory = Objects.requireNonNull(valueFactory);
}複製程式碼

這時候明白了嗎?其實 valueFactory就是ProxyClassFactory()

明白了這一點,就來分析一下valueFactory.apply(key,parameter)到底執行了什麼?我們直接看ProxyClassFactory的程式碼

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader,Class<?>>
{
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader,Class<?>[] interfaces) {

        ...

        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName,interfaces,accessFlags);
        try {
            return defineClass0(loader,proxyName,proxyClassFile,proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}複製程式碼

縱觀全覽,不難分析,程式碼中其實就是在建立$Proxy這個中間代理類,其中byte[] proxyClassFile是程式碼塊中組裝完成之後的類的位元組碼檔案資料,通過ProxyGenerator.generateProxyClass()生成;然後通過classloader動態載入位元組碼,並生成動態代理類的Class例項,並返回。

我們再跟進ProxyGenerator.generateProxyClass()方法,來看看在生成代理類過程中的處理邏輯,看重點程式碼:。

 public static byte[] generateProxyClass(final String var0,Class<?>[] var1,int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0,var1,var2);
    final byte[] var4 = var3.generateClassFile();
    ...

    return var4;
}複製程式碼

可以發現其程式碼呼叫了var3.generateClassFile()去生成Class檔案,所以我們跟進generateClassFile()方法,看重點內容:

private byte[] generateClassFile() {
    this.addProxyMethod(hashCodeMethod,Object.class);
    this.addProxyMethod(equalsMethod,Object.class);
    this.addProxyMethod(toStringMethod,Object.class);
    Class[] var1 = this.interfaces;
    int var2 = var1.length;

    int var3;
    Class var4;
    for(var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        Method[] var5 = var4.getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method var8 = var5[var7];
            this.addProxyMethod(var8,var4);
        }
    }
    ...
}複製程式碼

程式碼有點長,這裡就不全部展開了,有興趣的朋友可以跟進去詳細看一下。從程式碼中我們大致可以看出來,在生成代理類的過程中,還添加了hashCode、equals、toString這三個方法,然後後面的邏輯就是將代理物件中的所有介面進行迭代,將其所有的方法都重新生成代理方法;然後生成位元組碼。

最後再將代理類載入到JVM中。

看一下JDK Proxy生成的代理類$Proxy

我們通過下面這段程式碼,將$Proxy檔案輸出到檔案:

@Test
public void test1(){
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    RealSubject realSubject = new RealSubject();

    RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);
    Subject instance = (Subject) proxy.getInstance();
    try {
        byte[] proxychar=  ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Subject.class});
        OutputStream outputStream = new FileOutputStream("/Users/eamon.zhang/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/"+instance.getClass().getSimpleName()+".class");
        outputStream.write(proxychar);
        outputStream.flush();
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    instance.request();
    System.out.println(instance.getClass());

}複製程式碼

通過IDEA工具檢視$Proxy0,印證一下我們之前的分析:

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this,m1,new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this,m2,(Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void request() throws  {
        try {
            super.h.invoke(this,m3,(Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this,m0,(Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject").getMethod("request");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}複製程式碼

總結

總結一下JDK Proxy的實現步驟:

  1. 拿到被代理物件的引用,並獲取它的所有介面(通過反射)
  2. JDK Proxy 類重新生成一個新的類,同時新的類要實現被代理類的所有實現的介面,還有hashCode、equals、toString這三個方法
  3. 動態生成Java程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去呼叫(在程式碼中體現)
  4. 編譯新生成的Java程式碼的 .class檔案
  5. 重新載入到JVM中執行

模擬手寫 JDK Proxy

在明白了上面的原理之後,其實我們就可以嘗試手動來實現一個JDK Proxy

我們參照JDK Proxy實現原理分析一下需要動手編寫哪些內容:

  • 首先我們需要有一個代理類MimeProxy
  • 然後從代理類出發,需要有newProxyInstance(clazz.getClassLoader(),this)這一個方法,方法引數為:(ClassLoader loader,Class>[] interfaces,InvocationHandler h),所以我們需要建立一個ClassLoaderInvocationHandler;

下面來一步一步建立:

  1. 先建立MimeClassLoader類,繼承自ClassLoader,並重寫findClass()方法:
/**
 * @author eamon.zhang
 * @date 2019-10-10 下午2:47
 */
public class MimeClassLoader extends ClassLoader {
    private Object target;

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classname = target.getClass().getPackage().getName() + "." + name;
        String filePath = MimeClassLoader.class.getResource("").getPath() + name + ".class";
        try {
            URI uri = new URI("file:///" + filePath);
            Path path = Paths.get(uri);
            File file = path.toFile();
            if (file.exists()) {
                byte[] fileBytes = Files.readAllBytes(path);
                return defineClass(classname,fileBytes,fileBytes.length);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}複製程式碼

  1. 建立 MimeInvocationHandler 類:
/**
 * @author eamon.zhang
 * @date 2019-10-10 下午2:46
 */
public interface MimeInvocationHandler {
    public Object invoke(Object proxy,Object[] args)
            throws Throwable;
}複製程式碼

  1. 建立MimeProxy類,這個類就是用來組裝成代理類,並載入到JVM,然後返回這個代理物件:
/**
 * @author eamon.zhang
 * @date 2019-10-10 下午3:08
 */
public class MimeProxy {
    private static final String ln = "\r\n";
    private static final String semi = ";";

    private static Map<Class,Class> mappings = new HashMap<Class,Class>();

    static {
        mappings.put(int.class,Integer.class);
    }

    public static Object newProxyInstance(MimeClassLoader loader,MimeInvocationHandler h)
            throws IllegalArgumentException {
        try {
            // 1. 動態生成 .java 檔案
            String src = generateSrc(interfaces);
//            System.out.println(src);
            // 2. java 檔案輸出到磁碟
            String filePath = MimeProxy.class.getResource("").getPath();
//            System.out.println(filePath);
            File f = new File(filePath + "$Proxy8.java");
//            f.deleteOnExit();
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();
            // 3. 把 java 檔案編譯成 .class 檔案
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager sjfm = compiler.getStandardFileManager(null,null,null);
            Iterable<? extends JavaFileObject> iterable = sjfm.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compiler.getTask(null,sjfm,iterable);
            task.call();
            sjfm.close();
            // 4. 把.class 檔案載入到jvm
            Class<?> proxyClass = loader.findClass("$Proxy8");
            Constructor<?> c = proxyClass.getConstructor(MimeInvocationHandler.class);
            f.delete();

            // 5. 返回位元組碼重組以後的新的代理物件
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

    /**
     * 生成 代理類
     *
     * @param interfaces
     * @return
     */
    private static String generateSrc(Class<?>[] interfaces) {
        // 這裡使用 StringBuffer 執行緒安全
        StringBuffer sb = new StringBuffer();
        sb.append("package ").append(interfaces[0].getPackage().getName()).append(semi).append(ln);
        sb.append("import ").append(interfaces[0].getName()).append(semi).append(ln);
        sb.append("import java.lang.reflect.*;").append(ln);
        sb.append("import ").append(interfaces[0].getPackage().getName()).append(".mimeproxy.MimeInvocationHandler;").append(ln);
        sb.append("public class $Proxy8 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln);
        sb.append("MimeInvocationHandler h;" + ln);
        sb.append("public $Proxy8(MimeInvocationHandler h) {").append(ln);
        sb.append("this.h = h;").append(ln);
        sb.append("}").append(ln);

        for (Method method : interfaces[0].getMethods()) {
            Class<?>[] params = method.getParameterTypes();

            StringBuffer paramNames = new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramClasses = new StringBuffer();

            for (Class<?> clazz : params) {
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());

                paramNames.append(type).append(" ").append(paramName);

                paramValues.append(paramName);
                paramClasses.append(clazz.getName()).append(".class");

                for (int i = 0; i < params.length; i++) {
                    paramNames.append(",");
                    paramValues.append(",");
                    paramClasses.append(",");
                }
            }

            sb.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName())
                    .append("(").append(paramNames.toString()).append(") {").append(ln);
            sb.append("try {").append(ln);
            // Method m = interfaces[0].getName().class.getMethod(method.getName()),new Class[]{paramClasses.toString()});
            sb.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"")
                    .append(method.getName()).append("\",new Class[]{").append(paramClasses.toString()).append("});")
                    .append(ln);
            // return this.h.invoke(this,m,new Object[]{paramValues},method.getReturnType());
            sb.append(hasReturnValue(method.getReturnType()) ? "return " : "")
                    .append(getCaseCode("this.h.invoke(this,new Object[]{" + paramValues + "})",method.getReturnType()))
                    .append(";")
                    .append(ln);
            sb.append("} catch (Error _ex) {}").append(ln);
            sb.append("catch (Throwable e) {").append(ln);
            sb.append("throw new UndeclaredThrowableException(e);").append(ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(method.getReturnType())).append(ln);
            sb.append("}");

        }
        sb.append("}").append(ln);

        return sb.toString();
    }

    /**
     * 獲取返回值型別
     *
     * @param returnClass
     * @return
     */
    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    /**
     * 拼接 invocationHandler 執行程式碼
     *
     * @param code
     * @param returnClass
     * @return
     */
    private static String getCaseCode(String code,Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    /**
     * 判斷是否有返回值
     *
     * @param clazz
     * @return
     */
    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    /**
     * 首字母轉換為小寫
     *
     * @param src
     * @return
     */
    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}複製程式碼

這樣子就編寫了一個屬於自己的動態代理,當然,代理方法還不完善,只是針對本示例進行了編寫,有興趣的朋友可以試試將其改為更通用的程式碼。

CGlib 動態代理

下面來看一下 CGlib 的動態代理的使用

使用

先建立RealSubject類,注意,這個類不用實現任何介面:

/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:22
 */
public class RealSubject {
    public void request(){
        System.out.println("真實處理邏輯!");
    }
}複製程式碼

然後建立RealSubjectCglibDynamicProxy 代理類,它必須實現MethodInterceptor介面:

/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:23
 */
public class RealSubjectCglibDynamicProxy implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) {
        // 通過CGLIB動態代理獲取代理物件的過程
        Enhancer enhancer = new Enhancer();
        // 要把哪個設定為即將生成的新類父類
        enhancer.setSuperclass(clazz);
        // 設定回撥物件
        enhancer.setCallback(this);
        // 建立代理物件
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj,Object[] args,MethodProxy proxy) throws Throwable {
        before();
        Object invokeSuper = proxy.invokeSuper(obj,args);
        after();
        return invokeSuper;
    }

    private void before() {
        System.out.println("前置增強!");
    }

    private void after() {
        System.out.println("後置增強!");
    }
}複製程式碼

這樣,一個簡單的CGlib動態代理實現就完成了,我們現在來建立測試程式碼:

@Test
public void test(){
    RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
    RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
    instance.request();
}複製程式碼

測試結果:

前置增強!
真實處理邏輯!
後置增強!複製程式碼

原理分析

不管是JDK Proxy還是CGlib,他們的核心內容都是去建立代理類,所以我們只要去了解其建立代理類的過程就 OK 了。

從上面簡單的使用示例可以知道,要使用 CGlib 動態代理,代理類必須要實現MethodInterceptor(方法攔截器),MethodInterceptor介面原始碼如下:

/**
 * General-purpose {@link Enhancer} callback which provides for "around advice".
 * @author Juozas Baliuka <a href="mailto:[email protected]">[email protected]</a>
 * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
 */
public interface MethodInterceptor
extends Callback
{
    /**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,* or by using the MethodProxy (faster).
     * @param obj "this",the enhanced object
     * @param method intercepted Method
     * @param args argument array; primitive types are wrapped
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed
     * @throws Throwable any exception may be thrown; if so,super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */
    public Object intercept(Object obj,java.lang.reflect.Method method,MethodProxy proxy) throws Throwable;

}複製程式碼

介面中只有一個intercept方法,其中傳入的引數:

  1. obj 表示增強的物件,即實現這個介面類的一個物件;
  2. method 表示要被攔截的方法;
  3. args 表示方法引數;
  4. proxy 表示要觸發父類的方法物件;

在建立代理物件的邏輯getInstance(Class> clazz)中,呼叫了enhancer.create()方法,我們跟進原始碼看一下:

/**
 * Generate a new class if necessary and uses the specified
 * callbacks (if any) to create a new object instance.
 * Uses the no-arg constructor of the superclass.
 * @return a new instance
 */
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()方法的作用是,前置校驗,校驗callbackTypes、filter是否為空,以及為空時的處理。

然後通過KEY_FACTORY.newInstance()方法建立EnhancerKey物件,並將其作為super.create(key)方法的引數傳入,我們來看一下這個create()方法,發現它是Enhancer類的父類AbstractClassGenerator中的一個方法:

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(obj) 方法,它對應的實現,是在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);
}複製程式碼

這裡又呼叫了data.newInstance(argumentTypes,callbacks)方法,第一個引數為代理物件的構造器型別,第二個為代理物件構造方法引數,第三個為對應回撥物件。原始碼如下:

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);
    }

}複製程式碼

我們發現這裡面的邏輯的意思就是,根據傳進來的引數,通過反射來生成物件,我們可以利用cglib的代理類可以將記憶體中的 class 檔案寫入本地磁碟:

@Test
public void test1(){
    //利用 cglib 的代理類可以將記憶體中的 class 檔案寫入本地磁碟
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/eamon.zhang/Documents/cglib");
    RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
    RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
    instance.request();
}複製程式碼

執行之後,在對應的目錄中可以看到生成了下圖中這三個.class檔案:

通過除錯跟蹤,我們發現 RealSubject$$EnhancerByCGLIB$$5389cdca 就是 CGLib生成的代理類,繼承了 RealSubject 類。通過IDEA檢視該原始碼:

public class RealSubject$$EnhancerByCGLIB$$5389cdca extends RealSubject implements Factory {
    ...
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject$$EnhancerByCGLIB$$5389cdca");
        Class var1;
        CGLIB$request$0$Method = ReflectUtils.findMethods(new String[]{"request","()V"},(var1 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject")).getDeclaredMethods())[0];
        CGLIB$request$0$Proxy = MethodProxy.create(var1,var0,"()V","request","CGLIB$request$0");
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals","(Ljava/lang/Object;)Z","toString","()Ljava/lang/String;","hashCode","()I","clone","()Ljava/lang/Object;"},(var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1,"equals","CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1,"CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1,"CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1,"()Ljava/lang/Object;","CGLIB$clone$4");
    }

    final void CGLIB$request$0() {
        super.request();
    }

    public final void request() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this,CGLIB$request$0$Method,CGLIB$emptyArgs,CGLIB$request$0$Proxy);
        } else {
            super.request();
        }
    }
    ...
}複製程式碼

我們通過代理類的原始碼可以看到,代理類會獲得所有在父類繼承來的方法,並且會有 MethodProxy 與之對應,比如 Method CGLIB$request$0$MethodMethodProxy CGLIB$request$0$Proxy這些方法在代理類的 reuqest()中都有呼叫。

呼叫過程: 代理物件呼叫 this.request()方法 -> 呼叫攔截器 -> methodProxy.invokeSuper -> CGLIB$request$0() -> 被代理物件 request()方法。 此時,我們發現攔截器 MethodInterceptor 中就是由 MethodProxyinvokeSuper 方法呼叫代理方法的。

MethodProxy 非常關鍵,我們分析一下它具體做了什麼:

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private CreateInfo createInfo;

    private final Object initLock = new Object();
    private volatile FastClassInfo fastClassInfo;

    /**
     * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class
     * for similar functionality.
     */
    public static MethodProxy create(Class c1,Class c2,String desc,String name1,String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1,desc);
        proxy.sig2 = new Signature(name2,desc);
        proxy.createInfo = new CreateInfo(c1,c2);
        return proxy;
    }
    ...

    private static class CreateInfo
    {
        Class c1;
        Class c2;
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;

        public CreateInfo(Class c1,Class c2)
        {
            this.c1 = c1;
            this.c2 = c2;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if (fromEnhancer != null) {
                namingPolicy = fromEnhancer.getNamingPolicy();
                strategy = fromEnhancer.getStrategy();
                attemptLoad = fromEnhancer.getAttemptLoad();
            }
        }
    }
    ...複製程式碼

繼續看invokeSuper()方法:

public Object invokeSuper(Object obj,Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2,obj,args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

private static class FastClassInfo
{
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
}複製程式碼

上面程式碼呼叫過程就是獲取到代理類對應的 FastClass,並執行了代理方法。還記得之前生成三個 class 檔案嗎?RealSubject$$EnhancerByCGLIB$$5389cdca$$FastClassByCGLIB$$57b94d72.class就是代理類的 FastClassRealSubject$$FastClassByCGLIB$$ed23432.class就是被代理類的FastClass

CGLib 動態代理執行代理方法效率之所以比 JDK 的高是因為 Cglib 採用了 FastClass 機 制,它的原理簡單來說就是:

  • 為代理類和被代理類各生成一個 Class,這個 Class 會為代理類或被代理類的方法分配一個 index(int 型別)。這個 index 當做一個入參,FastClass就可以直接定位要呼叫的方法直接進行呼叫,這樣省去了反射呼叫,所以呼叫效率比 JDK動態代理通過反射呼叫高。

至此,Cglib 動態代理的原理我們就基本搞清楚了,如果對程式碼細節有興趣的小夥伴可以再自行深入研究。

JDK Proxy 與 CGlib 比較

  1. JDK 動態代理是實現了被代理物件的介面,CGLib繼承了被代理物件。
  2. JDKCGLib 都是在執行期生成位元組碼,JDK 是直接寫 Class 位元組碼,CGLib 使用 ASM 框架寫 Class 位元組碼,Cglib 代理實現更復雜,生成代理類JDK 效率低。
  3. JDK 呼叫代理方法,是通過反射機制呼叫,CGLib 是通過 FastClass 機制直接呼叫方法, CGLib 執行效率 更高

代理模式與 Spring

Spring 中的代理選擇原則

  1. Bean 有實現介面時,Spring 就會用 JDK 的動態代理
  2. Bean 沒有實現介面時,Spring 選擇 CGLib
  3. Spring 可以通過配置強制使用 CGLib,只需在 Spring 的配置檔案中加入如下程式碼:
<aop:aspectj-autoproxy proxy-target-class="true"/>複製程式碼

參考資料:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

總結

靜態代理和動態的本質區別

  1. 靜態代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增違背開閉原則
  2. 動態代理採用在執行時動態生成程式碼的方式,取消了對被代理類的擴充套件限制,遵循開閉原則
  3. 若動態代理要對目標類的增強邏輯擴充套件,結合策略模式,只需要新增策略類便可完成,無需修改代理類的程式碼。

代理模式的優缺點

優點

  1. 代理模式能將代理物件與真實被呼叫的目標物件分離。
  2. 一定程度上降低了系統的耦合度,擴充套件性好。
  3. 可以起到保護目標物件的作用。
  4. 可以對目標物件的功能增強

缺點

  1. 代理模式會造成系統設計中類的數量增加。
  2. 在客戶端和目標物件增加一個代理物件,會造成請求處理速度變慢。
  3. 增加了系統的複雜度。

---

本篇文章的原始碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesignpatterns/proxy

測試類原始碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesignpatterns/proxy

歡迎大家 star 原始碼,共同進步,我會按照 git 上的大綱在學習的同時,記錄文章與原始碼~

博主剛開始寫部落格不久,文中若有錯誤或者有任何的建議,請在留言中指出,向大家學習~

本文由部落格一文多發平臺 OpenWrite 釋出!