代理模式——JDK動態代理與CGLib原理及對比分析

来源:https://www.cnblogs.com/zjfjava/archive/2022/10/16/16795493.html
-Advertisement-
Play Games

1.前言 首先回顧下代理模式(Proxy Pattern)的定義:代理模式指為其他對象提供一種代理,以控制這個對象的訪問,屬於結構型設計模式。其適用於在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端於目標對象之間起到中介的作用。 代理模式主要分為靜態代理和動態代理兩種方 ...


1.前言

首先回顧下代理模式(Proxy Pattern)的定義:代理模式指為其他對象提供一種代理,以控制這個對象的訪問,屬於結構型設計模式。其適用於在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端於目標對象之間起到中介的作用。

代理模式主要分為靜態代理和動態代理兩種方式,靜態代理需要手動創建代理類,代理的目標對象是固定的;動態代理使用反射機制,代理的目標對象是活動的,不需要創建代理類即可給不同的目標隨時創建代理。本篇重點探究動態代理的實現。

2.JDK動態代理

JDK動態代理採用位元組重組,重新生成對象來替代原始對象,以達到動態代理的目的。JDK動態代理生成對象的步驟如下:

  1. 獲取被代理對象的引用,並且獲取它的所有介面,反射獲取。
  2. JDK動態代理類重新生成一個新的類,同時新的類要實現被代理類實現的所有介面。
  3. 動態生成Java代碼,新加的業務邏輯方法由一定的邏輯代碼調用(在代碼中體現)。
  4. 編譯新生成的Java代碼.class文件。
  5. 重新載入到JVM中運行。

2.1 JDK動態代理實現及原理源碼解析

實現一個JDK動態代理,方式為實現java.lang.reflect.InvocationHandler介面,並使用java.lang.reflect.Proxy.newProxyInstance()方法生成代理對象。

/**
* 要代理的介面
*/
public interface IPerson {
    void learn();
}

/**
* 真實調用類
*/
public class Zhangsan implements IPerson {
    public void learn() {
        System.out.println("==張三學習中間件==");
    }
}

/**
* JDK代理類生成
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkInvocationHandler implements InvocationHandler {
    private IPerson target;
    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz =  target.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    }
    private void before() {
        System.out.println("事前做好計劃");
    }
    
    private void after() {
        System.out.println("事後回顧梳理");
    }
}

/**
* 測試
*/
public class TestProxy {
    public static void main(String[] args) {
        try {
            //把生成的位元組碼保存到本地磁碟,動態生成的類會保存在工程根目錄下的 com/sun/proxy 目錄裡面
            System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
            IPerson obj = (IPerson) new JdkInvocationHandler().getInstance(new Zhangsan());
            obj.learn();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

看下 Proxy.newProxyInstance 裡面究竟發生了什麼?

結合流程圖,在生成位元組碼的那個地方,也就是 ProxyGenerator.generateProxyClass() 方法裡面,通過代碼可以看到(自行查閱,篇幅原因,這裡不貼代碼),裡面是用參數 saveGeneratedFiles 來控制是否把生成的位元組碼保存到本地磁碟。代碼中已經設置保存到本地,現在找到剛纔生成的 $Proxy0.class,反編譯打開如下:

import com.zang.jdkproxy.IPerson;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IPerson {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    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 void learn() throws  {
        try {
           // super.h 對應的是父類的h變數,也就是Proxy.newProxyInstance方法中的InvocationHandler參數
           // 所以這裡實際上就是使用了我們自己寫的InvocationHandler實現類的invoke方法
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            m3 = Class.forName("com.zang.jdkproxy.IPerson").getMethod("learn");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到 $Proxy0類繼承了Proxy類,裡面有一個跟IPerson一樣簽名的 learn 方法,方法實現中的super.h.invoke(this, m3, (Object[])null);,super.h 對應的是父類的h變數,也就是Proxy.newProxyInstance方法中的InvocationHandler參數:

package java.lang.reflect;
//import略

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

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

        final Class<?>[] intfs = interfaces.clone();
        //

所以這裡實際上就是使用了我自己寫的InvocationHandler實現類JdkInvocationHandlerinvoke方法,當調用 IPerson.learn的時候,其實它是被轉發到了 JdkInvocationHandler.invoke。至此,整個魔術過程就透明瞭。

2.2 手寫JDK動態代理

使用JDK動態代理的類名和方法名定義以及執行思路,下麵來進行手寫實現。

創建MyInvocationHandler介面:

import java.lang.reflect.Method;

public interface MyInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

創建MyProxy類:

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 自己實現的代理類,用來生成位元組碼文件,並動態載入到JVM中
 */
public class MyProxy {

    public static final String ln = "\r\n";

    /**
     * 生成代理對象
     * @param classLoader 類載入器,用於載入被代理類的類文件
     * @param interfaces 被代理類的介面
     * @param h 自定義的InvocationHandler介面,用於具體代理方法的執行
     * @return 返回被代理後的代理對象
     */
    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h) {
        try {
        //1、動態生成源代碼.java文件
            String src = generateSrc(interfaces);
        //2、Java文件輸出磁碟
            String filePath = MyProxy.class.getResource("").getPath();

            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

        //3、把生成的.java文件編譯成.class文件
            //獲取Java編譯器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            //標註Java文件管理器,用來獲取Java位元組碼文件
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);
            //創建task,通過java位元組碼文件將類信息載入到JVM中
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            //開始執行task
            task.call();
            //關閉管理器
            manage.close();

        //4、編譯生成的.class文件載入到JVM中來
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            f.delete();

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

    /**
     * 生成代理類的源代碼
     */
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append(MyProxy.class.getPackage() + ";" + ln);
        sb.append("import " + interfaces[0].getName() + ";" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("GPInvocationHandler h;" + ln);
        sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
        sb.append("this.h = h;");
        sb.append("}" + ln);
        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();

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

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }

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


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

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

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}

創建類載入器MyClassLoader:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader {

    private File classPathFile;
    public MyClassLoader(){
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    /**
     * 通過類名稱載入類位元組碼文件到JVM中
     * @param name 類名
     * @return 類的Class獨享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //獲取類名
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if(classPathFile  != null){
            //獲取類文件
            File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
            if(classFile.exists()){
                //將類文件轉化為位元組數組
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte [] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1){
                        out.write(buff,0,len);
                    }
                    //調用父類方法生成class實例
                    return defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

實現並測試

/**
* 要代理的介面
*/
public interface IPerson {
    void learn();
}

/**
* 真實調用類
*/
public class Zhangsan implements IPerson {
    public void learn() {
        System.out.println("==張三學習中間件==");
    }
}

/**
* JDK代理類生成
*/
public class CustomInvocationHandler implements MyInvocationHandler {
    private IPerson target;
    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz =  target.getClass();
        return (IPerson) MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    }

    private void before() {
        System.out.println("事前做好計劃");
    }
    
    private void after() {
        System.out.println("事後回顧梳理");
    }

}

/**
* 測試
*/
public class Test {
    public static void main(String[] args) {
        CustomInvocationHandler custom = new CustomInvocationHandler();
        IPerson zhangsan = custom.getInstance(new Zhangsan());
        zhangsan.learn();
    }
}

至此,手寫完成,讀者也可自行參照實現。

3.CGLib動態代理API原理分析

3.1 CGLib動態代理的使用

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CustomCGlib implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception{
        //相當於Proxy,代理的工具類
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    private void before() {
        System.out.println("事前做好計劃");

    }

    private void after() {
        System.out.println("事後回顧梳理");
    }
}

這裡有一個小細節,CGLib動態代理的目標對象不需要實現任何介面,它是通過動態繼承目標對象實現動態代理的,客戶端測試代碼如下:

public class CglibTest {
    public static void main(String[] args) {
        try {
            Zhangsan obj = (Zhangsan) new CustomCGlib().getInstance(Zhangsan.class);
            obj.learn();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2 CGLib動態代理的實現原理

CGLib動態代理的實現原理又是怎樣的呢?可以在客戶端測試代碼中加上一句代碼,將CGLib動態代理後的.class文件寫入磁碟,然後反編譯來一探究竟,代碼如下:

//import net.sf.cglib.core.DebuggingClassWriter;
//使用CGLib的代理類可以將記憶體中的.class文件寫入本地磁碟
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
Zhangsan obj = ···
//···

重新執行代碼,再輸出目錄下會出現三個.class文件,一個是目標(被代理)類的FastClass,一個是代理類,一個是代理類的FastClass。如圖:

其中,Zhangsan$$EnhancerByCGLIB$$3d23e0ea.class就是CGLib動態代理生成的代理類,繼承了Zhangsan類。

package com.zang.cglibproxy;

import java.lang.reflect.Method;
import net.sf.cglib.*;

public class Zhangsan$$EnhancerByCGLIB$$3d23e0ea extends Zhangsan implements Factory {
	//···
   //傳入的MethodInterceptor對象      
   private MethodInterceptor CGLIB$CALLBACK_0;
   //目標類的learn方法對象  
   private static final Method CGLIB$learn$0$Method;
   //代理類的learn方法對象  
   private static final MethodProxy CGLIB$learn$0$Proxy;
   private static final Object[] CGLIB$emptyArgs;

   //初始化方法,其中部分代碼略  
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.zang.cglibproxy.Zhangsan$$EnhancerByCGLIB$$78b38660");
        Class var1;
        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());
        //···
        //初始化目標類的learn方法對象
        CGLIB$learn$0$Method = ReflectUtils.findMethods(new String[]{"learn", "()V"}, (var1 = Class.forName("com.zang.cglibproxy.Zhangsan")).getDeclaredMethods())[0];
        //初始化代理類的learn方法對象
        CGLIB$learn$0$Proxy = MethodProxy.create(var1, var0, "()V", "learn", "CGLIB$learn$0");
    }
    
    //這裡直接調用Zhangsan#learn
    final void CGLIB$learn$0() {
        super.learn();
    }

    public final void learn() {
        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$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
        } else {
            super.learn();
        }
    }
  //···
}

調用過程為:代理對象調用this.learn方法→調用攔截器→methodProxy.invokeSuper()CGLIB$learn$0→被代理對象learn方法。

package net.sf.cglib.proxy;

import java.lang.reflect.Method;

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
public class CustomCGlib implements MethodInterceptor {
	//···
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }
    //···
}

MethodInterceptor攔截器就是由MethodProxyinvokeSuper方法調用代理方法的,因此,MethodProxy類中的代碼非常關鍵,下麵分析它具體做了什麼:

package net.sf.cglib.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.*;

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private MethodProxy.CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile MethodProxy.FastClassInfo fastClassInfo;

   private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    //創建目標類的FastClass對象(在緩存中,則取出;沒在,則重新生成)
                    fci.f1 = helper(ci, ci.c1);
                    //創建代理類的FastClass對象
                    fci.f2 = helper(ci, ci.c2);
                    //獲取learn方法的索引
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    //獲取CGLIB$learn$0方法的索引
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                }
            }
        }

    }
    
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            //初始化,創建了兩個FastClass類對象
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            //這裡將直接調用代理類的CGLIB$learn$0方法,而不是通過反射調用
            //fci.f2:代理類的FastClass對象,fci.i2為CGLIB$learn$0方法對應的索引,obj為當前的代理類對象,args為learn方法的參數列表
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

上面代碼調用獲取代理類對應的FastClass,並執行代理方法。還記得之前生成的三個.class文件嗎?Zhangsan$$EnhancerByCGLIB$$78b38660$$FastClassByCGLIB$$a8f9873c.class就是代理類的FastClass,Zhangsan$$FastClassByCGLIB$$bcf7b1f4.class就是目標類的FastClass。

CGLib動態代理執行代理方法的效率之所以比JDK高,是因為CGlib採用了FastClass機制,它的原理簡單來說就是:為代理類和被代理類各生成一個類,這個類會為代理類或被代理類的方法分配一個index(int類型);這個index被當作一個入參,FastClass可以直接定位要調用的方法並直接進行調用,省去了反射調用,因此調用效率比JDK動態代理通過反射調用高(並不絕對,還需參考JDK版本及使用場景來說)。下麵來反編譯一個FastClass。

public class Zhangsan$$FastClassByCGLIB$$bcf7b1f4 extends FastClass {
    public Zhangsan$$FastClassByCGLIB$$bcf7b1f4(Class var1) {
        super(var1);
    }

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case 1574139569:
            if (var10000.equals("learn()V")) {
                //learn方法返回0
                return 0;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
   //···
            }
        }
    }
    
    //根據index獲取方法
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        Zhangsan var10000 = (Zhangsan)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                //傳入index為0則執行learn方法
                var10000.learn();
                return null;
            case 1:
                return new Boolean(var10000.equals(var3[0]));
   //···           
                    

FastClass並不是跟代理類一起生成的,而是在第一次執行MethodProxyinvokeinvokeSuper方法時生成的,並被放在了緩存中。

4.總結

通過上面的分析,相信會對兩種動態代理的實現原理有一個深入的認識,總結性比較兩者的區別如下:

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

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

-Advertisement-
Play Games
更多相關文章
  • nginx配置文件rewrite和if rewrite 語法:rewrite regex replacement flag;,如: rewrite ^/images/(.*\.jpg)$ /imgs/$1 break; 此處的$1用於引用(.*.jpg)匹配到的內容,又如: rewrite ^/bb ...
  • 1、下載安裝包(官網下載) 直達鏈接:https://dev.mysql.com/downloads/mysql/ 下載後放到指定目錄下解壓即可(給電腦新手忠告:註意不要放在C盤,養成好習慣,放C盤多了會影響電腦運行速度) 像我放D盤: 2、安裝過程 2.1、配置環境 變數名:MYSQL_HOME ...
  • 隨著系統運行時間的推移,資料庫日誌文件會變得越來越大,這時我們需要對日誌文件進行備份或清理。 解決方案1 - 直接刪除本地ldf日誌文件:(比較靠譜方案!) 1. 在SQL管理器分離資料庫。 2. 對資料庫日誌文件進行壓縮備份(rar, zip) 3. 直接刪除ldf文件。 4. 再附加資料庫。若出 ...
  • MongoDB 是一個強大的分散式存儲引擎,天然支持高可用、分散式和靈活設計。MongoDB 的一個很重要的設計理念是:服務端只關註底層核心能力的輸出,至於怎麼用,就儘可能的將工作交個客戶端去決策。這也就是 MongoDB 靈活性的保證,但是靈活性帶來的代價就是使用成本的提升。與 MySql 相比,... ...
  • 我們在安卓開發學習中會遇到需要返回數據的情況,這裡我們使用了幾個方法 1、startActivityForResult通過這個方法我們可以啟動另外一個活動 2、onBasePressed使用這個方法我們可以 點擊返回鍵返回數據到上一個活動 3、onActivityResult我們在需要接收返回數據的 ...
  • 語法規範 JavaScript嚴格區分大小寫,對空格、換行、縮進不敏感,建議語句結束加‘;’ JavaScript 會忽略多個空格。您可以向腳本添加空格,以增強可讀性。 JavaScript 程式員傾向於使用以小寫字母開頭的駝峰大小寫 firstName, lastName, masterCard, ...
  • Openlayers介紹 ​ Openlayers是一個基於Javacript開發,免費、開源的前端地圖開發庫,使用它,可以很容易的開發出WebGIS系統。目前Openlayers支持地圖瓦片、矢量數據等眾多地圖數據格式,支持比較完整的地圖交互操作。目前OpenLayers已經成為一個擁有眾多開發者 ...
  • 一篇文章帶你掌握主流辦公框架——SpringBoot 在之前的文章中我們已經學習了SSM的全部內容以及相關整合 SSM是Spring的產品,主要用來簡化開發,但我們現在所介紹的這款框架——SpringBoot,卻是用來簡化Spring開發的框架 SpringBoot是由Pivowtal團隊提供的全新 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...