動態代理之Cglib淺析

什么是Cglib

Cglib是一個強大的,高性能,高質量的代碼生成類庫。它可以在運行期擴展JAVA類與實現JAVA接口。其底層實現是通過ASM字節碼處理框架來轉換字節碼并生成新的類。大部分功能實際上是ASM所提供的,Cglib只是封裝了ASM,簡化了ASM操作,實現了運行期生成新的class。

Cglib的原理

運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢植入橫切邏輯。

Cglib優缺點

優點:JDK動態代理要求被代理的類必須實現接口,當需要代理的類沒有實現接口時Cglib代理是一個很好的選擇。另一個優點是Cglib動態代理比使用java反射的JDK動態代理要快(Cglib的FastClass機制,解析參考http://www.cnblogs.com/cruze/p/3865180.html#lable3)

缺點:對于被代理類中的final方法,無法進行代理,因為子類中無法重寫final函數

Cglib類庫

net.sf.cglib.core:底層字節碼處理類,他們大部分與ASM有關系。

net.sf.cglib.transform:編譯期或運行期類和類文件的轉換

net.sf.cglib.proxy:實現創建代理和方法攔截器的類

net.sf.cglib.reflect:實現快速反射和C#風格代理的類

net.sf.cglib.util:集合排序等工具類

net.sf.cglib.beans:JavaBean相關的工具類

最主要的一個接口CallBack接口,即回調接口,下面的攔截器、過濾器、延遲加載都繼承于它,結構圖如下:

?

Cglib的攔截器和過濾器

攔截器:實現MethodInterceptor接口的類,在intercept方法中實現對代理目標類的方法攔截。但同時Cglib為簡化和提高性能提供了一些專門的回調類型如FixedValue(可以在實現的方法loadObject中指定返回固定值,而不調用目標類函數)、NoOp(把對回調方法的調用直接委派到這個方法的父類,即不進行攔截)

過濾器:實現CallbackFilter接口的類,通過accept方法返回一個下標值,用于指定調用哪個攔截器進行攔截處理

/*** @author longe*    被代理類*/
public class TDao {public void create() {System.out.println("create() is running !");}public void query() {System.out.println("query() is running !");}public void update() {System.out.println("update() is running !");}public void delete() {System.out.println("delete() is running !");}
}/*** @author longe*    過濾器*/
public class TDaoCglibFilter implements CallbackFilter {public final static int NO_RESTRICTION = 0;public final static int RESTRICTION_CREATE = 1;/* (non-Javadoc)*    根據調用的方法返回使用的callbacks下標*/@Overridepublic int accept(Method method) {if(method.getName().startsWith("create")){return RESTRICTION_CREATE;}return NO_RESTRICTION;}
}/*** @author longe* 攔截器*/
public class TDaoCglibProxy implements MethodInterceptor {private String name;private Object objT;public TDaoCglibProxy(String name) {this.name = name;}public TDaoCglibProxy(Object obj,String name) {this.name = name;this.objT = obj;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {if(!name.equals("張三")){System.out.println("沒有權限!");return null;}
//        return method.invoke(objT, args);    //通過反射進行調用return proxy.invokeSuper(obj, args);    //使用Cglib代理調用
    }public static void main(String[] args) {Enhancer en = new Enhancer();en.setSuperclass(TDao.class);TDaoCglibProxy callBack = new TDaoCglibProxy("張三1");
//        指定兩個callback,通過callBackFilter返回的下標值控制調用使用那一個callback處理en.setCallbacks(new Callback[] { NoOp.INSTANCE, callBack });
//        方法過濾器,根據過濾器返回不同的值回調對應下標的Callback方法en.setCallbackFilter(new TDaoCglibFilter());TDao dao = (TDao) en.create();dao.create();dao.query();dao.update();dao.delete();}}
代碼示例
執行輸出如下:沒有權限!
query() is running !
update() is running !
delete() is running !

可以看到只用create方法的調用被TDaoCglibProxy攔截器攔截了,從而判斷出沒有權限。而其他方法沒有被TDaoCglibProxy攔截器攔截,是因為在Filter過濾器中返回的索引為0,即指定的回調callback是NoOp,索引直接調用了父類的實現方法(即被代理類的實現)

Cglib的延遲加載

LazyLoader:當實際對象需要延遲加載時,可以使用LazyLoader回調。一旦實際對象被裝載,它將被每一個調用代理對象的方法使用(即實際對象被訪問時才調用此回調callback的loadObject方法)

Dispatcher:和LazyLoader回調具有相同的特點,區別是當代理方法被調用時,裝載對象的方法也總是要被調用

ProxyRefDispatcher:與Dispatcher一樣,只不過可以把代理對象作為參數進行傳遞

public class CglibLazyTest extends TestCase {public void testLazyBean() {TB tb = new TB();System.out.println("***************************************");System.out.println("cid's value: " + tb.getCid());System.out.println("***************************************");System.out.println("bean's username value: " + tb.getBean());
//        LazyLoader 這里將返回同一個對象System.out.println("bean's username value: " + tb.getBean());System.out.println("***************************************");System.out.println("bean2's username value: " + tb.getBean2());
//        這里將返回一個新的對象System.out.println("bean2's username value: " + tb.getBean2());}/*** @author longe*    通過實現LazyLoader接口延遲加載*/class Lazy implements LazyLoader {@Overridepublic Object loadObject() throws Exception {TestBean tb = new TestBean();System.out.println("lazy load ========= ");tb.setUserName("longe lazy");return tb;}}/*** @author longe*    通過實現Dispatcher接口延遲加載*/class DispacherTest implements Dispatcher {@Overridepublic Object loadObject() throws Exception {TestBean2 tb2 = new TestBean2();System.out.println("dispatcher load =========== ");tb2.setNo("longe dispatcher lazy");return tb2;}}/*** @author longe*    與Dispatcher一樣,只不過可以把代理對象作為參數進行傳遞*/class ProxyRefDispacherTest implements ProxyRefDispatcher {@Overridepublic Object loadObject(Object proxy) throws Exception {return null;}}class TB {private String cid;private TestBean bean;private TestBean2 bean2;LazyLoader lazy = new Lazy();Dispatcher dis = new DispacherTest();public TB() {cid = "no lazy";// Enhancer en = new Enhancer();// en.setSuperclass(TestBean.class);// en.setCallback(lazy);// bean = (TestBean) en.create(new Class[]{CglibLazyTest.class},new// Object[]{new CglibLazyTest()});bean = (TestBean) Enhancer.create(TestBean.class, lazy);bean2 = (TestBean2) Enhancer.create(TestBean2.class, dis);System.out.println("TB construct end...");}public TestBean getBean() {return bean;}public String getCid() {return cid;}public void setCid(String cid) {this.cid = cid;}public void setBean(TestBean bean) {this.bean = bean;}public TestBean2 getBean2() {return bean2;}public void setBean2(TestBean2 bean2) {this.bean2 = bean2;}}}class TestBean {public TestBean() {System.out.println("TestBean construct end...");}private String userName;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}
}class TestBean2 {public TestBean2() {System.out.println("TestBean2 construct end...");}private String no;public String getNo() {return no;}public void setNo(String no) {this.no = no;}
}
代碼示例
執行輸出如下:

TestBean construct end...
TestBean2 construct end...
TB construct end...
***************************************
cid's value: no lazy
***************************************
TestBean construct end...
lazy load =========
bean's username value: practices.model.proxy.cglib.TestBean@77521871
bean's username value: practices.model.proxy.cglib.TestBean@77521871
***************************************
TestBean2 construct end...
dispatcher load ===========
bean2's username value: practices.model.proxy.cglib.TestBean2@2ec2dfea
TestBean2 construct end...
dispatcher load ===========
bean2's username value: practices.model.proxy.cglib.TestBean2@7bfa93a1

上面的示例中TestBean對象使用了LazyLoader回調,TestBean2對象使用了Dispatcher回調。

分析:

  1. 在TB的構造函數中創建了TestBean和TestBean2對象,此時這兩個對象分別被創建,但是是空對象。
  2. 當tb.getBean().getUserName()第一次訪問TestBean對象時,此時LazyLoader回調被調用,TestBean裝載并進行初始化賦值,最終返回裝載的新對象。
  3. 當tb.getBean().getUserName()第二次訪問TestBean對象時,TestBean已經被裝載過,此時LazyLoader回調不被調用。
  4. 當tb.getBean2().getNo()第一次訪問TestBean2對象時,此時Dispatcher回調被調用,TestBean2裝載并進行初始化賦值,最終返回裝載的新對象。
  5. 當tb.getBean2().getNo()第二次訪問TestBean2對象時,此時Dispatcher回調還是會被調用,TestBean2裝載并進行初始化賦值,最終返回一個新裝載的新對象

Cglib中的Mixin

net.sf.cglib.proxy.Mixin允許多個對象被綁定到一個單個的大對象。在代理中對方法的調用委托到下面對應的對象中。

通過指定代理類型和實際的代理對象參數即可直接創建代理。可以綁定多個,但需要注意類型數組與對象數組的一一對應。如:

public static void testCglibMixin() {System.out.println(MixinIfA.class.getName());Class[] ints = new Class[]{MixinIfA.class,MixinIfB.class};Object[] objs = new Object[]{new MixinAImpl(),new MixinBImpl()};Object obj = Mixin.create(ints,    objs);MixinIfA a = (MixinIfA) obj;MixinIfB b = (MixinIfB) obj;a.methodA();b.methodB();}

?public static Mixin create(Class[] interfaces, Object[] delegates)中第一個參數必須是接口類型,第二個參數是接口實現類對象實例

OK,到這Cglib代理了解的差不多了,從Cglib的動態代理知道它可以動態的生成java類,那么這里順帶貼上一段簡單的Cglib生成bean的代碼.(供后續復習用...)

/*** @author longe*    動態生成bean*/
public class CglibBean {public Object object;public BeanMap beanMap;public CglibBean() {}public CglibBean(Map<String, Class> propertyMap) {this.object = generateBean(propertyMap);this.beanMap = BeanMap.create(this.object);}public void putValue(String property, Object value) {beanMap.put(property, value);}public Object getValue(String property) {return beanMap.get(property);}public Object getObject() {return this.object;}public Object generateBean(Map<String, Class> propertyMap) {BeanGenerator generator = new BeanGenerator();Set<String> keySet = propertyMap.keySet();for (String key : keySet) {generator.addProperty(key, propertyMap.get(key));}return generator.create();}public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {Map<String, Class> propertyMap = new HashMap<>();propertyMap.put("id", Class.forName("java.lang.Integer"));propertyMap.put("name", Class.forName("java.lang.String"));propertyMap.put("address", Class.forName("java.lang.String"));CglibBean cglibBean = new CglibBean(propertyMap);cglibBean.putValue("id", 101);cglibBean.putValue("name", "longe");cglibBean.putValue("address", "beijing");System.out.println(cglibBean.getValue("id"));System.out.println(cglibBean.getValue("name"));System.out.println(cglibBean.getValue("address"));Object object = cglibBean.getObject();Method[] ms = object.getClass().getDeclaredMethods();for (Method method : ms) {System.out.println(method.getName());if (method.getName().startsWith("get")) {System.out.println("=====" + method.invoke(object, null));}}}
}
View Code

Cglib異常

java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at practices.model.proxy.cglib.CglibLazyTest.testLazyBean1(CglibLazyTest.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at junit.framework.TestCase.runTest(TestCase.java:176)
at junit.framework.TestCase.runBare(TestCase.java:141)
at junit.framework.TestResult$1.protect(TestResult.java:122)
at junit.framework.TestResult.runProtected(TestResult.java:142)
at junit.framework.TestResult.run(TestResult.java:125)
at junit.framework.TestCase.run(TestCase.java:129)
at junit.framework.TestSuite.runTest(TestSuite.java:252)
at junit.framework.TestSuite.run(TestSuite.java:247)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

原因:

  • 代理目標對象不能是內部類(因為內部類的創建依賴外部類),如果是內部類,cglib代理內部會獲取到一個有參構造函數(參數是外部類對象,如果實在需要代理一個內部類,可以通過傳遞構造參數實現)
  • Cglib代理默認創建一個缺省構造函數的目標對象,如果目標對象存在有參構造函數,Cglib進行代理時需要指定構造函數的參數,或者在目標對象上必須存在缺省構造函數,否則拋出異常(可以通過傳遞構造參數創建代理類)

轉載于:https://www.cnblogs.com/mr-long/p/5889054.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/256348.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/256348.shtml
英文地址,請注明出處:http://en.pswp.cn/news/256348.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習——深度學習之卷積神經網絡(CNN)——AlexNet卷積神經網絡結構

目錄 一、AlexNet卷積神經網絡結構模型 1、數據庫ImageNet 2、AlexNet第一層卷積層 二、AlexNet卷積神經網絡的改進 1、非線性變化函數的改變——ReLU 2、最大池化&#xff08;Max Pooling&#xff09;概念的提出——卷積神經網絡通用 1&#xff09;池化層 2&#xff0…

POJ - 3470 Walls

小鳥往四個方向飛都枚舉一下&#xff0c;數據范圍沒給&#xff0c;離散以后按在其中一個軸線排序&#xff0c;在線段樹上更新墻的id&#xff0c;然后就是點查詢在在哪個墻上了。 這題有個trick&#xff0c;因為數據范圍沒給我老以為是inf設置小了&#xff0c;WA了很多發。&…

C# —— 深入理解委托類型

一. 委托定義 1. 委托與多播委托 委托類型表示對具有特定參數列表和返回類型的方法的引用&#xff0c;定義了委托實例可以調用的某類方法。 通過委托&#xff0c;我們可以動態的通過委托變量來調用委托方法。一般用delegate來命名委托類型,但Action和Func也可以達到同樣的效果…

【VS開發】【C++語言】reshuffle的容器實現算法random_shuffle()的使用

假設你需要指定范圍內的隨機數&#xff0c;傳統的方法是使用ANSI C的函數random(),然后格式化結果以便結果是落在指定的范圍內。但是&#xff0c;使用這個方法至少有兩個缺點。首先&#xff0c;做格式化時&#xff0c;結果常常是扭曲的&#xff0c;所以得不到正確的隨機數&…

C#委托——基礎2

在上一篇隨筆中&#xff0c;簡要說明了怎樣定義委托&#xff0c;定義事件&#xff0c;訂閱事件&#xff0c;最后也實現了效果&#xff0c;就是當員工類的某個對象&#xff0c;執行某個事件時&#xff0c;委托事件被觸發&#xff0c;后面也得到了結果&#xff0c;但是想象一下實…

機器學習——深度學習之編程工具、流行網絡結構、卷積神經網絡結構的應用

目錄 一、編程工具 caffe實現LENET-5 二、流行的網絡結構 1、VGGNET 2、Googlenet ? 3、ResNet? ? 三、卷積神經網絡的應用 1、人臉識別 ? 2、人臉驗證 3、人臉特征點檢測 4、卷積神經網絡壓縮 一、編程工具 caffe的優點&#xff1a;模型標準化&#xff0c;源代碼…

Halcon例程詳解(激光三角系統標定)—— calibrate_sheet_of_light_calplate.hdev

前言 1 激光三角測距 激光三角測距法原理很簡單,是通過一束激光以一定的入射角度照射被測目標,激光在目標表面會產生漫反射,在另一角度利用透鏡對反射激光匯聚成像,光斑成像在CCD(Charge-coupled Device,感光耦合組件)位置傳感器上。當被測物體沿激光方向發生移動時,…

【轉】如何實現一個文件系統

如何實現一個文件系統 摘要 本章目的是分析在Linux系統中如何實現新的文件系統。在介紹文件系統具體實現前先介紹文件系統的概念和作用&#xff0c;抽象出文件系統概念模型。熟悉文件系統的內涵后&#xff0c;我們再進一步討論Linux系統中文件系統的特殊風格和具體文件系統在Li…

【tenserflow】——數據類型以及常用屬性

目錄 一、什么是Tensor&#xff1f; 二、Tensorflow常見數據類型 三、Tensorflow常見屬性device\cpu\gpu\ndim\shape\rank等 1、創建一個tensor 1&#xff09;tf.constant() 2)tf.Variable() 2、判斷一個變量是否為tensor張量 3、生成不同設備&#xff08;cpu,gpu&#x…

C# 事件詳解附實例分析

一、定義 事件是兩個對象間發布消息和響應后處理消息的過程&#xff0c;通過委托類型來實現的。 事件的機制被稱為發布-訂閱機制&#xff0c;其算法過程為&#xff1a;首先定義一個委托類型&#xff0c;然后在發布者類中聲明一個event事件&#xff0c;同時此類中還有一個用來觸…

網頁開發瀏覽器兼容性問題

1、在ie6下的雙margin問題 在ie6下&#xff0c;設置了float的元素&#xff0c;以float:left為例&#xff0c;如圖所示。會出現第一個浮動元素&#xff0c;即相對于父級元素浮動的&#xff0c;會出現雙倍margin的問題。 注意僅僅是相對于父級元素浮動的&#xff0c;即第一個會出…

【tensorflow】——創建tensor的方法

目錄 1、tf.constant() 2、tf.Variable() 3、tf.zeros():用0去填充指定形狀的數組 4、tf.convert_to_tensor(a,dtypetf.int32) 5、tf.ones():用1去填充指定形狀的數組 6、tf.fill():用指定的元素去填充指定形狀的數組 7、隨機化初始化進行創建 1&#xff09;normal正態分…

Halcon —— 圖像像素類型與轉換

圖像類型 就目前工業領域主流的圖像處理工具halcon來講&#xff0c;有以下幾種圖像類型&#xff1a;‘byte’, ‘complex’, ‘cyclic’, ‘direction’, ‘int1’, ‘int2’, ‘int4’, ‘int8’, ‘real’, ‘uint2’&#xff0c;具體含義如下圖所示。 ‘byte’ 每像素1字節…

軟件方法

核心工作流業務建模&#xff08;組織建模&#xff09;&#xff1a;描述組織內部各個系統如何協作&#xff0c;使得組織可以為其他的組織提供有價值的服務&#xff0c;新系統只不過是組織為了對外提供更好的服務&#xff0c;對自己的內部重新設計而購買的一個零件。需求&#xf…

修改vim中的tab為4個空格

記錄一下&#xff0c;避免用時還得搜........ 1、臨時修改 在vi中&#xff0c;set tabstop4 或 set ts4  2、永久修改 vi --version 查看要修改的文件如果是vim的話&#xff0c;修改~/.vimrc如果是vi&#xff0c;修改~/.exrc加上&#xff1a;set tabstop4set nu //顯示行號set…

Halcon例程詳解(基于卡尺工具的匹配測量方法) —— measure_stamping_part.hdev

前言 1卡尺工具介紹 Halcon中的Metrology方法即為卡尺工具&#xff0c;可用來擬合線&#xff0c;圓&#xff0c;這種方法對于目標比背景很明顯的圖像尺寸測量是很方便的&#xff0c;不需要用blob進行邊緣提取等&#xff0c;但缺點也很明顯&#xff0c;需要目標的相對位置基本…

【TensorFlow】——不同shape的tensor在神經網絡中的應用(scalar,vector,matrix)

目錄 ? 1、scalar——標量 1&#xff09;在神經網絡中存在的場景 2&#xff09;one_hot編碼 3&#xff09;舉例應用 2、vector——向量 ? 3、matrixs——矩陣 4、dim3的tensor 5、dim4的tensor 6、dim5的tensor 本文主要的目的是讓初學者對tensor的各種形式的使用場…

404頁面 3秒后跳到首頁 實現

---恢復內容開始--- 當我們訪問一個頁面不存在的時候&#xff0c;就會跳到404頁面 一般網站都在在404頁面中做一個處理&#xff0c; 就是當用戶3秒種內還沒有任何操作的話&#xff0c;就會自動跳轉到其它頁面 技術實現有兩種方法 1. 在404頁面中的header間加上 <meta http-e…

Java - I/O

File類 java.io操作文件和目錄&#xff0c;與平臺無關。具體的常用實例方法&#xff1a; File file new File("."); // 以當前路徑創建名為 "." 的 File 對象 ? 文件目錄信息函數 ? ? - ? String getName/Path/Parent()&#xff1a; 文件名/路徑…

Halcon —— 邊緣檢測算子詳解

一、算子介紹 1.1 種類 halcon內常用的邊緣檢測算子包括如下幾種&#xff1a; 1.edges_image: 提取2D 圖像邊緣 2.edges_sub_pix&#xff1a;提取2D圖像亞像素邊緣 3.edges_object_model_3d &#xff1a;提取3D圖像邊緣 4.edges_color和edges_color_sub_pix&#xff1a;提取彩…