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

来源:https://www.cnblogs.com/eamonzzz/archive/2019/10/15/11675367.html
-Advertisement-
Play Games

本篇文章代碼內容較多,講的可能會有些粗糙,大家可以選擇性閱讀。 本篇文章的目的是簡單的分析動態代理的原理及模仿 手寫一個動態代理以及對幾種代理做一個總結。 對於代理模式的介紹和講解,網上已經有很多優質的文章,我這裡就不會再過多的介紹了,這裡推薦幾篇優質的文章作為參考: 1. "給女朋友講解什麼是代理 ...


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

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

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

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

另外,我的 github 倉庫對應目錄中也有相關的基礎示例代碼:https://github.com/eamonzzz/java-advanced...

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(), clazz.getInterfaces(), 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, parameter, subKey, 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, P, 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<?>[], 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, 0, 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(), clazz.getInterfaces(), 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, 0, 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, Method method, 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, Class<?>[] interfaces, 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, null, null, null, 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,m,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, Method method, 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, Object[] args,
                               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, arguments, 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, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "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 發佈!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 理解javascript事件執行機制 眾所周知,js是一個單線程的語言,這意味著同一時間只能做一件事,但是我們又說js是非同步的。首先,單線程並不是沒有優點。作為瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動,以及操作 DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。比如,假 ...
  • 簡單的描述了淺拷貝和深拷貝的區別後,分別進行實現且所有方法都已進行試驗。 ...
  • CSS樣式 CSS概述 CSS Cascading Style Shees層疊樣式表 HTML定義網頁的內容,CSS定義內容的樣式。 內容和樣式相互分離,便於修改樣式。 CSS語法 註意:1.最後一條聲明可以沒有分號,但是為了以後修改方便,一般也加上分號。 2.為了使用樣式更加容易閱讀,可以將每條代 ...
  • 前言 設計前端組件是最能考驗開發者基本功的測試之一,因為調用Material design、Antd、iView 等現成組件庫的 API 每個人都可以做到,但是很多人並不知道很多常用組件的設計原理。 能否設計出通用前端組件也是區分前端工程師和前端api調用師的標準之一,那麼應該如何設計出一個通用組件 ...
  • 前言 雙向綁定 其實已經是一個老掉牙的問題了,只要涉及到MVVM框架就不得不談的知識點,但它畢竟是Vue的三要素之一. Vue三要素 響應式: 例如如何監聽數據變化,其中的實現方法就是我們提到的雙向綁定 模板引擎: 如何解析模板 渲染: Vue如何將監聽到的數據變化和解析後的HTML進行渲染 可以實 ...
  • HTTP有哪些方法? HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法 HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 這些方法的具體作用是什麼? GET: 通常用於請求伺服器發送某些資源 HEAD: 請求資源的頭 ...
  • 前言 雖然前端開發作為 GUI 開發的一種,但是存在其特殊性,前端的特殊性就在於“動態”二字,傳統 GUI 開發,不管是桌面應用還是移動端應用都是需要預先下載的,只有先下載應用程式才會在本地操作系統運行,而前端不同,它是“動態增量”式的,我們的前端應用往往是實時載入執行的,並不需要預先下載,這就造成 ...
  • 降級 降級 對於一個高可用服務,很重要的一個設計就是降級開關。在設計降級開關時,主要有以下思路: 1.開關集中化管理:通過推送機制把開關推送到各個應用。 2.可降級的多級服務:比如服務調用降級為只讀本地緩存,只讀分散式緩存,只讀預設降級數據(如庫存狀態預設有貨) 3.開關前置化:如架構是nginx ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...