原創(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)听系,
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ù):
第二個(gè)參數(shù)為即將被代理的方法
第一個(gè)參數(shù)必須是一個(gè)無參數(shù)構(gòu)造的bean摹菠。因此MethodDelegate.create并不是你想象的那么有用
第三個(gè)參數(shù)為只含有一個(gè)方法的接口。當(dāng)這個(gè)接口中的方法被調(diào)用的時(shí)候骗爆,將會(huì)調(diào)用第一個(gè)參數(shù)所指向bean的第二個(gè)參數(shù)方法
MethodDelegate雖然用接口可以代理方法,但是擁有很多缺點(diǎn):
- 為每一個(gè)代理類創(chuàng)建了一個(gè)新的類次氨,這樣可能會(huì)占用大量的永久代堆內(nèi)存
- 你不能代理需要參數(shù)的方法
- 如果你定義的接口中的方法需要參數(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)行操作麦向。