設計模式之代理,手動實現動態代理,揭秘原理實現

来源:https://www.cnblogs.com/youzhibing/archive/2019/03/18/10464274.html
-Advertisement-
Play Games

前言 開心一刻 周末,帶著老婆兒子一起逛公園。兒子一個人跑在前面,吧唧一下不小心摔了一跤,腦袋瓜子摔了個包,稀里嘩啦的哭道:“爸爸,我會不會摔成傻子!” 我指了指我頭上的傷痕安慰道:“不會的,你看,這是爸爸小時候摔的。” 話還沒有說話,小家伙哭的更厲害了:“那就是說我長大後就會和你一樣傻了,我不要, ...


前言

  開心一刻

    周末,帶著老婆兒子一起逛公園。兒子一個人跑在前面,吧唧一下不小心摔了一跤,腦袋瓜子摔了個包,稀里嘩啦的哭道:“爸爸,我會不會摔成傻子!”

    我指了指我頭上的傷痕安慰道:“不會的,你看,這是爸爸小時候摔的。”

    話還沒有說話,小家伙哭的更厲害了:“那就是說我長大後就會和你一樣傻了,我不要,我不要!”

    老婆忍不住發飆:“別哭了,你怎麼會變傻呢?你看你爸,你爸傻嗎?”

    我趕緊回應道:“是啊,你看我多聰明!”

    兒子:“真的,不騙我?”

    老婆:“當然!”

    兒子:“可是如果老爸不是傻子,當年怎麼會娶你這個母老虎呢?”

    我、老婆:……

什麼是代理模式

  所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構採取行動。在一些情況下,一個客戶不想或者不能直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的左右。

  代理模式:給某一個對象提供一個代理或占位符,並由代理對象來控制對原對象的訪問,通過代理對象訪問目標對象,這樣可以在不修改原目標對象的前提下,提供額外的功能操作,擴展目標對象的功能。說簡單點,代理模式就是設置一個中間代理來控制訪問原目標對象,以達到增強原對象的功能和簡化訪問方式。一般而言會分三種:靜態代理、動態代理和CGLIB代理

  代理模式結構如下:

靜態代理

  靜態代理需要代理對象和被代理對象實現一樣的介面,我們來看個例子就清楚了

  示例代理:static-proxy

  代理類:UserDaoProxy.java

/**
 * 代理邏輯在代理類中,而不是由用戶自定義
 */
public class UserDaoProxy implements IUserDao {

    private IUserDao target;            // 被代理對象

    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }

    /**
     *  前置/後置 處理一旦寫完,就固定死了,後續想修改的話需要改此代理類
     * @param id
     * @return
     */
    public int delete(int id) {
        // 前置處理,例如開啟事務
        System.out.println("前置處理...");

        // 調用目標對象方法
        int count = target.delete(id);

        // 後置處理,例如提交事務或事務回滾
        System.out.println("前置處理...");
        return count;
    }
}
View Code

  UserDaoProxy代理IUserDao類型,此時也只能代理IUserDao類型的被代理對象。測試結果就不展示了,相信大家看了代碼也知道了

  優點:可以在不修改目標對象的前提下擴展目標對象的功能

  缺點:如果需要代理多個類,每個類都會有一個代理類,會導致代理類無限制擴展;如果類中有多個方法,同樣的代理邏輯需要反覆實現、應用到每個方法上,一旦介面增加方法,目標對象與代理對象都要進行修改

  一個靜態代理只能代理一個類,那麼有沒有什麼方式可以實現同一個代理類來代理任意對象呢?肯定有的,也就是下麵講到的:動態代理

動態代理

  代理類在程式運行時創建的代理方式被成為動態代理。 也就是說,這種情況下,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。下麵我們一步一步手動來實現動態代理。下麵的示例都是直接針對介面的,就不是針對介面的具體實現類了,靜態代理示例中,UserDaoProxy代理的是IUserDao的實現類:UserDaoImpl,那麼動態代理示例就直接針對介面了,下麵示例針對的都是UserMapper介面,模擬的mybatis,但不局限於UserMapper介面

  代理類源代碼持久化

    1、先利用反射動態生成代理類,並持久化代理類到磁碟(也就是生成代理類的java源文件),generateJavaFile方法如下

/**
     * 生成介面實現類的源代碼, 並持久化到java文件
     * @param interface_
     * @param proxyJavaFileDir
     * @throws Exception
     */
    private static void generateJavaFile(Class<?> interface_, String proxyJavaFileDir) throws Exception {
        StringBuilder proxyJava = new StringBuilder();
        proxyJava.append("package ").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
                .append("public class ").append(PROXY_CLASS_NAME).append(" implements ").append(interface_.getName()).append(" {");
        Method[] methods = interface_.getMethods();
        for(Method method : methods) {
            Type returnType = method.getGenericReturnType();
            Type[] paramTypes = method.getGenericParameterTypes();
            proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
                    .append(TAB_STR).append("public ").append(returnType.getTypeName()).append(" ").append(method.getName()).append("(");
            for(int i=0; i<paramTypes.length; i++) {
                if (i != 0) {
                    proxyJava.append(", ");
                }
                proxyJava.append(paramTypes[i].getTypeName()).append(" param").append(i);
            }
            proxyJava.append(") {").append(ENTER)
                    .append(TAB_STR).append(TAB_STR)
                    .append("System.out.println(\"資料庫操作, 並獲取執行結果...\");").append(ENTER); // 真正資料庫操作,會有返回值,下麵的return返回應該是此返回值
            if (!"void".equals(returnType.getTypeName())) {
                proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);      // 這裡的"null"應該是上述中操作資料庫後的返回值,為了演示寫成了null
            }
            proxyJava.append(TAB_STR).append("}").append(ENTER);
        }
        proxyJava .append("}");

        // 寫入文件
        File f = new File(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
        FileWriter fw = new FileWriter(f);
        fw.write(proxyJava.toString());
        fw.flush();
        fw.close();
    }
View Code

      生成的代理類:$Proxy0.java 如下

package com.lee.mapper;

public class $Proxy0 implements com.lee.mapper.UserMapper {

    @Override
    public java.lang.Integer save(com.lee.model.User param0) {
        System.out.println("資料庫操作, 並獲取執行結果...");
        return null;
    }


    @Override
    public com.lee.model.User getUserById(java.lang.Integer param0) {
        System.out.println("資料庫操作, 並獲取執行結果...");
        return null;
    }
}
View Code

      這個代理類的生成過程是我們自己實現的,實現不難,但排版太繁瑣,我們可以用javapoet來生成代理類源代碼,generateJavaFileByJavaPoet方法如下

/**
 * 用JavaPoet生成介面實現類的源代碼,並持久化到java文件
 * @param interface_ 目標介面類
 * @return
 */
public static void generateJavaFileByJavaPoet(Class<?> interface_) throws Exception {

    // 類名、實現的介面,以及類訪問限定符
    TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("JavaPoet$Proxy0")
            .addSuperinterface(interface_)
            .addModifiers(Modifier.PUBLIC);

    Method[] methods = interface_.getDeclaredMethods();
    for (Method method : methods) {

        // 方法參數列表
        List<ParameterSpec> paramList = new ArrayList<>();
        Type[] paramTypes = method.getGenericParameterTypes();
        int count = 1 ;
        for (Type param : paramTypes) {
            ParameterSpec paramSpec = ParameterSpec.builder(Class.forName(param.getTypeName()), "param" + count)
                    .build();
            count ++;
            paramList.add(paramSpec);
        }

        // 方法名、方法訪問限定符、參數列表、返回值、方法體等
        Class<?> returnType = method.getReturnType();
        MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getName())
                .addModifiers(Modifier.PUBLIC)
                .addParameters(paramList)
                .addAnnotation(Override.class)
                .returns(returnType)
                .addCode("\n")
                .addStatement("$T.out.println(\"資料庫操作, 並獲取執行結果...\")", System.class)    // 真正資料庫操作,會有返回值,下麵的return返回應該是此返回值
                .addCode("\n");
        if (!"void".equals(returnType.getName())) {
            builder.addStatement("return null");       // 這裡的"null"應該是上述中操作資料庫後的返回值,為了演示寫成了null
        }

        MethodSpec methodSpec = builder.build();
        typeSpecBuilder.addMethod(methodSpec);

    }

    JavaFile javaFile = JavaFile.builder(interface_.getPackage().getName(), typeSpecBuilder.build()).build();
    javaFile.writeTo(new File(SRC_JAVA_PATH));
}
View Code

      生成的代理類:JavaPoet$Proxy0.java 如下

package com.lee.mapper;

import com.lee.model.User;
import java.lang.Integer;
import java.lang.Override;
import java.lang.System;

public class JavaPoet$Proxy0 implements UserMapper {
  @Override
  public Integer save(User param1) {

    System.out.println("資料庫操作, 並獲取執行結果...");

    return null;
  }

  @Override
  public User getUserById(Integer param1) {

    System.out.println("資料庫操作, 並獲取執行結果...");

    return null;
  }
}
View Code

    利用javapoet生成的代理類更接近我們平時手動實現的類,排版更符合我們的編碼習慣,看上去更自然一些;兩者的實現過程是一樣的,只是javapoet排版更好

    2、既然代理類的源代碼已經有了,那麼需要對其編譯了,compileJavaFile方法如下

/**
 * 編譯代理類源代碼生成代理類class
 * @param proxyJavaFileDir
 */
private static void compileJavaFile(String proxyJavaFileDir) throws Exception {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //獲得文件管理者
    StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
    Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
    //編譯任務
    JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, fileObjects);
    //開始編譯,執行完可在指定目錄下看到.class文件
    task.call();
    //關閉文件管理者
    manager.close();
}
View Code

      會在指定目錄下看到:$Proxy0.class

    3、載入$Proxy0.class,並創建其實例對象(代理實例對象)

public static <T> T newInstance(Class<T> interface_) throws Exception{
    String proxyJavaFileDir = SRC_JAVA_PATH + interface_.getPackage().getName().replace(".", File.separator) + File.separator;

    // 1、生成interface_介面的實現類,並持久化到磁碟:$Proxy0.java
    generateJavaFile(interface_, proxyJavaFileDir);

    // 2、編譯$Proxy0.java,生成$Proxy0.class到磁碟
    compileJavaFile(proxyJavaFileDir);

    // 3、載入$Proxy0.class,並創建其實例對象(代理實例對象)
    MyClassLoader loader = new MyClassLoader(proxyJavaFileDir, interface_);
    Class<?> $Proxy0 = loader.findClass(PROXY_CLASS_NAME);
    return (T)$Proxy0.newInstance();
}

private static class MyClassLoader<T> extends ClassLoader {

    private String proxyJavaFileDir;
    private Class<T> interface_;

    public MyClassLoader(String proxyJavaFileDir, Class<T> interface_) {
        this.proxyJavaFileDir = proxyJavaFileDir;
        this.interface_ = interface_;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        File clazzFile = new File(proxyJavaFileDir, name + ".class");
        //如果位元組碼文件存在
        if (clazzFile.exists()) {
            //把位元組碼文件載入到VM
            try {
                //文件流對接class文件
                FileInputStream inputStream = new FileInputStream(clazzFile);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);                     // 將buffer中的內容讀取到baos中的buffer
                }
                //將buffer中的位元組讀到記憶體載入為class
                return defineClass(interface_.getPackage().getName() + "." + name, baos.toByteArray(), 0, baos.size());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return super.findClass(name);
    }
}
View Code

      有了代理實例對象,我們就可以利用它進行操作了,演示結果如下

    完整工程地址:proxy-java-file,完整流程圖如下

    此時的Proxy類能創建任何介面的實例,解決了靜態代理存在的代理類泛濫、多個方法中代理邏輯反覆實現的問題;但有個問題不知道大家註意到:$Proxy0.java有必要持久化到磁碟嗎,我們能不能直接編譯記憶體中的代理類的字元串源代碼,得到$Proxy0.class呢?

  代理類源代碼不持久化

    $Proxy0.java和$Proxy0.class是沒必要生成到磁碟的,我們直接編譯記憶體中的代理類的字元串源代碼,同時直接在記憶體中載入$Proxy0.class,不用寫、讀磁碟,可以提升不少性能

    完整工程地址:proxy-none-java-file,此時的流程圖如下

    Proxy.java源代碼如下

package com.lee.proxy;

import com.itranswarp.compiler.JavaStringCompiler;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;

public class Proxy {

    private static final String ENTER = "\r\n";
    private static final String TAB_STR = "    ";
    private static final String PROXY_FILE_NAME = "$Proxy0";

    /**
     * 生成介面實現類的源代碼, 並持久化到java文件
     * @param interface_
     * @throws Exception
     */
    private static String generateJavaFile(Class<?> interface_) throws Exception {
        StringBuilder proxyJava = new StringBuilder();
        proxyJava.append("package ").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
                .append("public class ").append(PROXY_FILE_NAME).append(" implements ").append(interface_.getName()).append(" {");
        Method[] methods = interface_.getMethods();
        for(Method method : methods) {
            Type returnType = method.getGenericReturnType();
            Type[] paramTypes = method.getGenericParameterTypes();
            proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
                    .append(TAB_STR).append("public ").append(returnType.getTypeName()).append(" ").append(method.getName()).append("(");
            for(int i=0; i<paramTypes.length; i++) {
                if (i != 0) {
                    proxyJava.append(", ");
                }
                proxyJava.append(paramTypes[i].getTypeName()).append(" param").append(i);
            }
            proxyJava.append(") {").append(ENTER)
                    .append(TAB_STR).append(TAB_STR)
                    .append("System.out.println(\"資料庫操作, 並獲取執行結果...\");").append(ENTER); // 真正資料庫操作,會有返回值,下麵的return返回應該是此返回值
            if (!"void".equals(returnType.getTypeName())) {
                proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);      // 這裡的"null"應該是上述中操作資料庫後的返回值,為了演示寫成了null
            }
            proxyJava.append(TAB_STR).append("}").append(ENTER);
        }
        proxyJava .append("}");

        return proxyJava.toString();
    }

    private final static Class<?> compile(String className, String content) throws Exception {
        JavaStringCompiler compiler = new JavaStringCompiler();
        Map<String, byte[]> byteMap = compiler.compile(PROXY_FILE_NAME + ".java", content);
        Class<?> clazz = compiler.loadClass(className, byteMap);
        return clazz;
    }

    public static <T> T newInstance(Class<T> interface_) throws Exception{

        // 1、生成源代碼字元串
        String proxyCodeStr = generateJavaFile(interface_);

        // 2、字元串編譯成Class對象
        Class<?> clz = compile(interface_.getPackage().getName() + "." + PROXY_FILE_NAME, proxyCodeStr);
        return (T)clz.newInstance();
    }
}
View Code

    相比有代理類源代碼持久化,核心的動態代理生成過程不變,只是減少了.java和.class文件的持久化;其中用到了第三方工具:com.itranswarp.compile(我們也可以拓展jdk,實現記憶體中操作),完成了字元串在記憶體中的編譯、class在記憶體中的載入,直接用jdk的編譯工具,會在磁碟生成$Proxy0.class

    測試結果如下

      可以看到,沒有.java和.class的持久化

    此時就完美了嗎?如果現在有另外一個介面ISendMessage,代理邏輯不是

System.out.println("資料庫操作, 並獲取執行結果...")

    我們該怎麼辦? 針對ISendMessage又重新寫一個Proxy?顯然還不夠靈活,說的簡單點:此種代理可以代理任何介面,但是代理邏輯確是固定死的,不能自定義,這樣會造成一種代理邏輯會有一個代理工廠(Proxy),會造成代理工廠的泛濫

  代理邏輯介面化,供用戶自定義

    既然無代理類源代碼持久化中的代理邏輯不能自定義,那麼我們就將它抽出來,提供代理邏輯介面

    完整工程地址:proxy-none-java-file-plus,流程圖與無代理類源代碼持久化中一樣,此時代理類的生成過程複雜了不少,涉及到代理邏輯介面:InvacationHandler的處理

    generateJavaFile(...)方法

/**
 * 生成介面實現類的源代碼
 * @param interface_
 * @throws Exception
 */
private static String generateJavaFile(Class<?> interface_, InvocationHandler handler) throws Exception {
    StringBuilder proxyJava = new StringBuilder();
    proxyJava.append("package ").append(PROXY_PACKAGE_NAME).append(";").append(ENTER).append(ENTER)
            .append("import java.lang.reflect.Method;").append(ENTER).append(ENTER)
            .append("public class ").append(PROXY_FILE_NAME).append(" implements ").append(interface_.getName()).append(" {").append(ENTER)
            .append(ENTER).append(TAB_STR).append("private InvocationHandler  handler;").append(ENTER).append(ENTER);

    // 代理對象構造方法
    proxyJava.append(TAB_STR).append("public ").append(PROXY_FILE_NAME).append("(InvocationHandler handler) {").append(ENTER)
            .append(TAB_STR).append(TAB_STR).append("this.handler = handler;").append(ENTER)
            .append(TAB_STR).append("}").append(ENTER);

    // 介面方法
    Method[] methods = interface_.getMethods();
    for(Method method : methods) {
        String returnTypeName = method.getGenericReturnType().getTypeName();
        Type[] paramTypes = method.getGenericParameterTypes();
        proxyJava.append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
                .append(TAB_STR).append("public ").append(returnTypeName).append(" ").append(method.getName()).append("(");

        List<String> paramList = new ArrayList<>();     // 方法參數值
        List<String> paramTypeList = new ArrayList<>(); // 方法參數類型
        for(int i=0; i<paramTypes.length; i++) {
            if (i != 0) {
                proxyJava.append(", ");
            }
            String typeName = paramTypes[i].getTypeName();
            proxyJava.append(typeName).append(" param").append(i);
            paramList.add("param" + i);
            paramTypeList.add(typeName+".class");
        }
        proxyJava.append(") {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("try {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append(TAB_STR)
                .append("Method method = ").append(interface_.getName()).append(".class.getDeclaredMethod(\"")
                .append(method.getName()).append("\",").append(String.join(",", paramTypeList)).append(");")
                .append(ENTER).append(TAB_STR).append(TAB_STR).append(TAB_STR);
        if (!"void".equals(returnTypeName)) {
            proxyJava.append("return (").append(returnTypeName).append(")");
        }
        proxyJava.append("handler.invoke(this, method, new Object[]{")
                .append(String.join(",", paramList)).append("});").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("} catch(Exception e) {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append(TAB_STR).append("e.printStackTrace();").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("}").append(ENTER);
        if (!"void".equals(returnTypeName)) {
            proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);
        }
        proxyJava.append(TAB_STR).append("}").append(ENTER);
    }
    proxyJava .append("}");

    // 這裡可以將字元串生成java文件,看看源代碼對不對
    /*String proxyJavaFileDir = System.getProperty("user.dir") + File.separator + "proxy-none-java-file-plus"
            + String.join(File.separator, new String[]{"","src","main","java",""})
            + PROXY_PACKAGE_NAME.replace(".", File.separator) + File.separator;
    File f = new File(proxyJavaFileDir + PROXY_FILE_NAME + ".java");
    FileWriter fw = new FileWriter(f);
    fw.write(proxyJava.toString());
    fw.flush();
    fw.close();*/

    return proxyJava.toString();
}
View Code

    測試結果如下

    此時各組件之間關係、調用情況如下

    此時Proxy就可以完全通用了,可以生成任何介面的代理對象了,也可以實現任意的代理邏輯;至此,我們完成了一個簡易的仿JDK實現的動態代理

  JDK的動態代理

    我們來看看JDK下動態代理的實現,示例工程:proxy-jdk,測試結果就不展示了,我們來看看JDK下Proxy.newInstance方法,有三個參數

      1、Classloader:類載入器,我們可以使用自定義的類載入器;上述手動實現示例中,直接在Proxy寫死了;

      2、Class<?>[]:介面類數組,這個其實很容易理解,我們應該允許我們自己實現的代理類同時實現多個介面。我們上述手動實現中只傳入一個介面,是為了簡化實現;

      3、InvocationHandler:這個沒什麼好說的,與我們的實現一致,用於自定義代理邏輯

    我們來追下源碼,看看JDK的動態代理是否與我們的手動實現是否一致

    與我們的自定義實現差不多,利用反射,逐個介面、逐個方法進行處理;ProxyClassFactory負責生成代理類的Class對象,主要有apply方法負責,調用了

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

    來生成代理類的Class;ProxyGenerator中有個是有靜態常量:saveGeneratedFiles,標識是否持久化代理類的class文件,預設值是false,也就是不持久化,我們可以通過設置jdk系統參數,實現JDK的動態代理持久化代理類的class文件

CGLIB代理

  對cglib不做深入研究了,只舉個使用案例:proxy-cglib,使用方式與JDK的動態代理類似,實現的效果也基本一致,但是實現原理上還是有差別的

  JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個介面,而CGLIB沒有這個限制,具體區別不是本文範疇了,大家自行去查閱資料

應用場景

  長篇大論講了那麼多,我們卻一直沒有講動態代理的作用,使用動態代理我們可以在不改變源碼的情況下,對目標對象的目標方法進行前置或後置增強處理。這有點不太符合我們的一條線走到底的編程邏輯,這種編程模型有一個專業名稱叫AOP,面向切麵編程,具體案例有如下:

  1、spring的事務,事務的開啟可以作為前置增強,事務的提交或回滾作為後置增強,資料庫的操作處在兩者之前;

  2、日誌記錄,我們可以在不改變原有實現的基礎上,對目標對象進行日誌的輸出,可以前置處理,記錄參數情況,也可以後置處理,記錄返回的結果;

  3、web編程,傳入參數的校驗;

  4、web編程,許可權的控制也可以用aop來實現;

  只要明白了AOP,那麼哪些場景能使用動態代理也就比較明瞭了

總結

  1、示例代碼中的Proxy是代理工廠,負責生產代理對象的,不是代理對象類

  2、手動實現動態代理,我們分了三版

    第一版:代理類源代碼持久化,為了便於理解,我們將代理類的java文件和class文件持久化到了磁碟,此時解決了靜態代理中代理類泛濫的問題,我們的代理類工廠(Proxy)能代理任何介面;

    第二版:代理類源代碼不持久化,代理類的java文件和和class文件本來就只是臨時文件,將其去掉,不用讀寫磁碟,可以提高效率;但此時有個問題,我們的代理邏輯卻寫死了,也就說一個代理類工廠只能生產一種代理邏輯的代理類對象,如果我們有多種代理邏輯,那麼就需要有多個代理類工廠,顯然靈活性不夠高,還有優化空間;

    第三版:代理邏輯介面化,供用戶自定義,此時代理類工廠就可以代理任何介面、任何代理邏輯了,反正代理邏輯是用戶自定義傳入,用戶想怎麼定義就怎麼定義;

  3、示例參考的是mybatis中mapper的生成過程,雖然只是簡單的模擬,但流程卻是一致的,有興趣的可以看看我前兩篇博客,結合起來看更好理解

參考

  《java與模式》

  10分鐘看懂動態代理設計模式


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

-Advertisement-
Play Games
更多相關文章
  • css對齊方案總結 css對齊方案總結 垂直居中 通用佈局方式(內斂元素和塊狀元素都適用) 利用flex:核心代碼: 12345 .container{ display:flex; flex-direction:column; justify:center} 利用transformX(-50%):核 ...
  • 原型鏈 原型鏈 引入從Object和Function開始 Object和Function都作為JS的自帶函數,Object繼承自己,Funtion繼承自己,Object和Function互相是繼承對方,也就是說Object和Function都既是函數也是對象。 12 console.log(Func ...
  • 數組 數組 1.數組:數組是一組數據(數據類型不限,任意)的有序集合 >我們寫代碼,一般一個數組只放一種數據類型的數據 2.我們寫代碼,一般一個數組只放一種類型的數據 3.註意: 大多數的語言裡面數組的存儲是連續的,但是js的數組特點決定了js的數組不一定是連續的。 數組的特點 1.作用:將許多零散 ...
  • 問:JavaScript 如何查找對象中某個 value 並返迴路徑上所有的 key? 有例如上面這樣一個對象,要求封裝一個函數,傳入對象和某個 value,返回該 value 路徑上的 key。比如:searchKeys(obj, "str3"),得到 "key3, key2"。—— 來源於 @z ...
  • 一、Angular2框架的開發語言 Angular2是谷歌開發的一套前端框架,Angular2就是用Typescript語言的寫的。因此,Typescript語言幫你更好的學習angular2框架。 二、支持ES6 Typescript支持ES6規範的語言,ES6規範指出未來客戶端腳本語言的發展方向 ...
  • 這兩天剛把適配器模式與外觀模式學習了一遍,記錄一下自己在學習中的思考。 適配器設計模式與外觀設計模式所涉及到的一個設計原則: 最少知識原則:不要讓太多的類耦合在一起,以免當修改了某一部分後,會影響到其他部分。 對於任何對象而言,在該對象的方法內,其中最少所指的範圍: 1. 該對象本身; 2.被當作方 ...
  • 題意 "題目鏈接" Sol yy出了一個暴躁線段樹的做法。 因為題目保證了 $a_i + k_i define Pair pair define MP(x, y) make_pair(x, y) define fi first define se second define int long lon ...
  • 先給出十轉二的除法 2 60 30 0 15 0 7 1 3 1 1 1 0 1 60轉二 111100 再介紹位運算符 a=60 b=13 A = 0011 1100 B = 0000 1101 A&b = 0000 1100A | B = 0011 1101A ^ B = 0011 0001~A ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...