欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

CGLIB代理的使用與原理解析

 更新時(shí)間:2023年09月20日 09:52:33   作者:流煙默  
這篇文章主要介紹了CGLIB代理的使用與原理解析,靜態(tài)代理和JDK 代理模式都要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候可使用目標(biāo)對(duì)象子類來實(shí)現(xiàn)代理,這就是Cglib代理,需要的朋友可以參考下

CGLIB概述

Cglib代理

靜態(tài)代理和JDK 代理模式都要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候可使用目標(biāo)對(duì)象子類來實(shí)現(xiàn)代理-這就是Cglib 代理。

JDK中提供的生成動(dòng)態(tài)代理類的機(jī)制有個(gè)鮮明的特點(diǎn)是:

  • 某個(gè)類必須有實(shí)現(xiàn)的接口
  • 生成的代理類也只能代理某個(gè)類接口定義的方法。

那么如果一個(gè)類沒有實(shí)現(xiàn)接口怎么辦呢?

這就有CGLIB的誕生了,前面說的JDK的動(dòng)態(tài)代理的實(shí)現(xiàn)方式是實(shí)現(xiàn)相關(guān)的接口成為接口的實(shí)現(xiàn)類,那么我們自然可以想到用繼承的方式實(shí)現(xiàn)相關(guān)的代理類。

Cglib 代理也叫作子類代理,它是在內(nèi)存中構(gòu)建一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能擴(kuò)展, 有些書也將Cglib 代理歸屬到動(dòng)態(tài)代理。

CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類,覆蓋其中的方法。因?yàn)槭抢^承,所以該類或方法最好不要聲明成final, static方法,private方法,final方法是不能被代理的

Cglib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展java 類與實(shí)現(xiàn)java 接口.它廣泛的被許多AOP 的框架使用,例如Spring AOP,實(shí)現(xiàn)方法攔截。

在AOP 編程中如何選擇代理模式:

  • 目標(biāo)對(duì)象需要實(shí)現(xiàn)接口,用JDK 代理
  • 目標(biāo)對(duì)象不需要實(shí)現(xiàn)接口,用Cglib 代理

Cglib 包的底層是通過使用字節(jié)碼處理框架ASM 來轉(zhuǎn)換字節(jié)碼并生成新的類

應(yīng)用案例

pom依賴

 <!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

在JDK動(dòng)態(tài)代理的代碼基礎(chǔ)上進(jìn)行修改

① 測(cè)試客戶端

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestCglibProxy {
	public static void main(String[] args) {
		//創(chuàng)建一個(gè)被代理類的對(duì)象
		SuperMan man = new SuperMan();
		CGLibProxy cgLibProxy = new CGLibProxy();
		//返回一個(gè)代理類的對(duì)象--注意這里現(xiàn)在傳入的是實(shí)現(xiàn)類
		Object obj = cgLibProxy.getProxyInstance(man);
		System.out.println(obj.getClass());
		//class com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240
		Human hu = (Human)obj;
		//通過代理類的對(duì)象調(diào)用重寫的抽象方法
		hu.info();
		System.out.println();
		hu.fly();
	}
}

② 自定義CGLibProxy

class CGLibProxy implements MethodInterceptor {  
	 // CGLib需要代理的目標(biāo)對(duì)象  
    private Object targetObject;
    public Object getProxyInstance(Object obj) {  
        this.targetObject = obj;  
        //1. 創(chuàng)建一個(gè)工具類
        Enhancer enhancer = new Enhancer();  
        // 2.設(shè)置父類--可以是類或者接口
        enhancer.setSuperclass(obj.getClass());  
        //3. 設(shè)置回調(diào)函數(shù)
        enhancer.setCallback(this);  
        //4. 創(chuàng)建子類對(duì)象,即代理對(duì)象
        Object proxyObj = enhancer.create();  
        // 返回代理對(duì)象  
        return proxyObj;
    }  
    public Object intercept(Object proxy, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {  
        Object obj = null;  
        //模擬功能增強(qiáng)
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();
        // 執(zhí)行目標(biāo)目標(biāo)對(duì)象方法
        obj = method.invoke(targetObject, args);
        //模擬功能增強(qiáng)
        humanUtil.method2();
        return obj;  
    }  
}

HumanUtil 如下:

class HumanUtil {
	public void method1() {
		System.out.println("=======方法一=======");
	}
	public void method2() {
		System.out.println("=======方法二=======");
	}
}

測(cè)試結(jié)果

如下所示:

class com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240
=======方法一=======
我是超人!我怕誰!
=======方法二=======
=======方法一=======
I believe I can fly!
=======方法二=======

獲取代理類源碼

有了源碼才好分析驗(yàn)證cglib生成的代理類究竟是個(gè)什么樣子?這里主要用到下面代碼:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);

修改上面的main方法如下:

public static void main(String[] args) {
		SuperMan man = new SuperMan();//創(chuàng)建一個(gè)被代理類的對(duì)象
		// 添加如下代碼,獲取代理類源文件
		String path = CGLibProxy.class.getResource(".").getPath();  
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
		CGLibProxy cgLibProxy = new CGLibProxy();
		Object obj = cgLibProxy.bind(man);//返回一個(gè)代理類的對(duì)象
		System.out.println(obj.getClass());
		//class com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240
		Human hu = (Human)obj;
		hu.info();//通過代理類的對(duì)象調(diào)用重寫的抽象方法
		System.out.println();
		hu.fly();
	}

測(cè)試結(jié)果如下:

這里寫圖片描述

生成的代理類名字 SuperMan$$EnhancerByCGLIB$$3be74240 ,源碼如下:

可以直接在//javare.cn/網(wǎng)站下在線反編譯。

package com.web.test;
import com.web.test.SuperMan;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//繼承目標(biāo)被代理類
public class SuperMan$$EnhancerByCGLIB$$3be74240 extends SuperMan implements Factory {
   private boolean CGLIB$BOUND;
   public static Object CGLIB$FACTORY_DATA;
   private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
   private static final Callback[] CGLIB$STATIC_CALLBACKS;
   private MethodInterceptor CGLIB$CALLBACK_0;
   private static Object CGLIB$CALLBACK_FILTER;
   private static final Method CGLIB$info$0$Method;
   private static final MethodProxy CGLIB$info$0$Proxy;
   private static final Object[] CGLIB$emptyArgs;
   private static final Method CGLIB$fly$1$Method;
   private static final MethodProxy CGLIB$fly$1$Proxy;
   private static final Method CGLIB$equals$2$Method;
   private static final MethodProxy CGLIB$equals$2$Proxy;
   private static final Method CGLIB$toString$3$Method;
   private static final MethodProxy CGLIB$toString$3$Proxy;
   private static final Method CGLIB$hashCode$4$Method;
   private static final MethodProxy CGLIB$hashCode$4$Proxy;
   private static final Method CGLIB$clone$5$Method;
   private static final MethodProxy CGLIB$clone$5$Proxy;
// 一系列私有靜態(tài)常量定義
// 常量初始化
   static void CGLIB$STATICHOOK1() {
      CGLIB$THREAD_CALLBACKS = new ThreadLocal();
      CGLIB$emptyArgs = new Object[0];
      Class var0 = Class.forName("com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240");
      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());
      CGLIB$equals$2$Method = var10000[0];
      CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
      CGLIB$toString$3$Method = var10000[1];
      CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
      CGLIB$hashCode$4$Method = var10000[2];
      CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
      CGLIB$clone$5$Method = var10000[3];
      CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
      var10000 = ReflectUtils.findMethods(new String[]{"info", "()V", "fly", "()V"}, (var1 = Class.forName("com.web.test.SuperMan")).getDeclaredMethods());
      CGLIB$info$0$Method = var10000[0];
      CGLIB$info$0$Proxy = MethodProxy.create(var1, var0, "()V", "info", "CGLIB$info$0");
      CGLIB$fly$1$Method = var10000[1];
      CGLIB$fly$1$Proxy = MethodProxy.create(var1, var0, "()V", "fly", "CGLIB$fly$1");
   }
//綁定MethodInterceptor callback的方法會(huì)額外實(shí)現(xiàn)一個(gè)和原方法一模一樣的方法
   final void CGLIB$info$0() {
      super.info();
   }
// 代理對(duì)象的方法調(diào)用將會(huì)轉(zhuǎn)發(fā)到代理對(duì)象的intercept方法
   public final void info() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
      } else {
         super.info();
      }
   }
   final void CGLIB$fly$1() {
      super.fly();
   }
   public final void fly() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$fly$1$Method, CGLIB$emptyArgs, CGLIB$fly$1$Proxy);
      } else {
         super.fly();
      }
   }
   final boolean CGLIB$equals$2(Object var1) {
      return super.equals(var1);
   }
   public final boolean equals(Object var1) {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         Object var2 = var10000.intercept(this, CGLIB$equals$2$Method, new Object[]{var1}, CGLIB$equals$2$Proxy);
         return var2 == null?false:((Boolean)var2).booleanValue();
      } else {
         return super.equals(var1);
      }
   }
   final String CGLIB$toString$3() {
      return super.toString();
   }
   public final String toString() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy):super.toString();
   }
   final int CGLIB$hashCode$4() {
      return super.hashCode();
   }
   public final int hashCode() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         Object var1 = var10000.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
         return var1 == null?0:((Number)var1).intValue();
      } else {
         return super.hashCode();
      }
   }
   final Object CGLIB$clone$5() throws CloneNotSupportedException {
      return super.clone();
   }
   protected final Object clone() throws CloneNotSupportedException {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      return var10000 != null?var10000.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy):super.clone();
   }
// 獲取方法的 MethodProxy
   public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
      String var10000 = var0.toString();
      switch(var10000.hashCode()) {
      case -1271409118:
         if(var10000.equals("fly()V")) {
            return CGLIB$fly$1$Proxy;
         }
         break;
      case -508378822:
         if(var10000.equals("clone()Ljava/lang/Object;")) {
            return CGLIB$clone$5$Proxy;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return CGLIB$equals$2$Proxy;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return CGLIB$toString$3$Proxy;
         }
         break;
      case 1945358343:
         if(var10000.equals("info()V")) {
            return CGLIB$info$0$Proxy;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return CGLIB$hashCode$4$Proxy;
         }
      }
      return null;
   }
	//無參構(gòu)造器
   public SuperMan$$EnhancerByCGLIB$$3be74240() {
      CGLIB$BIND_CALLBACKS(this);
   }
   public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
      CGLIB$THREAD_CALLBACKS.set(var0);
   }
   public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
      CGLIB$STATIC_CALLBACKS = var0;
   }
   private static final void CGLIB$BIND_CALLBACKS(Object var0) {
      SuperMan$$EnhancerByCGLIB$$3be74240 var1 = (SuperMan$$EnhancerByCGLIB$$3be74240)var0;
      if(!var1.CGLIB$BOUND) {
         var1.CGLIB$BOUND = true;
         Object var10000 = CGLIB$THREAD_CALLBACKS.get();
         if(var10000 == null) {
            var10000 = CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS == null) {
               return;
            }
         }
         var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
      }
   }
   public Object newInstance(Callback[] var1) {
      CGLIB$SET_THREAD_CALLBACKS(var1);
      SuperMan$$EnhancerByCGLIB$$3be74240 var10000 = new SuperMan$$EnhancerByCGLIB$$3be74240();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }
   public Object newInstance(Callback var1) {
      CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
      SuperMan$$EnhancerByCGLIB$$3be74240 var10000 = new SuperMan$$EnhancerByCGLIB$$3be74240();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }
   public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
      CGLIB$SET_THREAD_CALLBACKS(var3);
      SuperMan$$EnhancerByCGLIB$$3be74240 var10000 = new SuperMan$$EnhancerByCGLIB$$3be74240;
      switch(var1.length) {
      case 0:
         var10000.<init>();
         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
         return var10000;
      default:
         throw new IllegalArgumentException("Constructor not found");
      }
   }
   public Callback getCallback(int var1) {
      CGLIB$BIND_CALLBACKS(this);
      MethodInterceptor var10000;
      switch(var1) {
      case 0:
         var10000 = this.CGLIB$CALLBACK_0;
         break;
      default:
         var10000 = null;
      }
      return var10000;
   }
   public void setCallback(int var1, Callback var2) {
      switch(var1) {
      case 0:
         this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
      default:
      }
   }
   public Callback[] getCallbacks() {
      CGLIB$BIND_CALLBACKS(this);
      return new Callback[]{this.CGLIB$CALLBACK_0};
   }
// 初始化定義的callback
   public void setCallbacks(Callback[] var1) {
      this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
   }
// 這里,類加載的時(shí)候首先執(zhí)行?。。?
   static {
      CGLIB$STATICHOOK1();
   }
}

可以發(fā)現(xiàn)這個(gè)類繼承自接口實(shí)現(xiàn)類–SuperMan,其在加載的時(shí)候會(huì)先進(jìn)行一系列靜態(tài)常量的初始化且在實(shí)例化的時(shí)候調(diào)用 CGLIB$BIND_CALLBACKS(this); 進(jìn)行call back的綁定。

那么現(xiàn)在的情況就是我們的生成了一個(gè)代理類,這個(gè)代理類是我們需要代理的實(shí)現(xiàn)類的繼承類。我們的被代理類的方法在這個(gè)代理類中幫我們重寫了,并且全部變成了final的。同時(shí)覆蓋了一些Object類中的方法。

以 info 這個(gè)方法舉例,方法中會(huì)調(diào)用 MethodInterceptor 類中的 intercept 方法(也就是我們實(shí)現(xiàn)的邏輯的地方),同時(shí)把自己的Method對(duì)象,參數(shù)列表等傳入進(jìn)去。

兩個(gè)小問題

如果代理的目標(biāo)對(duì)象為接口行不行?·

接口中的方法代理類實(shí)現(xiàn)了,那么類中自定義的方法代理類是否也可以實(shí)現(xiàn)?

毫無疑問,cglib是基于類的動(dòng)態(tài)代理,代理類繼承自目標(biāo)類, 類中的方法除了final自然可以繼承 !

傳入接口為代理對(duì)象進(jìn)行測(cè)試

public class TestCglibProxy2 {
	public static void main(String[] args) {
		String path = CGLibProxy.class.getResource(".").getPath();  
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
		CGLibProxy2 cgLibProxy = new CGLibProxy2();
		//返回一個(gè)代理類的對(duì)象--這里直接傳入Human接口 Class!!!
		Object obj = cgLibProxy.getProxyInstance(Human.class);
		System.out.println(obj.getClass());
		//class com.web.test.Human$$EnhancerByCGLIB$$9fc9106
		Human hu = (Human)obj;
		hu.info();//通過代理類的對(duì)象調(diào)用重寫的抽象方法
		System.out.println();
		hu.fly();
	}
}
class CGLibProxy2 implements MethodInterceptor {  
    public Object getProxyInstance(Class<?> obj) {  
        Enhancer enhancer = new Enhancer();  
        // 這里傳入Class
        enhancer.setSuperclass(obj);  
        enhancer.setCallback(this);  
        Object proxyObj = enhancer.create();  
        return proxyObj;// 返回代理對(duì)象  
    }  
    public Object intercept(Object proxy, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {  
        Object obj = null;  
        //模擬功能增強(qiáng)
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();
        // 執(zhí)行目標(biāo)目標(biāo)對(duì)象方法--這里直接傳入目標(biāo)對(duì)象
        obj = method.invoke(new SuperMan(), args);
        //模擬功能增強(qiáng)
        humanUtil.method2();
        return obj;  
    }  
}

測(cè)試結(jié)果如下圖:

這里寫圖片描述

此時(shí)生成的源碼 Human$$EnhancerByCGLIB$$9fc9106 如下所示:

package com.web.test;
import com.web.test.Human;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//注意,這里變?yōu)榱藢?shí)現(xiàn)Human接口形式
public class Human$$EnhancerByCGLIB$$9fc9106 implements Human, Factory {
   private boolean CGLIB$BOUND;
   public static Object CGLIB$FACTORY_DATA;
   private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
   private static final Callback[] CGLIB$STATIC_CALLBACKS;
   private MethodInterceptor CGLIB$CALLBACK_0;
   private static Object CGLIB$CALLBACK_FILTER;
   private static final Method CGLIB$equals$0$Method;
   private static final MethodProxy CGLIB$equals$0$Proxy;
   private static final Object[] CGLIB$emptyArgs;
   private static final Method CGLIB$toString$1$Method;
   private static final MethodProxy CGLIB$toString$1$Proxy;
   private static final Method CGLIB$hashCode$2$Method;
   private static final MethodProxy CGLIB$hashCode$2$Proxy;
   private static final Method CGLIB$clone$3$Method;
   private static final MethodProxy CGLIB$clone$3$Proxy;
   private static final Method CGLIB$info$4$Method;
   private static final MethodProxy CGLIB$info$4$Proxy;
   private static final Method CGLIB$fly$5$Method;
   private static final MethodProxy CGLIB$fly$5$Proxy;
   static void CGLIB$STATICHOOK1() {
      CGLIB$THREAD_CALLBACKS = new ThreadLocal();
      CGLIB$emptyArgs = new Object[0];
      Class var0 = Class.forName("com.web.test.Human$$EnhancerByCGLIB$$9fc9106");
      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());
      CGLIB$equals$0$Method = var10000[0];
      CGLIB$equals$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$0");
      CGLIB$toString$1$Method = var10000[1];
      CGLIB$toString$1$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$1");
      CGLIB$hashCode$2$Method = var10000[2];
      CGLIB$hashCode$2$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$2");
      CGLIB$clone$3$Method = var10000[3];
      CGLIB$clone$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$3");
      var10000 = ReflectUtils.findMethods(new String[]{"info", "()V", "fly", "()V"}, (var1 = Class.forName("com.web.test.Human")).getDeclaredMethods());
      CGLIB$info$4$Method = var10000[0];
      CGLIB$info$4$Proxy = MethodProxy.create(var1, var0, "()V", "info", "CGLIB$info$4");
      CGLIB$fly$5$Method = var10000[1];
      CGLIB$fly$5$Proxy = MethodProxy.create(var1, var0, "()V", "fly", "CGLIB$fly$5");
   }
   final boolean CGLIB$equals$0(Object var1) {
      return super.equals(var1);
   }
   public final boolean equals(Object var1) {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         Object var2 = var10000.intercept(this, CGLIB$equals$0$Method, new Object[]{var1}, CGLIB$equals$0$Proxy);
         return var2 == null?false:((Boolean)var2).booleanValue();
      } else {
         return super.equals(var1);
      }
   }
   final String CGLIB$toString$1() {
      return super.toString();
   }
   public final String toString() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$1$Method, CGLIB$emptyArgs, CGLIB$toString$1$Proxy):super.toString();
   }
   final int CGLIB$hashCode$2() {
      return super.hashCode();
   }
   public final int hashCode() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         Object var1 = var10000.intercept(this, CGLIB$hashCode$2$Method, CGLIB$emptyArgs, CGLIB$hashCode$2$Proxy);
         return var1 == null?0:((Number)var1).intValue();
      } else {
         return super.hashCode();
      }
   }
   final Object CGLIB$clone$3() throws CloneNotSupportedException {
      return super.clone();
   }
   protected final Object clone() throws CloneNotSupportedException {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      return var10000 != null?var10000.intercept(this, CGLIB$clone$3$Method, CGLIB$emptyArgs, CGLIB$clone$3$Proxy):super.clone();
   }
   final void CGLIB$info$4() {
      super.info();
   }
   public final void info() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$info$4$Method, CGLIB$emptyArgs, CGLIB$info$4$Proxy);
      } else {
         super.info();
      }
   }
   final void CGLIB$fly$5() {
      super.fly();
   }
   public final void fly() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$fly$5$Method, CGLIB$emptyArgs, CGLIB$fly$5$Proxy);
      } else {
         super.fly();
      }
   }
   public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
      String var10000 = var0.toString();
      switch(var10000.hashCode()) {
      case -1271409118:
         if(var10000.equals("fly()V")) {
            return CGLIB$fly$5$Proxy;
         }
         break;
      case -508378822:
         if(var10000.equals("clone()Ljava/lang/Object;")) {
            return CGLIB$clone$3$Proxy;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return CGLIB$equals$0$Proxy;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return CGLIB$toString$1$Proxy;
         }
         break;
      case 1945358343:
         if(var10000.equals("info()V")) {
            return CGLIB$info$4$Proxy;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return CGLIB$hashCode$2$Proxy;
         }
      }
      return null;
   }
   public Human$$EnhancerByCGLIB$$9fc9106() {
      CGLIB$BIND_CALLBACKS(this);
   }
   public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
      CGLIB$THREAD_CALLBACKS.set(var0);
   }
   public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
      CGLIB$STATIC_CALLBACKS = var0;
   }
   private static final void CGLIB$BIND_CALLBACKS(Object var0) {
      Human$$EnhancerByCGLIB$$9fc9106 var1 = (Human$$EnhancerByCGLIB$$9fc9106)var0;
      if(!var1.CGLIB$BOUND) {
         var1.CGLIB$BOUND = true;
         Object var10000 = CGLIB$THREAD_CALLBACKS.get();
         if(var10000 == null) {
            var10000 = CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS == null) {
               return;
            }
         }
         var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
      }
   }
   public Object newInstance(Callback[] var1) {
      CGLIB$SET_THREAD_CALLBACKS(var1);
      Human$$EnhancerByCGLIB$$9fc9106 var10000 = new Human$$EnhancerByCGLIB$$9fc9106();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }
   public Object newInstance(Callback var1) {
      CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
      Human$$EnhancerByCGLIB$$9fc9106 var10000 = new Human$$EnhancerByCGLIB$$9fc9106();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }
   public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
      CGLIB$SET_THREAD_CALLBACKS(var3);
      Human$$EnhancerByCGLIB$$9fc9106 var10000 = new Human$$EnhancerByCGLIB$$9fc9106;
      switch(var1.length) {
      case 0:
         var10000.<init>();
         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
         return var10000;
      default:
         throw new IllegalArgumentException("Constructor not found");
      }
   }
   public Callback getCallback(int var1) {
      CGLIB$BIND_CALLBACKS(this);
      MethodInterceptor var10000;
      switch(var1) {
      case 0:
         var10000 = this.CGLIB$CALLBACK_0;
         break;
      default:
         var10000 = null;
      }
      return var10000;
   }
   public void setCallback(int var1, Callback var2) {
      switch(var1) {
      case 0:
         this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
      default:
      }
   }
   public Callback[] getCallbacks() {
      CGLIB$BIND_CALLBACKS(this);
      return new Callback[]{this.CGLIB$CALLBACK_0};
   }
   public void setCallbacks(Callback[] var1) {
      this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
   }
   static {
      CGLIB$STATICHOOK1();
   }
}

對(duì)比和上面的 SuperMan$$EnhancerByCGLIB$$3be74240 發(fā)現(xiàn)并無差別(此時(shí)類中還無自定義方法)。

需要注意的是,這里只是測(cè)試綁定的代理目標(biāo)對(duì)象為接口的可能性,代理類實(shí)現(xiàn)類接口的方法,并將方法的調(diào)用轉(zhuǎn)發(fā)到intercept—具體業(yè)務(wù)邏輯實(shí)現(xiàn)。且在intercept中, obj = method.invoke(new SuperMan(), args); 將實(shí)際實(shí)現(xiàn)類寫死了。

實(shí)際實(shí)現(xiàn)類(SuperMan)中添加自定義方法

如下,修改SuperMan:

// 被代理類
class SuperMan implements Human {
	public void info() {
		System.out.println("我是超人!我怕誰!");
	}
	public void fly() {
		System.out.println("I believe I can fly!");
	}
	public void self(){
		System.out.println("this is suman's method--self !");
	}
}

測(cè)試代碼如下–傳入實(shí)現(xiàn)類對(duì)象:

import java.lang.reflect.Method;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestCglibProxy {
	public static void main(String[] args) {
		//創(chuàng)建一個(gè)被代理類的對(duì)象
		SuperMan man = new SuperMan();
		String path = CGLibProxy.class.getResource(".").getPath();  
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
		CGLibProxy cgLibProxy = new CGLibProxy();
		Object obj = cgLibProxy.getProxyInstance(man);//返回一個(gè)代理類的對(duì)象
		System.out.println(obj.getClass());
		//class com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240
		Suman su = (Suman)obj;
		su.info();//通過代理類的對(duì)象調(diào)用重寫的抽象方法
		System.out.println();
		// 注意,這里調(diào)用Suman自定義方法
		su.self();
	}
}
class CGLibProxy implements MethodInterceptor {  
    private Object targetObject;// CGLib需要代理的目標(biāo)對(duì)象  
    public Object getProxyInstance(Object obj) {  
        this.targetObject = obj;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj.getClass());  
        enhancer.setCallback(this);  
        Object proxyObj = enhancer.create();  
        return proxyObj;// 返回代理對(duì)象  
    }  
    public Object intercept(Object proxy, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {  
        Object obj = null;  
        //模擬功能增強(qiáng)
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();
        // 執(zhí)行目標(biāo)目標(biāo)對(duì)象方法
        obj = method.invoke(targetObject, args);
        //模擬功能增強(qiáng)
        humanUtil.method2();
        return obj;  
    }  
}

測(cè)試結(jié)果如下圖:

這里寫圖片描述

毫無疑問是可以的,因?yàn)榇眍惱^承自目標(biāo)被代理類,故而添加的自定義方法可以被實(shí)現(xiàn)。因?yàn)镃GLIB是繼承自目標(biāo)類-SuperMan,而非實(shí)現(xiàn)目標(biāo)類的上層接口-Human!

此時(shí)生成的 SuperMan$$EnhancerByCGLIB$$3be74240.class 源碼如下:

//... 省略代碼,這里只表明方法
  final void CGLIB$info$0() {
      super.info();
   }
   public final void info() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
      } else {
         super.info();
      }
   }
   final void CGLIB$fly$1() {
      super.fly();
   }
   public final void fly() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$fly$1$Method, CGLIB$emptyArgs, CGLIB$fly$1$Proxy);
      } else {
         super.fly();
      }
   }
   final void CGLIB$self$2() {
      super.self();
   }
   public final void self() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }
      if(var10000 != null) {
         var10000.intercept(this, CGLIB$self$2$Method, CGLIB$emptyArgs, CGLIB$self$2$Proxy);
      } else {
         super.self();
      }
   }

此時(shí)如果使用①中的代碼–即 enhancer.setSuperclass(obj); 傳入Human.class,intercept中方法反射調(diào)用執(zhí)行Suman.self()是會(huì)拋異常的,且生成的代理類源碼中無self方法!

測(cè)試代碼如下:

public class TestCglibProxy2 {
	public static void main(String[] args) {
		String path = CGLibProxy.class.getResource(".").getPath();  
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
		CGLibProxy2 cgLibProxy = new CGLibProxy2();
		//這里傳入Human
		Object obj = cgLibProxy.getProxyInstance(Human.class);//返回一個(gè)代理類的對(duì)象
		System.out.println(obj.getClass());
		// 強(qiáng)轉(zhuǎn)可能會(huì)拋異常
		SuperMan su = (SuperMan)obj;
		su.info();
		System.out.println();
		// 嘗試調(diào)用Suman私有方法
		su.self();
	}
}
class CGLibProxy2 implements MethodInterceptor {  
    public Object getProxyInstance(Class<?> obj) {  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj);  
        enhancer.setCallback(this);  
        Object proxyObj = enhancer.create();  
        return proxyObj;// 返回代理對(duì)象  
    }  
    public Object intercept(Object proxy, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {  
        Object obj = null;  
        //模擬功能增強(qiáng)
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();
        // 執(zhí)行目標(biāo)目標(biāo)對(duì)象方法
        obj = method.invoke(new SuperMan(), args);
        //模擬功能增強(qiáng)
        humanUtil.method2();
        return obj;  
    }  
}

測(cè)試結(jié)果如下:

這里寫圖片描述

生成的代理類 Human$$EnhancerByCGLIB$$9fc9106.class并無Suman.self()—很顯然的事情?。?!

Cglib動(dòng)態(tài)代理總結(jié)

① CGlib可以傳入接口也可以傳入普通的類,接口使用實(shí)現(xiàn)的方式,普通類使用會(huì)使用繼承的方式生成代理類。

通常使用Cglib的時(shí)候側(cè)重于實(shí)際實(shí)現(xiàn)類??!

② 由于是繼承方式,如果是 static方法,private方法,final方法是不能被代理的。

③ CGLIB會(huì)默認(rèn)代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。

獲取JDK/Cglib動(dòng)態(tài)代理對(duì)象

至此可以獲取動(dòng)態(tài)代理的class 文件,那么如何在項(xiàng)目中獲取動(dòng)態(tài)代理的目標(biāo)對(duì)象呢?

示例代碼如下:

import java.lang.reflect.Field;  
import org.springframework.aop.framework.AdvisedSupport;  
import org.springframework.aop.framework.AopProxy;  
import org.springframework.aop.support.AopUtils;  
public class AopTargetUtils {  
    /** 
     * 獲取 目標(biāo)對(duì)象 
     * @param proxy 代理對(duì)象 
     * @return  
     * @throws Exception 
     */  
    public static Object getTarget(Object proxy) throws Exception {  
        if(!AopUtils.isAopProxy(proxy)) {  
            return proxy;//不是代理對(duì)象  
        }  
        if(AopUtils.isJdkDynamicProxy(proxy)) {  
            return getJdkDynamicProxyTargetObject(proxy);  
        } else { //cglib  
            return getCglibProxyTargetObject(proxy);  
        }  
    }  
    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {  
        Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");  
        h.setAccessible(true);  
        Object dynamicAdvisedInterceptor = h.get(proxy);  
        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");  
        advised.setAccessible(true);  
        Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();  
        return target;  
    }  
    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {  
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");  
        h.setAccessible(true);  
        AopProxy aopProxy = (AopProxy) h.get(proxy);  
        Field advised = aopProxy.getClass().getDeclaredField("advised");  
        advised.setAccessible(true);  
        Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();  
        return target;  
    }  
} 

Spring中動(dòng)態(tài)代理的實(shí)現(xiàn)

Spring代理實(shí)際上是對(duì)JDK代理和CGLIB代理做了一層封裝,并且引入了AOP概念:Aspect、advice、joinpoint等等,同時(shí)引入了AspectJ中的一些注解@pointCut,@after,@before等等。Spring Aop嚴(yán)格的來說都是動(dòng)態(tài)代理。

Spring在選擇用JDK還是CGLiB的依據(jù):

  • 當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理
  • 當(dāng)Bean沒有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn)

如何強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP?

  • 添加CGLIB庫,SPRING_HOME/cglib/*.jar
  • 可以強(qiáng)制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)

到此這篇關(guān)于CGLIB代理的使用與原理解析的文章就介紹到這了,更多相關(guān)CGLIB代理原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決使用ProcessBuilder踩到的坑及注意事項(xiàng)

    解決使用ProcessBuilder踩到的坑及注意事項(xiàng)

    這篇文章主要介紹了解決使用ProcessBuilder踩到的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Lucene源碼系列多值編碼壓縮算法實(shí)例詳解

    Lucene源碼系列多值編碼壓縮算法實(shí)例詳解

    這篇文章主要為大家介紹了Lucene源碼系列多值編碼壓縮算法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Spring Bean管理注解方式代碼實(shí)例

    Spring Bean管理注解方式代碼實(shí)例

    這篇文章主要介紹了Spring Bean管理注解方式代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • SpringBoot使用PageHelper分頁詳解

    SpringBoot使用PageHelper分頁詳解

    這篇文章主要介紹了SpringBoot使用PageHelper分頁詳解,我們?cè)谌魏蔚南到y(tǒng)中,分頁功能是必不可少的,然而,對(duì)于這個(gè)功能如果有一種快速開發(fā)的實(shí)現(xiàn)方式,當(dāng)然可以節(jié)省我們很多的時(shí)間了,接下來,我就給大家基于不同的環(huán)境來說說如何使用一個(gè)分頁插件,需要的朋友可以參考下
    2023-10-10
  • Spring Cloud Admin健康檢查 郵件、釘釘群通知的實(shí)現(xiàn)

    Spring Cloud Admin健康檢查 郵件、釘釘群通知的實(shí)現(xiàn)

    這篇文章主要介紹了Spring Cloud Admin健康檢查 郵件、釘釘群通知的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • springmvc項(xiàng)目使用@Valid+BindingResult遇到的問題

    springmvc項(xiàng)目使用@Valid+BindingResult遇到的問題

    這篇文章主要介紹了springmvc項(xiàng)目使用@Valid+BindingResult遇到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • vue 使用vuex在頁面跳轉(zhuǎn)的實(shí)現(xiàn)方式

    vue 使用vuex在頁面跳轉(zhuǎn)的實(shí)現(xiàn)方式

    這篇文章主要介紹了vue 使用vuex在頁面跳轉(zhuǎn)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Spring?Boot?使用?Disruptor?做內(nèi)部高性能消息隊(duì)列

    Spring?Boot?使用?Disruptor?做內(nèi)部高性能消息隊(duì)列

    這篇文章主要介紹了Spring?Boot?使用?Disruptor?做內(nèi)部高性能消息隊(duì)列,工作中遇到項(xiàng)目使用Disruptor做消息隊(duì)列,對(duì)你沒看錯(cuò),不是Kafka,也不是rabbitmq。Disruptor有個(gè)最大的優(yōu)點(diǎn)就是快,還有一點(diǎn)它是開源的哦,下面做個(gè)簡(jiǎn)單的記錄
    2022-06-06
  • JAVA實(shí)現(xiàn)經(jīng)典掃雷游戲的示例代碼

    JAVA實(shí)現(xiàn)經(jīng)典掃雷游戲的示例代碼

    windows自帶的游戲《掃雷》是陪伴了無數(shù)人的經(jīng)典游戲,本程序參考《掃雷》的規(guī)則進(jìn)行了簡(jiǎn)化,用java語言實(shí)現(xiàn),采用了swing技術(shù)進(jìn)行了界面化處理。感興趣的可以學(xué)習(xí)一下
    2022-01-01
  • Java實(shí)現(xiàn)FTP批量大文件上傳下載篇2

    Java實(shí)現(xiàn)FTP批量大文件上傳下載篇2

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)FTP批量大文件上傳下載的強(qiáng)化篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-08-08

最新評(píng)論