主頁 > 軟體設計 > 設計模式 - 動態代理原理及模仿JDK Proxy 寫一個屬于自己的動態代理

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

2020-09-15 00:43:08 軟體設計

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

本篇文章的目的是簡單的分析動態代理的原理及模仿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 = https://www.cnblogs.com/eamonzzz/p/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 = https://www.cnblogs.com/eamonzzz/p/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 = https://www.cnblogs.com/eamonzzz/p/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 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 = https://www.cnblogs.com/eamonzzz/p/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 = https://www.cnblogs.com/eamonzzz/p/cache.get(loader);
        if (data == null) {
            synchronized (AbstractClassGenerator.class) {
                cache = CACHE;
                data = cache.get(loader);
                if (data == null) {
                    Map newCache = new WeakHashMap(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 = https://www.cnblogs.com/eamonzzz/p/(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 發布!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/39960.html

標籤:設計模式

上一篇:SpringCloud-創建服務消費者-Ribbon方式(附代碼下載)

下一篇:設計模式之美—單例模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more