什麼是CGLIB
CGLIB是一個強大的、高性能的代碼生成庫。其被廣泛應用於AOP框架(Spring、dynaop)中,用以提供方法攔截操作。Hibernate作為一個比較受歡迎的ORM框架,同樣使用CGLIB來代理單端(多對一和一對一)關聯(延遲提取集合使用的另一種機制)。CGLIB作為一個開源項目,其代碼托管在github,地址為: https://github.com/cglib/cglib
為什麼使用CGLIB
CGLIB代理主要通過對字節碼的操作,為對像引入間接級別,以控制對象的訪問。我們知道Java中有一個動態代理也是做這個事情的,那我們為什麼不直接使用Java動態代理,而要使用CGLIB呢?答案是CGLIB相比於JDK動態代理更加強大,JDK動態代理雖然簡單易用,但是其有一個致命缺陷是,只能對接口進行代理。如果要代理的類為一個普通類、沒有接口,那麼Java動態代理就沒法使用了。關於Java動態代理,可以參者這裡 Java動態代理分析
CGLIB組成結構

CGLIB底層使用了ASM(一個短小精悍的字節碼操作框架)來操作字節碼生成新的類。除了CGLIB庫外,腳本語言(如Groovy何BeanShell)也使用ASM生成字節碼。ASM使用類似SAX的解析器來實現高性能。我們不鼓勵直接使用ASM,因為它需要對Java字節碼的格式足夠的瞭解
例子
說了這麼多,可能大家還是不知道CGLIB是幹什麼用的。下面我們將使用一個簡單的例子來演示如何使用CGLIB對一個方法進行攔截。
首先,我們需要在工程的POM文件中引入cglib的dependency,這裡我們使用的是2.2.2版本
dependency>
groupId>
artifactId>
version>
dependency>
依賴包下載後,我們就可以幹活了,按照國際慣例,寫個hello world
class SampleClass {
test(){
System.);
}
main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.);
Object result = proxy.invokeSuper(obj, args);
System.);
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}
在mian函數中,我們通過一個Enhancer和一個MethodInterceptor來實現對方法的攔截,運行程序後輸出為:
run...
world
run...
在上面的程序中,我們引入了Enhancer和MethodInterceptor,可能有些讀者還不太瞭解。別急,我們後面將會一一進行介紹。就目前而言,一個使用CGLIB的小demo就完成了
常用的API
目前網絡上對CGLIB的介紹資料比較少,造成對cglib的學習困難。這裡我將對cglib中的常用類進行一個介紹。為了避免解釋的不清楚,我將為每個類都配有一個demo,用來做進一步的說明。首先就從Enhancer開始吧。
Enhancer
Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多(如果對Proxy不懂,可以參考 這裡)。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創建一個被代理對象的子類並且攔截所有的方法調用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什麼不能持久化final class的原因。
class SampleClass {
test(String input){
;
}
}
下面我們將以這個類作為主要的測試類,來測試調用各種方法
@Test
testFixedValue(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValue() {
@Override
loadObject() throws Exception {
;
}
});
SampleClass proxy = (SampleClass) enhancer.create();
System.//攔截test,輸出Hello cglib
System.out.println(proxy.toString());
System.out.println(proxy.getClass());
System.out.println(proxy.hashCode());
}
程序的輸出為:
Hello cglib
Hello cglib
class .SampleClass$$EnhancerByCGLIB$$e3ea9b7
.Number
at .hashCode(<generated>)
...
上述代碼中,FixedValue用來對所有攔截的方法返回相同的值,從輸出我們可以看出來,Enhancer對非final方法test()、toString()、hashCode()進行了攔截,沒有對getClass進行攔截。由於hashCode()方法需要返回一個Number,但是我們返回的是一個String,這解釋了上面的程序中為什麼會拋出異常。
Enhancer.setSuperclass用來設置父類型,從toString方法可以看出,使用CGLIB生成的類為被代理類的一個子類,形如:SampleClass $ $EnhancerByCGLIB $ $e3ea9b7
Enhancer.create(Object…)方法是用來創建增強對象的,其提供了很多不同參數的方法用來匹配被增強類的不同構造方法。(雖然類的構造放法只是Java字節碼層面的函數,但是Enhancer卻不能對其進行操作。Enhancer同樣不能操作static或者final類)。我們也可以先使用Enhancer.createClass()來創建字節碼(.class),然後用字節碼動態的生成增強後的對象。
可以使用一個InvocationHandler(如果對InvocationHandler不懂,可以參考 這裡)作為回調,使用invoke方法來替換直接訪問類的方法,但是你必須注意死循環。因為invoke中調用的任何原代理類方法,均會重新代理到invoke方法中。
throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new InvocationHandler() {
@Override
throws Throwable {
if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
;
}else{
);
}
}
});
SampleClass proxy = (SampleClass) enhancer.create();
Assert.assertEquals(null));
Assert.assertNotEquals(, proxy.toString());
}
為了避免這種死循環,我們可以使用MethodInterceptor,MethodInterceptor的例子在前面的hello world中已經介紹過了,這裡就不浪費時間了。
有些時候我們可能只想對特定的方法進行攔截,對其他的方法直接放行,不做任何操作,使用Enhancer處理這種需求同樣很簡單,只需要一個CallbackFilter即可:
@Test
throws Exception{
Enhancer enhancer = new Enhancer();
CallbackHelper callbackHelper = 0]) {
@Override
getCallback(Method method) {
if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
new FixedValue() {
@Override
throws Exception {
;
}
};
}else{
return NoOp.INSTANCE;
}
}
};
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallbackFilter(callbackHelper);
enhancer.setCallbacks(callbackHelper.getCallbacks());
SampleClass proxy = (SampleClass) enhancer.create();
Assert.assertEquals(null));
Assert.assertNotEquals(,proxy.toString());
System.out.println(proxy.hashCode());
}
ImmutableBean
通過名字就可以知道,不可變的Bean。ImmutableBean允許創建一個原來對象的包裝類,這個包裝類是不可變的,任何改變底層對象的包裝類操作都會拋出IllegalStateException。但是我們可以通過直接操作底層對像來改變包裝類對象。這有點類似於Guava中的不可變視圖
為了對ImmutableBean進行測試,這裡需要再引入一個bean
class SampleBean {
value;
SampleBean() {
}
value) {
value;
}
getValue() {
value;
}
value) {
value;
}
}
然後編寫測試類如下:
@Test(expected = IllegalStateException.class)
throws Exception{
SampleBean bean = new SampleBean();
bean.setValue();
SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //創建不可變類
Assert.assertEquals(,immutableBean.getValue());
bean.setValue(//可以通過底層對像來進行修改
Assert.assertEquals(, immutableBean.getValue());
immutableBean.setValue(//直接修改將throw exception
}
Bean generator
cglib提供的一個操作bean的工具,使用它能夠在運行時動態的創建一個bean。
@Test
public void testBeanGenerator() throws Exception{
BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator;
Object myBean = beanGenerator;
Method setter = myBean;
setter;
Method getter = myBean;
Assert;
}
在上面的代碼中,我們使用cglib動態的創建了一個和SampleBean相同的Bean對象,包含一個屬性value以及getter、setter方法
Bean Copier
cglib提供的能夠從一個bean複製到另一個bean中,而且其還提供了一個轉換器,用來在轉換的時候對bean的屬性進行操作。
class OtherSampleBean {
value;
getValue() {
value;
}
value) {
value;
}
}
@Test
testBeanCopier() throws Exception{
BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, //設置為true,則使用converter
SampleBean myBean = new SampleBean();
myBean.setValue();
OtherSampleBean otherBean = new OtherSampleBean();
copier.copy(myBean, otherBean, //設置為true,則傳入converter指明怎麼進行轉換
assertEquals(, otherBean.getValue());
}
BulkBean
相比於BeanCopier,BulkBean將copy的動作拆分為getPropertyValues和setPropertyValues兩個方法,允許自定義處理屬性
@Test
public void testBulkBean() throws Exception{
BulkBean bulkBean = BulkBean.create(SampleBean.class,
},
},
class});
SampleBean bean = new SampleBean();
bean.setValue();
Object[] propertyValues = bulkBean.getPropertyValues(bean);
assertEquals(1, bulkBean.getPropertyValues(bean).length);
assertEquals(0]);
bulkBean.setPropertyValues(bean,});
assertEquals(, bean.getValue());
}
使用注意:
1. 避免每次進行BulkBean.create創建對象,一般將其聲明為static的
2. 應用場景:針對特定屬性的get,set操作,一般適用通過xml配置注入和注出的屬性,運行時才確定處理的Source,Target類,只需要關注屬性名即可。
BeanMap
BeanMap類實現了Java Map,將一個bean對像中的所有屬性轉換為一個String-to-Obejct的Java Map
@Test
public void testBeanMap() throws Exception{
BeanGenerator generator = new BeanGenerator();
generator;
generator;
Object bean = generator;
Method setUserName = bean;
Method setPassword = bean;
setUserName;
setPassword;
BeanMap map = BeanMap;
Assert;
Assert;
}
我們使用BeanGenerator生成了一個含有兩個屬性的Java Bean,對其進行賦值操作後,生成了一個BeanMap對象,通過獲取值來進行驗證
keyFactory
keyFactory類用來動態生成接口的實例,接口需要只包含一個newInstance方法,返回一個Object。keyFactory為構造出來的實例動態生成了Object.equals和Object.hashCode方法,能夠確保相同的參數構造出的實例為單例的。
interface SampleKeyFactory {
Object newInstance(String first, int second);
}
我們首先構造一個滿足條件的接口,然後進行測試
@Test
throws Exception{
SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);
Object key = keyFactory.newInstance(42);
Object key1 = keyFactory.newInstance(42);
Assert.assertEquals(key,key1);//測試參數相同,結果是否相等
}
Mixin(混合)
Mixin能夠讓我們將多個對象整合到一個對像中去,前提是這些對像必須是接口的實現。可能這樣說比較晦澀,以代碼為例:
MixinInterfaceTest {
Interface1{
String first();
}
Interface2{
String second();
}
Interface1{
@Override
public String first() {
;
}
}
Interface2{
@Override
public String second() {
;
}
}
Interface2{
}
@Test
Exception{
Mixin mixin = Mixin.create(Class[]{Interface1.class, Interface2.class,
MixinInterface.class}, new Class2()});
MixinInterface mixinDelegate = (MixinInterface) mixin;
assertEquals(, mixinDelegate.first());
assertEquals(, mixinDelegate.second());
}
}
Mixin類比較尷尬,因為他要求Minix的類(例如MixinInterface)實現一些接口。既然被Minix的類已經實現了相應的接口,那麼我就直接可以通過純Java的方式實現,沒有必要使用Minix類。
String switcher
用來模擬一個String到int類型的Map類型。如果在Java7以後的版本中,類似一個switch語句。
@Test
throws Exception{
String[] strings = };
20};
StringSwitcher stringSwitcher = StringSwitcher.create(strings,values,true);
assertEquals());
assertEquals());
assertEquals(-));
}
Interface Maker
正如名字所言,Interface Maker用來創建一個新的Interface
@Test
throws Exception{
Signature signature = new Type[]{Type.INT_TYPE});
InterfaceMaker interfaceMaker = new InterfaceMaker();
interfaceMaker.add(signature, 0]);
Class iface = interfaceMaker.create();
assertEquals(1, iface.getMethods().length);
assertEquals(0].getName());
assertEquals(0].getReturnType());
}
上述的Interface Maker創建的接口中只含有一個方法,簽名為double foo(int)。Interface Maker與上面介紹的其他類不同,它依賴ASM中的Type類型。由於接口僅僅只用做在編譯時期進行類型檢查,因此在一個運行的應用中動態的創建接口沒有什麼作用。但是InterfaceMaker可以用來自動生成代碼,為以後的開發做準備。
Method delegate
MethodDelegate主要用來對方法進行代理
interface BeanDelegate{
String getValueFromDelegate();
}
@Test
testMethodDelegate() throws Exception{
SampleBean bean = new SampleBean();
bean.setValue();
BeanDelegate , BeanDelegate.class);
assertEquals(delegate.getValueFromDelegate());
}
關於Method.create的參數說明:
1. 第二個參數為即將被代理的方法
2. 第一個參數必須是一個無參數構造的bean。因此MethodDelegate.create並不是你想像的那麼有用
3. 第三個參數為只含有一個方法的接口。當這個接口中的方法被調用的時候,將會調用第一個參數所指向bean的第二個參數方法
缺點:
1. 為每一個代理類創建了一個新的類,這樣可能會佔用大量的永久代堆內存
2. 你不能代理需要參數的方法
3. 如果你定義的接口中的方法需要參數,那麼代理將不會工作,並且也不會拋出異常;如果你的接口中方法需要其他的返回類型,那麼將拋出IllegalArgumentException
MulticastDelegate
- 多重代理和方法代理差不多,都是將代理類方法的調用委託給被代理類。使用前提是需要一個接口,以及一個類實現了該接口
- 通過這種interface的繼承關係,我們能夠將接口上方法的調用分散給各個實現類上面去。
- 多重代理的缺點是接口只能含有一個方法,如果被代理的方法擁有返回值,那麼調用代理類的返回值為最後一個添加的被代理類的方法返回值
DelegatationProvider {
void setValue(String value);
}
DelegatationProvider {
private String value;
@Override
setValue(String value) {
this.value = value;
}
getValue() {
return value;
}
}
@Test
throws Exception{
MulticastDelegate multicastDelegate = MulticastDelegate.create(DelegatationProvider.class);
SimpleMulticastBean first = new SimpleMulticastBean();
SimpleMulticastBean second = new SimpleMulticastBean();
multicastDelegate = multicastDelegate.add(first);
multicastDelegate = multicastDelegate.add(second);
DelegatationProvider provider = (DelegatationProvider) multicastDelegate;
provider.setValue();
assertEquals(, first.getValue());
assertEquals(, second.getValue());
}
Constructor delegate
為了對構造函數進行代理,我們需要一個接口,這個接口只含有一個Object newInstance(…)方法,用來調用相應的構造函數
interface SampleBeanConstructorDelegate{
Object newInstance(String value);
}
/**
* 對構造函數進行代理
* @throws Exception
*/
@Test
throws Exception{
SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
SampleBean.class, SampleBeanConstructorDelegate.class);
SampleBean bean = (SampleBean) constructorDelegate.newInstance();
assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
System.out.println(bean.getValue());
}
Parallel Sorter(並行排序器)
能夠對多個數組同時進行排序,目前實現的算法有歸並排序和快速排序
@Test
testParallelSorter() throws Exception{
Integer[][] value = {
{0},
{0}
};
ParallelSorter.create(0);
value){
1;
int val : row){
assertTrue(former < val);
former = val;
}
}
}
FastClass
顧明思義,FastClass就是對Class對像進行特定的處理,比如通過數組保存method引用,因此FastClass引出了一個index下標的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的獲取method的方法。通過數組存儲method,constructor等class信息,從而將原先的反射調用,轉化為class.index的直接調用,從而體現所謂的FastClass。
@Test
throws Exception{
FastClass fastClass = FastClass.create(SampleBean.class);
FastMethod fastMethod = fastClass.getMethod(0]);
SampleBean bean = new SampleBean();
bean.setValue();
assertEquals(0]));
}
注意
由於CGLIB的大部分類是直接對Java字節碼進行操作,這樣生成的類會在Java的永久堆中。如果動態代理操作過多,容易造成永久堆滿,觸發OutOfMemory異常。
CGLIB和Java動態代理的區別
- Java動態代理只能夠對接口進行代理,不能對普通的類進行代理(因為所有生成的代理類的父類為Proxy,Java類繼承機制不允許多重繼承);CGLIB能夠代理普通類;
- Java動態代理使用Java原生的反射API進行操作,在生成類上比較高效;CGLIB使用ASM框架直接對字節碼進行操作,在類的執行過程中比較高效
3.
CGLIB相關的文章:
- http://jnb.ociweb.com/jnb/jnbNov2005.html
- http://www.iteye.com/topic/799827
- http://mydailyjava.blogspot.kr/2013/11/cglib-missing-manual.html