當前位置:
首頁 > 知識 > CGLIB(Code Generation Library) 介紹與原理

CGLIB(Code Generation Library) 介紹與原理

一、什麼是 CGLIB?

CGLIB是一個功能強大,高性能的代碼生成包。它為沒有實現介面的類提供代理,為JDK的動態代理提供了很好的補充。通常可以使用Java的動態代理創建代理,但當要代理的類沒有實現介面或者為了更好的性能,CGLIB是一個好的選擇。

CGLIB作為一個開源項目,其代碼託管在github,地址為:https://github.com/cglib/cglib



二、CGLIB 原理

CGLIB 原理:動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。它比使用java反射的JDK動態代理要快。

CGLIB 底層:使用位元組碼處理框架ASM,來轉換位元組碼並生成新的類。不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。

CGLIB缺點:對於final方法,無法進行代理。



三、CGLIB 的應用

廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop。Hibernate使用CGLIB來代理單端single-ended(多對一和一對一)關聯。



四、為什麼使用 CGLIB?

CGLIB代理主要通過對位元組碼的操作,為對象引入間接級別,以控制對象的訪問。我們知道Java中有一個動態代理也是做這個事情的,那我們為什麼不直接使用Java動態代理,而要使用CGLIB呢?答案是CGLIB相比於JDK動態代理更加強大,JDK動態代理雖然簡單易用,但是其有一個致命缺陷是,只能對介面進行代理。如果要代理的類為一個普通類、沒有介面,那麼Java動態代理就沒法使用了。



五、CGLIB組成結構

CGLIB(Code Generation Library) 介紹與原理

CGLIB底層使用了ASM(一個短小精悍的位元組碼操作框架)來操作位元組碼生成新的類。除了CGLIB庫外,腳本語言(如Groovy何BeanShell)也使用ASM生成位元組碼。ASM使用類似SAX的解析器來實現高性能。我們不鼓勵直接使用ASM,因為它需要對Java位元組碼的格式足夠的了解。



六、CGLIB的API

1、Jar包:

  • cglib-nodep-2.2.jar

    :使用nodep包不需要關聯asm的jar包,jar包內部包含asm的類.
  • cglib-2.2.jar

    :使用此jar包需要關聯asm的jar包,否則運行時報錯.

2、CGLIB類庫:

由於基本代碼很少,學起來有一定的困難,主要是缺少文檔和示例,這也是CGLIB的一個不足之處。

本系列使用的CGLIB版本是2.2。

  • 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相關的工具類

本篇介紹通過MethodInterceptor和Enhancer實現一個動態代理。

一、首先說一下JDK中的動態代理:

JDK中的動態代理是通過反射類Proxy以及InvocationHandler回調介面實現的,但是,JDK中所要進行動態代理的類必須要實現一個介面,也就是說只能對該類所實現介面中定義的方法進行代理,這在實際編程中具有一定的局限性,而且使用反射的效率也並不是很高。

二、使用CGLib實現:

使用CGLib實現動態代理,完全不受代理類必須實現介面的限制,而且CGLib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進行代理,因為CGLib原理是動態生成被代理類的子類。

下面,將通過一個實例介紹使用CGLib實現動態代理。

1、被代理類:

首先,定義一個類,該類沒有實現任何介面。

package com.zghw.cglib;

/**

* 沒有實現介面,需要CGlib動態代理的目標類

*

* @author zghw

*

*/

public class TargetObject {

public String method1(String paramName) {

return paramName;

}

public int method2(int count) {

return count;

}

public int method3(int count) {

return count;

}

@Override

public String toString() {

return "TargetObject []"+ getClass();

}

}

2、攔截器:

定義一個攔截器。在調用目標方法時,CGLib會回調MethodInterceptor介面方法攔截,來實現你自己的代理邏輯,類似於JDK中的InvocationHandler介面。

package com.zghw.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

/**

* 目標對象攔截器,實現MethodInterceptor

* @author zghw

*

*/

public class TargetInterceptor implements MethodInterceptor{

/**

* 重寫方法攔截在方法前和方法後加入業務

* Object obj為目標對象

* Method method為目標方法

* Object[] params 為參數,

* MethodProxy proxy CGlib方法代理對象

*/

@Override

public Object intercept(Object obj, Method method, Object[] params,

MethodProxy proxy) throws Throwable {

System.out.println("調用前");

Object result = proxy.invokeSuper(obj, params);

System.out.println(" 調用後"+result);

return result;

}

}

參數:Object為由CGLib動態生成的代理類實例,Method為上文中實體類所調用的被代理的方法引用,Object[]為參數值列表,MethodProxy為生成的代理類對方法的代理引用。

返回:從代理實例的方法調用返回的值。

其中,proxy.invokeSuper(obj,arg) 調用代理類實例上的proxy方法的父類方法(即實體類TargetObject中對應的方法)

在這個示例中,只在調用被代理類方法前後各列印了一句話,當然實際編程中可以是其它複雜邏輯。

3、生成動態代理類:

package com.zghw.cglib;

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.NoOp;

public class TestCglib {

public static void main(String args[]) {

Enhancer enhancer =new Enhancer();

enhancer.setSuperclass(TargetObject.class);

enhancer.setCallback(new TargetInterceptor());

TargetObject targetObject2=(TargetObject)enhancer.create();

System.out.println(targetObject2);

System.out.println(targetObject2.method1("mmm1"));

System.out.println(targetObject2.method2(100));

System.out.println(targetObject2.method3(200));

}

}

這裡Enhancer類是CGLib中的一個位元組碼增強器,它可以方便的對你想要處理的類進行擴展,以後會經常看到它。

首先將被代理類TargetObject設置成父類,然後設置攔截器TargetInterceptor,最後執行enhancer.create()動態生成一個代理類,並從Object強制轉型成父類型TargetObject。

最後,在代理類上調用方法。

4、回調過濾器CallbackFilter

一、作用

在CGLib回調時可以設置對不同方法執行不同的回調邏輯,或者根本不執行回調。

在JDK動態代理中並沒有類似的功能,對InvocationHandler介面方法的調用對代理類內的所以方法都有效。

定義實現過濾器CallbackFilter介面的類:

package com.zghw.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.CallbackFilter;

/**

* 回調方法過濾

* @author zghw

*

*/

public class TargetMethodCallbackFilter implements CallbackFilter {

/**

* 過濾方法

* 返回的值為數字,代表了Callback數組中的索引位置,要到用的Callback

*/

@Override

public int accept(Method method) {

if(method.getName().equals("method1")){

System.out.println("filter method1 ==0");

return 0;

}

if(method.getName().equals("method2")){

System.out.println("filter method2 ==1");

return 1;

}

if(method.getName().equals("method3")){

System.out.println("filter method3 ==2");

return 2;

}

return 0;

}

}

其中return值為被代理類的各個方法在回調數組Callback[]中的位置索引(見下文)。

package com.zghw.cglib;

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.NoOp;

public class TestCglib {

public static void main(String args[]) {

Enhancer enhancer =new Enhancer();

enhancer.setSuperclass(TargetObject.class);

CallbackFilter callbackFilter = new TargetMethodCallbackFilter();

/**

* (1)callback1:方法攔截器

(2)NoOp.INSTANCE:這個NoOp表示no operator,即什麼操作也不做,代理類直接調用被代理的方法不進行攔截。

(3)FixedValue:表示鎖定方法返回值,無論被代理類的方法返回什麼值,回調方法都返回固定值。

*/

Callback noopCb=NoOp.INSTANCE;

Callback callback1=new TargetInterceptor();

Callback fixedValue=new TargetResultFixed();

Callback[] cbarray=new Callback[]{callback1,noopCb,fixedValue};

//enhancer.setCallback(new TargetInterceptor());

enhancer.setCallbacks(cbarray);

enhancer.setCallbackFilter(callbackFilter);

TargetObject targetObject2=(TargetObject)enhancer.create();

System.out.println(targetObject2);

System.out.println(targetObject2.method1("mmm1"));

System.out.println(targetObject2.method2(100));

System.out.println(targetObject2.method3(100));

System.out.println(targetObject2.method3(200));

}

}

package com.zghw.cglib;

import net.sf.cglib.proxy.FixedValue;

/**

* 表示鎖定方法返回值,無論被代理類的方法返回什麼值,回調方法都返回固定值。

* @author zghw

*

*/

public class TargetResultFixed implements FixedValue{

/**

* 該類實現FixedValue介面,同時鎖定回調值為999

* (整型,CallbackFilter中定義的使用FixedValue型回調的方法為getConcreteMethodFixedValue,該方法返回值為整型)。

*/

@Override

public Object loadObject() throws Exception {

System.out.println("鎖定結果");

Object obj = 999;

return obj;

}

}

5.延遲載入對象

一、作用:

說到延遲載入,應該經常接觸到,尤其是使用Hibernate的時候,本篇將通過一個實例分析延遲載入的實現方式。 LazyLoader介面繼承了Callback,因此也算是CGLib中的一種Callback類型。

另一種延遲載入介面Dispatcher。

Dispatcher介面同樣繼承於Callback,也是一種回調類型。

但是Dispatcher和LazyLoader的區別在於:LazyLoader只在第一次訪問延遲載入屬性時觸發代理類回調方法,而Dispatcher在每次訪問延遲載入屬性時都會觸發代理類回調方法。

二、示例:

首先定義一個實體類LoaderBean,該Bean內有一個需要延遲載入的屬性PropertyBean。

package com.zghw.cglib;

import net.sf.cglib.proxy.Enhancer;

public class LazyBean {

private String name;

private int age;

private PropertyBean propertyBean;

private PropertyBean propertyBeanDispatcher;

public LazyBean(String name, int age) {

System.out.println("lazy bean init");

this.name = name;

this.age = age;

this.propertyBean = createPropertyBean();

this.propertyBeanDispatcher = createPropertyBeanDispatcher();

}

/**

* 只第一次懶載入

* @return

*/

private PropertyBean createPropertyBean() {

/**

* 使用cglib進行懶載入 對需要延遲載入的對象添加代理,在獲取該對象屬性時先通過代理類回調方法進行對象初始化。

* 在不需要載入該對象時,只要不去獲取該對象內屬性,該對象就不會被初始化了(在CGLib的實現中只要去訪問該對象內屬性的getter方法,

* 就會自動觸發代理類回調)。

*/

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(PropertyBean.class);

PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,

new ConcreteClassLazyLoader());

return pb;

}

/**

* 每次都懶載入

* @return

*/

private PropertyBean createPropertyBeanDispatcher() {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(PropertyBean.class);

PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,

new ConcreteClassDispatcher());

return pb;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public PropertyBean getPropertyBean() {

return propertyBean;

}

public void setPropertyBean(PropertyBean propertyBean) {

this.propertyBean = propertyBean;

}

public PropertyBean getPropertyBeanDispatcher() {

return propertyBeanDispatcher;

}

public void setPropertyBeanDispatcher(PropertyBean propertyBeanDispatcher) {

this.propertyBeanDispatcher = propertyBeanDispatcher;

}

@Override

public String toString() {

return "LazyBean [name=" + name + ", age=" + age + ", propertyBean="

+ propertyBean + "]";

}

}

package com.zghw.cglib;

public class PropertyBean {

private String key;

private Object value;

public String getKey() {

return key;

}

public void setKey(String key) {

this.key = key;

}

public Object getValue() {

return value;

}

public void setValue(Object value) {

this.value = value;

}

@Override

public String toString() {

return "PropertyBean [key=" + key + ", value=" + value + "]" +getClass();

}

}

package com.zghw.cglib;

import net.sf.cglib.proxy.LazyLoader;

public class ConcreteClassLazyLoader implements LazyLoader {

/**

* 對需要延遲載入的對象添加代理,在獲取該對象屬性時先通過代理類回調方法進行對象初始化。

* 在不需要載入該對象時,只要不去獲取該對象內屬性,該對象就不會被初始化了(在CGLib的實現中只要去訪問該對象內屬性的getter方法,

* 就會自動觸發代理類回調)。

*/

@Override

public Object loadObject() throws Exception {

System.out.println("before lazyLoader...");

PropertyBean propertyBean = new PropertyBean();

propertyBean.setKey("zghw");

propertyBean.setValue(new TargetObject());

System.out.println("after lazyLoader...");

return propertyBean;

}

}

package com.zghw.cglib;

import net.sf.cglib.proxy.Dispatcher;

public class ConcreteClassDispatcher implements Dispatcher{

@Override

public Object loadObject() throws Exception {

System.out.println("before Dispatcher...");

PropertyBean propertyBean = new PropertyBean();

propertyBean.setKey("xxx");

propertyBean.setValue(new TargetObject());

System.out.println("after Dispatcher...");

return propertyBean;

}

}

6.介面生成器InterfaceMaker

一、作用:

InterfaceMaker會動態生成一個介面,該介面包含指定類定義的所有方法。

二、示例:

package com.zghw.cglib;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.InterfaceMaker;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

public class TestInterfaceMaker {

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

InterfaceMaker interfaceMaker =new InterfaceMaker();

//抽取某個類的方法生成介面方法

interfaceMaker.add(TargetObject.class);

Class<?> targetInterface=interfaceMaker.create();

for(Method method : targetInterface.getMethods()){

System.out.println(method.getName());

}

//介面代理並設置代理介面方法攔截

Object object = Enhancer.create(Object.class, new Class[]{targetInterface}, new MethodInterceptor(){

@Override

public Object intercept(Object obj, Method method, Object[] args,

MethodProxy methodProxy) throws Throwable {

if(method.getName().equals("method1")){

System.out.println("filter method1 ");

return "mmmmmmmmm";

}

if(method.getName().equals("method2")){

System.out.println("filter method2 ");

return 1111111;

}

if(method.getName().equals("method3")){

System.out.println("filter method3 ");

return 3333;

}

return "default";

}});

Method targetMethod1=object.getClass().getMethod("method3",new Class[]{int.class});

int i=(int)targetMethod1.invoke(object, new Object[]{33});

Method targetMethod=object.getClass().getMethod("method1",new Class[]{String.class});

System.out.println(targetMethod.invoke(object, new Object[]{"sdfs"}));

}

}

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

如果一個函數作為另一個函數參數使用,那麼這函數叫做回調函數
「python」colorama 模塊-改變控制台輸出文本的顏色

TAG:程序員小新人學習 |