CGLIB全網(wǎng)詳細(xì)教程

原創(chuàng)文章,部分信息參考互聯(lián)網(wǎng),轉(zhuǎn)載需告知

簡(jiǎn)介

CGLIB是一個(gè)強(qiáng)大劲够、高性能和高質(zhì)量的第三方代碼生成庫棒厘。該庫被Spring蕊蝗、Mybatis盯捌、Hibernate等第三方框架廣泛應(yīng)用,用以提供方法攔截操作署隘。CGLIB屬于開源項(xiàng)目宠能,其CGLIB源碼地址為:https://github.com/cglib/cglib

CGLIB代理主要通過對(duì)字節(jié)碼的操作,為對(duì)象引入間接級(jí)別磁餐,以控制對(duì)象的訪問违崇。

CGLIB向比與基于Java反射的JDK動(dòng)態(tài)代理來說,CGLIB功能更加強(qiáng)大崖媚。JDK動(dòng)態(tài)代理有個(gè)缺陷就是只能對(duì)接口進(jìn)行代理亦歉,無法對(duì)單獨(dú)普通類進(jìn)行代理,而CGLIB則可以解決這一問題畅哑。

構(gòu)成框架

作為開源項(xiàng)目肴楷,其內(nèi)部結(jié)構(gòu)必然會(huì)使用其他不少其他開源框架,CGLIB也不例外荠呐。

CGLIB底層使用了ASM來進(jìn)行操控字節(jié)碼赛蔫,用來生成新的類砂客。類似的情況比如Groovy動(dòng)態(tài)語言或者BeanShell都是利用ASM生成字節(jié)碼。

ASM是一種通用Java字節(jié)碼操作和分析框架呵恢。它使用類似于SAX解析器來實(shí)現(xiàn)高效能處理鞠值,所以自然CGLIB也是注重高性能的。

原理

CGLIB代理渗钉,就是動(dòng)態(tài)生成一個(gè)要代理類的子類彤恶,子類重寫要代理的類的所有不是final的方法。在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用鳄橘,順勢(shì)織入橫切邏輯声离。它比使用基于java反射的JDK動(dòng)態(tài)代理要快,并且不需要局限于代理類是否實(shí)現(xiàn)接口瘫怜。

同時(shí)缺點(diǎn)也暴露出來术徊,由于只是重寫了不是final的方法,所以代理的類的final方法鲸湃,則無法進(jìn)行代理實(shí)現(xiàn)赠涮。

教程

這里先批評(píng)以下CGLIB的開發(fā)者,一個(gè)開源項(xiàng)目目前居然還缺少詳細(xì)的官方文檔和實(shí)例...

依賴導(dǎo)入

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

庫的分類

  • net.sf.cglib.core: 底層字節(jié)碼處理類暗挑,他們大部分與ASM有關(guān)系笋除。
  • net.sf.cglib.transform: 編譯期或運(yùn)行期類和類文件的轉(zhuǎn)換
  • net.sf.cglib.proxy: 實(shí)現(xiàn)創(chuàng)建代理和方法攔截器的類
  • net.sf.cglib.reflect: 實(shí)現(xiàn)快速反射和C#風(fēng)格代理的類
  • net.sf.cglib.util: 集合排序等工具類
  • net.sf.cglib.beans: JavaBean相關(guān)的工具類

Enhancer創(chuàng)建動(dòng)態(tài)代理

使用CGLIB中的Enhancer來創(chuàng)建動(dòng)態(tài)代理。

Enhancer是一個(gè)CGLIB中非常重要的類窿祥,它允許為非接口類型創(chuàng)建一個(gè)JAVA代理株憾,Enhancer動(dòng)態(tài)的創(chuàng)建給定類的子類蝙寨,和JDK動(dòng)態(tài)代理不一樣的是不管代理的類是實(shí)現(xiàn)接口還是普通的類它都能正常工作晒衩。然后通過 回調(diào) 來進(jìn)行攔截代理類的方法進(jìn)行處理。

Callback-回調(diào)

在利用Enhancer前墙歪,我們先介紹回調(diào)听系,

image.png

Enhancer幾乎所有的類都基于回調(diào)這個(gè)接口。

當(dāng)Enhancer代理類執(zhí)行方法虹菲,即是使用Enhancer進(jìn)行方法代理靠胜,Enhancer在進(jìn)行處理時(shí),這個(gè)過程稱之為回調(diào)毕源。

MethodInterceptor-方法攔截器

MethodInterceptor 翻譯過來就是方法攔截器浪漠,它基礎(chǔ)于Callback,說明它也屬于回調(diào)霎褐。

我們先要?jiǎng)?chuàng)建一個(gè) 代理類方法攔截器址愿,實(shí)現(xiàn)net.sf.cglib.proxy.MethodInterceptor接口,用作代理類的方法攔截處理冻璃,它的用法其實(shí)和JDK動(dòng)態(tài)代理的java.lang.reflect.InvocationHandler一樣响谓。

其中MethodInterceptor接口中只有一個(gè)方法intercept:

public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;

它接受4個(gè)參數(shù):

obj – 代理的原類對(duì)象
方法 – 攔截實(shí)現(xiàn)的方法
args – 方法中的參數(shù)組
proxy – 用于調(diào)用 父類代理损合; 可以根據(jù)需要多次調(diào)用,既做到多次代理娘纷。

這里面的proxy對(duì)象是MethodProxy類嫁审,不同于JDK動(dòng)態(tài)代理InvocationHandler中的proxy,它擁有一個(gè)invokeSuper方法赖晶,能調(diào)用原始(父類)的方法律适,類似于Method類的Invoke方法調(diào)用。

所以代理類方法攔截器例子如下:

/**
 * @description: 代理類方法攔截器
 * @author: Zhaotianyi
 * @time: 2021/10/20 10:31
 */
public class ProxyInterceptor implements MethodInterceptor {

    /**
     * 當(dāng)對(duì)基于代理的方法回調(diào)時(shí)遏插,攔截其方法擦耀,進(jìn)行自定義處理
     * @param obj 代理對(duì)象
     * @param method 攔截的方法
     * @param args 攔截的方法的參數(shù)
     * @param proxy 代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object result = null;
        System.out.println("method invoke before...");
        result = proxy.invokeSuper(obj, args);
        System.out.println("method invoke after...");
        return result;
    }
}

上面除了在調(diào)用方法代理使用了MethodProxy的invokeSuper方法之外,其余的基本和JDK動(dòng)態(tài)代理的InvocationHandler的實(shí)現(xiàn)類編寫方法一樣涩堤。

Enhancer代理

有了代理類方法攔截器 后眷蜓,我們就可利用net.sf.cglib.proxy.Enhancer類來創(chuàng)建動(dòng)態(tài)代理了。

其中Enhancer類概念和原本JDK動(dòng)態(tài)代理的Proxy類幾乎一樣胎围,創(chuàng)建Enhancer類來進(jìn)行代理類吁系,但和Proxy不同的是,Enhancer既能夠代理普通的class白魂,也能夠代理接口汽纤。

Enhancer創(chuàng)建一個(gè)被代理對(duì)象的子類并且攔截所有的方法調(diào)用(包括從Object中繼承的toString和hashCode方法)。注意:Enhancer不能夠攔截final方法

public class ProxyMain {
    public static void main(String[] args) {
        // 創(chuàng)建Enhancer類,用作實(shí)現(xiàn)Student類的代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(new ProxyInterceptor());

        // 創(chuàng)建Enhancer代理,來代理Student類
        Student o = (Student) enhancer.create();
    }
}

其中創(chuàng)建Enhancer后需要設(shè)置兩個(gè)參數(shù)福荸,一個(gè)為Superclass:代理的類蕴坪,一個(gè)為Callback:回調(diào)對(duì)象。

最后需要調(diào)用其對(duì)象的create方法敬锐,即建立一個(gè)代理類出來背传。其代理類的所有非final參數(shù)、方法都會(huì)被其攔截台夺,返回至MethodInterceptor中進(jìn)行自定義處理径玖。

當(dāng)然直接使用Enhancer中的靜態(tài)Create方法,輸入兩個(gè)參數(shù)也可以進(jìn)行快速創(chuàng)建一個(gè)代理類出來颤介。

Student o = (Student) Enhancer.create(Student.class, new ProxyInterceptor(new Student()));

當(dāng)我們直接打印一下對(duì)象梳星,看看會(huì)出現(xiàn)什么?

結(jié)果如下:

method invoke before...
method invoke before...
method invoke after...
method invoke after...
com.test2.bean.Student$$EnhancerByCGLIB$$72d6b74c@2957fcb0

控制臺(tái)產(chǎn)生兩次代理攔截處理滚朵,而且為什么為什么還會(huì)有EnhancerByCGLIB字段冤灾,這時(shí)大伙可能會(huì)產(chǎn)生疑惑。

其實(shí)都知道直接打印對(duì)象的話辕近,其實(shí)調(diào)用的是對(duì)象的toString方法韵吨,而在默認(rèn)的toString方法中:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

其中getName()也會(huì)調(diào)用一次內(nèi)部方法,所以自然會(huì)產(chǎn)生兩次攔截亏推。而不同的是getClass()默認(rèn)是屬于final類型的学赛,前面說了Enhancer的特性年堆,所以自然不能被攔截,自然會(huì)暴露出原Enhancer類型出來盏浇。

當(dāng)然变丧,上面的操作需要先創(chuàng)建代理方法攔截類還要在運(yùn)行下創(chuàng)建Enhancer代理,對(duì)于工程中必然顯得代碼重復(fù)了绢掰,所以可以直接精簡(jiǎn)為一個(gè)工具類:

/**
 * Enhancer代理生成工具
 * @version 1.0
 * @since JDK1.7
 */
public class ProxyUtil implements MethodInterceptor {

    //代理的類對(duì)象
    private Object obj;
    
    /**
     * 創(chuàng)建代理類
     * @param target 代理的類對(duì)象
     * @return
     */
    public Object createProxy(Object target) {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        //設(shè)置代理目標(biāo)
        enhancer.setSuperclass(this.obj.getClass());
        //設(shè)置單一回調(diào)對(duì)象痒蓬,在調(diào)用中攔截對(duì)目標(biāo)方法的調(diào)用
        enhancer.setCallback(this);
        //設(shè)置類加載器
        enhancer.setClassLoader(this.obj.getClass().getClassLoader());
        
        return enhancer.create();
    }
    /**
     * 
     * 方法描述 當(dāng)對(duì)基于代理的方法回調(diào)時(shí),在調(diào)用原方法之前會(huì)調(diào)用該方法
     * 攔截對(duì)目標(biāo)方法的調(diào)用
     *
     * @param obj 代理對(duì)象
     * @param method 攔截的方法
     * @param args 攔截的方法的參數(shù)
     * @param proxy 代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        Object result = null;
        
        result = proxy.invokeSuper(obj, args);
        
        return result;
    }

}

使用:

ProxyUtil proxyUtil = new ProxyUtil();
XXX xxx = (XXX)proxyUtil.createProxy(new XXX());

CallbackFilter-回調(diào)過濾器

有些時(shí)候我們可能只想對(duì)特定的方法進(jìn)行攔截滴劲,對(duì)其他的方法直接放行攻晒,不做任何操作。這種情況下我們這需要在Enhancer上綁定一個(gè)CallbackFilter班挖。

CallbackFilter是一個(gè)net.sf.cglib.proxy下的一個(gè)接口鲁捏,該接口主要作用允許您在代理的方法中控制回調(diào)規(guī)則,起著過濾作用萧芙。

其CallbackFilter接口中含有兩個(gè)方法给梅,其中accept方法用于將其代理方法進(jìn)行過濾然后分別回調(diào)。

例如双揪,我們實(shí)現(xiàn)一個(gè)CallbackFilter接口類:

    /**
     * PersistenceService代理類方法回調(diào)過濾器
     *
     */
    public class PersistenceServiceCallbackFilter implements CallbackFilter {

        //SAVE方法 回調(diào)組的序號(hào)
        private static final int SAVE = 0;

        //LOAD方法 回調(diào)組的回調(diào)序號(hào)
        private static final int LOAD = 1;

        /**
         * 代理方法過濾动羽,控制回調(diào)
         * @method 代理類的方法
         * @return 回調(diào)組的序號(hào)
         */
        public int accept(Method method) {
            String name = method.getName();
            if ("save".equals(name)) {
                return SAVE;
            }
            // 對(duì)于其他方法
            return LOAD;
        }
    }

上面是一個(gè)簡(jiǎn)單的自定義回調(diào)過濾器,它執(zhí)行了過濾操作渔期,將其 save方法進(jìn)行返回 0 运吓,其余的方法返回 1。

回調(diào)過濾器的Accept 方法將代理方法映射到回調(diào)疯趟。返回值是特定方法的回調(diào)數(shù)組中的索引拘哨。

隨后在其Enhancer綁定回調(diào)過濾器:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersistenceServiceImpl.class);

CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
// 綁定回調(diào)過濾器
enhancer.setCallbackFilter(callbackFilter);

AuthorizationService authorizationService = ...
Callback saveCallback = new AuthorizationInterceptor(authorizationService);
Callback loadCallback = NoOp.INSTANCE;
// 設(shè)置回調(diào)數(shù)組
Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
enhancer.setCallbacks(callbacks);
...
return (PersistenceServiceImpl)enhancer.create();

enhancer.setCallbacks方法設(shè)置了回調(diào)數(shù)組,這個(gè)回調(diào)數(shù)組的索引就是這個(gè)上面Accept 方法返回索引對(duì)應(yīng)迅办。

NoOp的回調(diào)行為 表示返回原類方法執(zhí)行宅静,即放行章蚣。

所以當(dāng)代理類執(zhí)行save方法后站欺,就會(huì)被回調(diào)到AuthorizationInterceptor上。如果執(zhí)行其他方法纤垂,那么就會(huì)直接放行執(zhí)行矾策。

CallbackHelper

使用CallbackFilter進(jìn)行方法回調(diào)過濾,那么Enhancer就還需要自己手動(dòng)設(shè)置回調(diào)數(shù)組峭沦,如果回調(diào)有一兩個(gè)還好贾虽,如果有很多需要進(jìn)行過濾的話,那么不但不好設(shè)置回調(diào)數(shù)組吼鱼,還容易搞混索引蓬豁。

能否只寫過濾規(guī)則直接跳轉(zhuǎn)到XX回調(diào)绰咽,不寫回調(diào)數(shù)組呢?

可以地粪!CGLIB提供了一個(gè)快速過濾類 - CallbackHelper取募。

net.sf.cglib.proxy.CallbackHelper類實(shí)現(xiàn)了CallbackFilter接口,它在創(chuàng)建后自動(dòng)將其回調(diào)數(shù)組進(jìn)行排序蟆技,用戶使用它只需要關(guān)注過濾規(guī)則和回調(diào)玩敏,不需要關(guān)注回調(diào)數(shù)組的排序索引。

CallbackHelper的構(gòu)造方法需要兩個(gè)參數(shù):

Class superclass:代理的類

Class[] interfaces:代理的類的實(shí)現(xiàn)的接口組质礼,如果沒有的話旺聚,可以傳入new Class[0]

CallbackHelper需要重寫它的getCallback(Method method)方法,當(dāng)代理類每次執(zhí)行一個(gè)方法時(shí)眶蕉,其代理方法都會(huì)通過getCallback這個(gè)方法中砰粹,在其進(jìn)行過濾設(shè)置回調(diào)。

所以將其過濾條件放置在getCallback方法內(nèi):

CallbackHelper helper = new CallbackHelper(Student.class, new Class[0]) {
            @Override
            protected Object getCallback(Method method) {
               // 如果方法名為sout的話,將其方法返回至ProxyInterceptor處理
                if (method.getName() == "sout") {
                    return new ProxyInterceptor();
                } else {
                    // 否則直接將其方法回調(diào)給默認(rèn)實(shí)現(xiàn),即正常執(zhí)行
                    return NoOp.INSTANCE;
                }
            }
        };
// 創(chuàng)建Enhancer代理,來代理Student類
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
// 設(shè)置要使用的代理方法回調(diào)數(shù)組
enhancer.setCallbacks(helper.getCallbacks());
// 設(shè)置要代理方法回調(diào)過濾器
enhancer.setCallbackFilter(helper);
Student o = (Student)enhancer.create();

Enhancer在設(shè)置回調(diào)數(shù)組時(shí)造挽,只需使用CallbackHelper.getCallbacks()即可獲取對(duì)應(yīng)的回調(diào)數(shù)組伸眶。所以使用CallbackHelper會(huì)比一般的自定義CallbackFilter使用簡(jiǎn)便。

更多回調(diào)行為

上面我們只介紹了兩種回調(diào)行為 MethodInterceptor方法攔截 和 NoOp放行刽宪,CGLIB還提供了很多其他回調(diào)行為接口:

  • net.sf.cglib.proxy.FixedValue : 直接強(qiáng)行返回指定內(nèi)容厘贼,在一些場(chǎng)景下可以提升處理性能。例如:
Enhancer enhancer = new Enhancer();
//設(shè)置代理目標(biāo)
enhancer.setSuperclass(hello.getClass());
//設(shè)置單一回調(diào)對(duì)象圣拄,在調(diào)用中攔截對(duì)目標(biāo)方法的調(diào)用
enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
        // TODO Auto-generated method stub
        return "FixedValue";
    }
});
Object obj = enhancer.create();

上述行為嘴秸,不管代理類其調(diào)用方法內(nèi)容如何,都會(huì)返回“FixedValue”這個(gè)字節(jié)串庇谆,并且調(diào)用方法內(nèi)其他不執(zhí)行岳掐。

  • net.sf.cglib.proxy.LazyLoader:當(dāng)實(shí)際的對(duì)象需要延遲裝載時(shí),可以使用LazyLoader回調(diào)饭耳。一旦實(shí)際對(duì)象被裝載串述,它將被每一個(gè)調(diào)用代理對(duì)象的方法使用;
  • net.sf.cglib.proxy.Dispatcher:Dispathcer回調(diào)和LazyLoader回調(diào)有相同的特點(diǎn),不同的是寞肖,當(dāng)代理方法被調(diào)用時(shí)纲酗,裝載對(duì)象的方法也總要被調(diào)用;
  • net.sf.cglib.proxy.ProxyRefDispatcher:ProxyRefDispatcher回調(diào)和Dispatcher一樣,不同的是新蟆,它可以把代理對(duì)象作為裝載對(duì)象方法的一個(gè)參數(shù)傳遞;

Bean操作

CGLIB除了可以用Enhancer來實(shí)現(xiàn)動(dòng)態(tài)代理外觅赊,其net.sf.cglib.beans包中還可對(duì)其JAVA Bean進(jìn)行操作,配合JAVA-Reflection(反射機(jī)制)同樣可以做到動(dòng)態(tài)制作類琼稻、創(chuàng)建類吮螺。

創(chuàng)建Bean

使用net.sf.cglib.beans.BeanGenerator類可以進(jìn)行創(chuàng)建Bean對(duì)象操作。

BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator.addProperty("name",String.class);
Object o1 = beanGenerator.create();

Method m1 = o1.getClass().getMethod("setName",String.class);
m1.invoke(o1,"zxx");

Method m2 = o1.getClass().getMethod("getName");
System.out.println(m2.invoke(o1));

使用CGLIB的BeanGenerator動(dòng)態(tài)的創(chuàng)建了一個(gè)Bean對(duì)象,使用addProperty方法可以添加一個(gè)屬性鸠补,在添加屬性的同時(shí)BeanGenerator會(huì)自動(dòng)生成其Getting萝风、Setting方法。

使用其BeanGenerator的create的方法進(jìn)行Bean實(shí)例化紫岩,配合Java反射可以進(jìn)行對(duì)Bean的操作闹丐。

創(chuàng)建只讀 Bean

使用net.sf.cglib.beans.ImmutableBean類可以進(jìn)行根據(jù)現(xiàn)有的Bean對(duì)象,創(chuàng)建一個(gè)對(duì)應(yīng)的只讀Bean對(duì)象被因。

Student student = new Student();
student.setName("zzz");
// 創(chuàng)建一個(gè)student實(shí)例的對(duì)應(yīng)只讀Bean
Student o = (Student) ImmutableBean.create(student);
student.setName("ooo");
System.out.println(o.getName()); //只讀Bean對(duì)象的值會(huì)自動(dòng)改變

o.setName("kkk"); // 拋出IllegalStateException,不可直接修改只讀Bean

ImmutableBean的對(duì)象雖然可以強(qiáng)制為對(duì)應(yīng)設(shè)置的Bean類型卿拴,但是無法直接進(jìn)行修改設(shè)置,其屬性隨原Bean改變而改變梨与。

利用BeanMap實(shí)現(xiàn)對(duì)象與Map的轉(zhuǎn)換

利用net.sf.cglib.beans.BeanMap類可實(shí)現(xiàn)將Bean對(duì)象轉(zhuǎn)換為Map對(duì)象堕花,其中Bean對(duì)象中的屬性全部以Key-Value方式放置在Map中。

BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator.addProperty("name",String.class);
beanGenerator.addProperty("age",Integer.class);
Object o1 = beanGenerator.create();

Method m1 = o1.getClass().getMethod("setName",String.class);
m1.invoke(o1,"zxx");
Method m2 = o1.getClass().getMethod("setAge",Integer.class);
m2.invoke(o1,23);
// 為o1對(duì)象創(chuàng)建BeanMap
BeanMap beanMap = BeanMap.create(o1);
Integer age = (Integer) beanMap.get("age");

BeanMap基于Map類型粥鞋,擁有Map的存儲(chǔ)功能缘挽,自然也可以用containsKey、containsValue呻粹、keySet等功能壕曼。

在Java中bean與map的轉(zhuǎn)換有很多種方式,比如通過ObjectMapper先將bean轉(zhuǎn)換為json等浊,再將json轉(zhuǎn)換為map 或者 通過Java反射等腮郊,但是這些方法都沒有使用CGLIB中的BeanMap來的快,因?yàn)檫@種方式效率極高筹燕,它跟第其它方式的區(qū)別就是因?yàn)樗褂昧司彺嬖桑詢?yōu)先考慮使用BeanMap。

利用Mixin實(shí)現(xiàn)多個(gè)對(duì)象整合為單個(gè)對(duì)象

Mixin能夠?qū)⒍鄠€(gè)Bean對(duì)象整合為一個(gè)Bean對(duì)象撒踪,這個(gè)Bean對(duì)象擁有它們所有方法过咬。

但前提就是這些Bean對(duì)象必須是實(shí)現(xiàn)接口的,Mixin通過接口來對(duì)對(duì)象進(jìn)行整合的制妄。

interface Interface1 {
    String one();
}

interface Interface2 {
    String two();
}

interface MixinInterface extends Interface1, Interface2 {
    String one(String ok);
    String three();
}

static class Class1 implements Interface1 {
    @Override
    public String one() {
        return "one";
    }
}

static class Class2 implements MixinInterface {
    @Override
    public String three() {
        return "three";
    }

    @Override
    public String one() {
        return "no 22one";
    }

    @Override
    public String one(String ok) {
        return "no one";
    }

    @Override
    public String two() {
        return "two";
    }
    public static void main(String[] args) {
        Mixin mixin = Mixin.create(new Class[]{Interface1.class,MixinInterface.class},
                new Object[]{new Class1(), new Class2()});
        MixinInterface mixinInterface = (MixinInterface) mixin;
        System.out.println(mixinInterface.two());
    }
}

Mixin.create方法接受兩個(gè)參數(shù):第一為接口類組掸绞,第二為對(duì)象組。其中接口組與對(duì)象組一一對(duì)應(yīng)耕捞,引索對(duì)應(yīng)衔掸。

其實(shí)Mixin這個(gè)工具幾乎不怎么被使用,因?yàn)樗罁?jù)與接口進(jìn)行創(chuàng)建砸脊,很多時(shí)候可以通過純Java的方式實(shí)現(xiàn)具篇,沒有必要使用Minix類。

類操作

CGLIB可以進(jìn)行對(duì)類進(jìn)行操作凌埂,獲取到類中的各個(gè)屬性、方法代理诗芜、接口創(chuàng)建等功能瞳抓,在某些方面上比JDK的反射更強(qiáng)大埃疫。

利用InterfaceMaker動(dòng)態(tài)創(chuàng)建接口

利用net.sf.cglib.proxy.InterfaceMaker可以動(dòng)態(tài)創(chuàng)建Interface接口,并且可以自定義接口內(nèi)容孩哑。

// 創(chuàng)建一個(gè)Signature方法認(rèn)證類
Signature signature = new Signature("hello", Type.INT_TYPE, new Type[]{Type.getType(String.class)});
InterfaceMaker maker = new InterfaceMaker();

maker.add(signature,new Type[0]);
Class aClass = maker.create();

上述的InterfaceMaker創(chuàng)建的接口中含有了一個(gè)方法栓霜,為Integer hello(String XXX)。

InterfaceMaker類的add方法用于向自定義接口添加方法横蜒,它有很多選擇胳蛮,它可以直接接受一個(gè)Method類等。

上面我們使用了傳入Signature類丛晌,它的定義起來更簡(jiǎn)單方便仅炊,它還需要一個(gè)拋出的exceptions數(shù)組,用于方法存在的exceptions拋出聲明澎蛛,當(dāng)然這兒可以直接創(chuàng)建一個(gè)空數(shù)組抚垄,來表示這個(gè)方法沒有異常拋出。

對(duì)于Signature方法認(rèn)證類谋逻,它實(shí)際是一個(gè)Method的簡(jiǎn)單描述類:(這個(gè)類是net.sf.cglib.core.Signature,不是SpringAOP中的Signature)

Signature的有兩個(gè)構(gòu)造方法:

/**
* @param name 方法名
* @param desc 簡(jiǎn)介
*/
public Signature(String name, String desc)
/**
* @param name 方法名
* @param returnType 返回類型數(shù)組
* @param argumentTypes 參數(shù)類型數(shù)組
*/    
public Signature(String name, Type returnType, Type[] argumentTypes)

其中我們常用第二個(gè)呆馁,它直接提供了返回類型和參數(shù)類型的選擇,在創(chuàng)建方法時(shí)這些都可以根據(jù)自己的類型來進(jìn)行調(diào)用毁兆。

由于接口僅僅只用做在編譯時(shí)期進(jìn)行類型檢查浙滤,因此在一個(gè)運(yùn)行的應(yīng)用中動(dòng)態(tài)的創(chuàng)建接口其實(shí)沒有什么用。

但是InterfaceMaker可以用來自動(dòng)生成代碼气堕,為以后的開發(fā)做準(zhǔn)備瓷叫。

利用MethodDelegate對(duì)方法進(jìn)行代理

CGLIB可以通過一個(gè)只含有一個(gè)方法的接口 使用MethodDelegate來 代理對(duì)象中某一個(gè)方法,最終讓該接口的那個(gè)方法來代理對(duì)象中指定的方法送巡。

interface ProxyMethod{
    String getValueFromProxy();
}

...
BeanGenerator beanGenerator = new BeanGenerator();
// 創(chuàng)建一個(gè)Bean 內(nèi)部擁有 value屬性
beanGenerator.addProperty("value",String.class);
Object o = beanGenerator.create();
Method method = o.getClass().getMethod("setValue", String.class);
method.invoke(o,"zty");

// 將其ProxyMethod接口的方法代理 o對(duì)象中g(shù)etValue方法
ProxyMethod delegate = (ProxyMethod)MethodDelegate.create(o, "getValue", ProxyMethod.class);
System.out.println(delegate.getValueFromProxy());  // 結(jié)果為 "zty"

Method.create方法接受3個(gè)參數(shù):

  1. 第二個(gè)參數(shù)為即將被代理的方法

  2. 第一個(gè)參數(shù)必須是一個(gè)無參數(shù)構(gòu)造的bean摹菠。因此MethodDelegate.create并不是你想象的那么有用

  3. 第三個(gè)參數(shù)為只含有一個(gè)方法的接口。當(dāng)這個(gè)接口中的方法被調(diào)用的時(shí)候骗爆,將會(huì)調(diào)用第一個(gè)參數(shù)所指向bean的第二個(gè)參數(shù)方法

MethodDelegate雖然用接口可以代理方法,但是擁有很多缺點(diǎn):

  1. 為每一個(gè)代理類創(chuàng)建了一個(gè)新的類次氨,這樣可能會(huì)占用大量的永久代堆內(nèi)存
  2. 你不能代理需要參數(shù)的方法
  3. 如果你定義的接口中的方法需要參數(shù),那么代理將不會(huì)工作摘投,并且也不會(huì)拋出異常煮寡;如果你的接口中方法需要其他的返回類型,那么將拋出IllegalArgumentException

所以大部分時(shí)間不建議使用MethodDelegate代理方法犀呼。

FastClass - 另一種Class類

CGLIB中的FastClass類是比JDK中Class類更好(它們聲稱的)幸撕。相比于傳統(tǒng)的Class類,F(xiàn)astClass引出了一個(gè)index下標(biāo)的新概念外臂。

通過create方法創(chuàng)建一個(gè)對(duì)應(yīng)的FastClass類卸伞,它通過數(shù)組存儲(chǔ)類中的所有method,constructor等class信息,用戶通過向數(shù)組下標(biāo)index尋找對(duì)應(yīng)的方法或者構(gòu)造器等柴淘,用方法簽名轉(zhuǎn)化為對(duì)應(yīng)index方法,用模板方式解決Java語法不支持問題炭菌,同時(shí)改善Java反射性能。

傳統(tǒng)的Class類使用Java反射機(jī)制來確定方法等逛漫,而FastClass則是通過使用index快速確定對(duì)應(yīng)的方法等黑低,從而達(dá)到高效率。

FastClass fastClass = FastClass.create(Student.class);
FastMethod fastMethod = fastClass.getMethod("getName",new Class[0]);

Student student = new Student();
student.setName("zty");
System.out.println(fastMethod.invoke(student,new Object[0]));  // 輸出"zty"

FastClass的使用反射和普通的Class類似酌毡,獲取到的FastMethod方法克握、FastConstructor構(gòu)造方法,需要參數(shù)也相同枷踏。

注意:FastMethod中的invoke方法是調(diào)用的FastClass中的invoke方法菩暗,它也沒有無參的方法,所以當(dāng)參的情況下呕寝,需要傳入一個(gè)空的對(duì)象數(shù)組勋眯。

FastClass的實(shí)現(xiàn)邏輯,是生成增強(qiáng)類實(shí)現(xiàn)invoke方法下梢,invoke方法中客蹋,用switch語義將被增強(qiáng)類的所有方法調(diào)用枚舉出來。用戶使用FastClass.invoke方法孽江,傳入方法簽名和被調(diào)用實(shí)例讶坯,從而達(dá)到不使用反射就能實(shí)現(xiàn)不確定方法的調(diào)用。

但是岗屏!FastClass 這一工具在提出來的時(shí)候Java反射并沒有進(jìn)行足夠的優(yōu)化辆琅,所以或許FastClass會(huì)更效率,但是從JDK7開始到目前的Java新版本的JVM擁有inflation這個(gè)概率这刷, 當(dāng)一個(gè)反射方法調(diào)用次數(shù)少于15次時(shí)婉烟,會(huì)使用調(diào)用native方法。大于15次之后則使用ASM生成新的類類處理反射調(diào)用暇屋。所以在JDK7后面的版本使用FastClass反而比Class慢似袁,所以不推薦使用!**

使用警告

使用所有的 CGLIB類進(jìn)行操作都會(huì)生成字節(jié)代碼咐刨,這會(huì)導(dǎo)致額外的類被存儲(chǔ)在 JVM 內(nèi)存的一個(gè)特殊區(qū)域昙衅,如果操作過多,容易造成永久堆滿定鸟,觸發(fā)OutOfMemory異常而涉。

或許這樣說來,大伙可能會(huì)放棄使用CGLIB联予,但是啼县,如果您明智而謹(jǐn)慎地使用 cglib材原,您可以用它做一些令人驚奇的事情,利大于弊谭羔,因此华糖,節(jié)制地使用 CGLIB進(jìn)行操作麦向。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘟裸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诵竭,更是在濱河造成了極大的恐慌话告,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵慰,死亡現(xiàn)場(chǎng)離奇詭異沙郭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)裳朋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門病线,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鲤嫡,你說我怎么就攤上這事送挑。” “怎么了暖眼?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵惕耕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我诫肠,道長(zhǎng)司澎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任栋豫,我火速辦了婚禮挤安,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丧鸯。我一直安慰自己蛤铜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布骡送。 她就那樣靜靜地躺著昂羡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摔踱。 梳的紋絲不亂的頭發(fā)上虐先,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音派敷,去河邊找鬼蛹批。 笑死撰洗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腐芍。 我是一名探鬼主播差导,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼猪勇!你這毒婦竟也來了设褐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤泣刹,失蹤者是張志新(化名)和其女友劉穎助析,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椅您,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡外冀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掀泳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雪隧。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖员舵,靈堂內(nèi)的尸體忽然破棺而出脑沿,到底是詐尸還是另有隱情,我是刑警寧澤固灵,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布捅伤,位于F島的核電站,受9級(jí)特大地震影響巫玻,放射性物質(zhì)發(fā)生泄漏丛忆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一仍秤、第九天 我趴在偏房一處隱蔽的房頂上張望熄诡。 院中可真熱鬧,春花似錦诗力、人聲如沸凰浮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袜茧。三九已至,卻和暖如春瓣窄,著一層夾襖步出監(jiān)牢的瞬間笛厦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工俺夕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裳凸,地道東北人贱鄙。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像姨谷,于是被迫代替她去往敵國和親逗宁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容