茫茫網海中的冷日
         
茫茫網海中的冷日
發生過的事,不可能遺忘,只是想不起來而已!
 恭喜您是本站第 1729835 位訪客!  登入  | 註冊
主選單

Google 自訂搜尋

Goole 廣告

隨機相片
PIMG_00367.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

爪哇咖啡屋 : [轉貼]CGLIB(Code Generation Library)詳解

發表者 討論內容
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15773
[轉貼]CGLIB(Code Generation Library)詳解

CGLIB(Code Generation Library)詳解

2017年04月19日 10:55:11

什麼是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組成結構

image

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

  1. 多重代理和方法代理差不多,都是將代理類方法的調用委託給被代理類。使用前提是需要一個接口,以及一個類實現了該接口
  2. 通過這種interface的繼承關係,我們能夠將接口上方法的調用分散給各個實現類上面去。
  3. 多重代理的缺點是接口只能含有一個方法,如果被代理的方法擁有返回值,那麼調用代理類的返回值為最後一個添加的被代理類的方法返回值

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動態代理的區別

  1. Java動態代理只能夠對接口進行代理,不能對普通的類進行代理(因為所有生成的代理類的父類為Proxy,Java類繼承機制不允許多重繼承);CGLIB能夠代理普通類;
  2. Java動態代理使用Java原生的反射API進行操作,在生成類上比較高效;CGLIB使用ASM框架直接對字節碼進行操作,在類的執行過程中比較高效
  3. 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



原文出處:CGLIB(Code Generation Library)详解 - 小楼一夜听春雨 - CSDN博客
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15773
[轉貼]CGLIB介紹與原理

CGLIB介紹與原理

2016年03月22日 18:58:06

CGLIB介紹與原理(部分節選自網絡)

一、什麼是CGLIB?

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

二、CGLIB原理

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

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

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

三、CGLIB的應用

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

四、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、被代理類

首先,定義一個類,該類沒有實現任何接口
<span >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();
}
}</span>


2、攔截器

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


<span >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;
}
}
</span>


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

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

其中,proxy.invokeSuper(obj,arg):

調用代理類實例上的proxy方法的父類方法(即實體類TargetObject中對應的方法)

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

3、生成動態代理類
<span >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));
}
}
</span>

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

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

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

4、回調過濾器CallbackFilter

一、作用

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

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


定義實現過濾器CallbackFilter接口的類:


<span >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;
}
}
</span>


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

<span >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));
}
}
</span>



<span >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;
}
}
</span>


5.延遲加載對像

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

另一種延遲加載接口Dispatcher。

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

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


二、示例:
首先定義一個實體類LoaderBean,該Bean內有一個需要延遲加載的屬性PropertyBean。



<span >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 + "]";
}
}
</span>



<span >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();
}
}
</span>



<span >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;
}
}
</span>



<span >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;
}
}
</span>


6.接口生成器InterfaceMaker

一、作用:

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

二、示例:


<span >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"}));
}
}
</span>




原文出處:CGLIB介绍与原理 - zghwaicsdn的专栏 - CSDN博客
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15773
[轉貼]略談 cglib

略談 cglib

Java 本身的動態代理,必須要基於介面定義,若類別並沒有實作特定介面,就無法使用 Java 動態代理機制,這時可以透過 cglib(Code Generation Library),它的底層基於 ASM,可於執行時期修改位元組碼、動態生成代理物件。

若要將〈 動態代理〉的專案範例改用 cglib,可以在 gradle.build 中加入相依:


compile 'cglib:cglib-nodep:'3.2.9'

接著實作一個攔截器,它實作了 MethodInterceptor


package cc.openhome.interceptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Logger;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Logger.getLogger(target.getClass().getName())
.info(String.format("%s.%s(%s)",
target.getClass().getName(), method.getName(), Arrays.toString(args)));
return proxy.invokeSuper(target, args);
}
}

然後 AppConfig 基於 cglib 來建立代理物件:


package cc.openhome;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import cc.openhome.interceptor.LoggingInterceptor;
import cc.openhome.model.AccountDAO;
import net.sf.cglib.proxy.Enhancer;
@Configuration
@ComponentScan
public class AppConfig {
@Autowired
@Qualifier("accountDAOJdbcImpl")
private AccountDAO accountDAO;
@Autowired
private DataSource dataSource;
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:testData.sql")
.build();
}
@Bean
public AccountDAO accountDAO() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(accountDAO.getClass());
enhancer.setCallback(new LoggingInterceptor());
return (AccountDAO) enhancer.create(
new Class[] {DataSource.class},
new Object[] {dataSource}
);
}
}

在進行動態代理時,Spring 底層預設會採用 Java 動態代理,若目標對象沒有實作介面則改用 cglib,實際上,AOP 是個概念,各廠商會有各自的實現,為此, AOP Alliance 定義了一套介面標準, MethodInterceptor 在 AOP Alliance 的定義則是:


package org.aopalliance.intercept;
public interface MethodInterceptor {
Object invoke(MethodInvocation methodInvocation) throws Throwable;
}

若使用 Spring AOP,你也可以實作這個介面,這部份可參考我舊版文件中的 AroundAdvice 中的範例。


原文出處:略談 cglib
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15773
[轉貼]Java Proxy和CGLIB動態代理原理

Java Proxy和CGLIB動態代理原理

動態代理在Java中有著廣泛的應用,比如Spring AOP,Hibernate數據查詢、測試框架的後端mock、RPC,Java註解對像獲取等。靜態代理的代理關係在編譯時就確定了,而動態代理的代理關係是在編譯期確定的。靜態代理實現簡單,適合於代理類較少且確定的情況,而動態代理則給我們提供了更大的靈活性。今天我們來探討Java中兩種常見的動態代理方式:JDK原生動態代理和CGLIB動態代理

JDK原生動態代理

先從直觀的示例說起,假設我們有一個接口 Hello和一個簡單實現 HelloImp


// 接口
interface Hello{
String sayHello(String str);
}
// 實現
class HelloImp implements Hello{
@Override
public String sayHello(String str) {
return "HelloImp: " + str;
}
}

這是Java種再常見不過的場景,使用接口制定協議,然後用不同的實現來實現具體行為。假設你已經拿到上述類庫,如果我們想通過日誌記錄對 sayHello()的調用,使用靜態代理可以這樣做:


// 靜態代理方式
class StaticProxiedHello implements Hello{
...
private Hello hello = new HelloImp();
@Override
public String sayHello(String str) {
logger.info("You said: " + str);
return hello.sayHello(str);
}
}

上例中靜態代理類 StaticProxiedHello作為 HelloImp的代理,實現了相同的 Hello接口。用Java動態代理可以這樣做:

  1. 首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
  2. 然後在需要使用Hello的時候,通過JDK動態代理獲取Hello的代理對象。

// Java Proxy
// 1. 首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
class LogInvocationHandler implements InvocationHandler{
...
private Hello hello;
public LogInvocationHandler(Hello hello) {
this.hello = hello;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("sayHello".equals(method.getName())) {
logger.info("You said: " + Arrays.toString(args));
}
return method.invoke(hello, args);
}
}
// 2. 然後在需要使用Hello的時候,通過JDK動態代理獲取Hello的代理對象。
Hello hello = (Hello)Proxy.newProxyInstance(
getClass().getClassLoader(), // 1. 類加載器
new Class<?>[] {Hello.class}, // 2. 代理需要實現的接口,可以有多個
new LogInvocationHandler(new HelloImp()));// 3. 方法調用的實際處理者
System.out.println(hello.sayHello("I love you!"));

運行上述代碼輸出結果:


日誌信息: You said: [I love you!]
HelloImp: I love you!

上述代碼的關鍵是 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,該方法會根據指定的參數動態創建代理對象。三個參數的意義如下:

  1. loader,指定代理對象的類加載器;
  2. interfaces,代理對像需要實現的接口,可以同時指定多個接口;
  3. handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這裡(*注意1)。

newProxyInstance()會返回一個實現了指定接口的代理對象,對該對象的所有方法調用都會轉發給 InvocationHandler.invoke()方法。理解上述代碼需要對Java反射機制有一定瞭解。動態代理神奇的地方就是:

  1. 代理對象是在程序運行時產生的,而不是編譯期;
  2. 對代理對象的所有接口方法調用都會轉發到 InvocationHandler.invoke()方法,在 invoke()方法裡我們可以加入任何邏輯,比如修改方法參數,加入日誌功能、安全檢查功能等;之後我們通過某種方式執行真正的方法體,示例中通過反射調用了Hello對象的相應方法,還可以通過RPC調用遠程方法。

注意1:對於從Object中繼承的方法,JDK Proxy會把 hashCode()equals()toString()這三個非接口方法轉發給 InvocationHandler,其餘的Object方法則不會轉發。詳見
JDK Proxy官方文檔

如果對JDK代理後的對象類型進行深挖,可以看到如下信息:


# Hello代理對象的類型信息
class=class jdkproxy.$Proxy0
superClass=class java.lang.reflect.Proxy
interfaces:
interface jdkproxy.Hello
invocationHandler=jdkproxy.LogInvocationHandler@a09ee92

代理對象的類型是 jdkproxy.$Proxy0,這是個動態生成的類型,類名是形如$ProxyN的形式;父類是 java.lang.reflect.Proxy,所有的JDK動態代理都會繼承這個類;同時實現了 Hello接口,也就是我們接口列表中指定的那些接口。

如果你還對 jdkproxy.$Proxy0具體實現感興趣,它大致長這個樣子:


// JDK代理類具體實現
public final class $Proxy0 extends Proxy implements Hello
{
...
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
...
@Override
public final String sayHello(String str){
...
return super.h.invoke(this, m3, new Object[] {str});// 將方法調用轉發給invocationhandler
...
}
...
}

這些邏輯沒什麼複雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。更多詳情,可參考BrightLoong的 Java靜態代理&動態代理筆記

Java動態代理為我們提供了非常靈活的代理機制,但Java動態代理是基於接口的,如果對像沒有實現接口我們該如何代理呢?CGLIB登場。

CGLIB動態代理

CGLIB( Code Generation Library)是一個基於 ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承方式實現代理。

來看示例,假設我們有一個沒有實現任何接口的類 HelloConcrete


public class HelloConcrete {
public String sayHello(String str) {
return "HelloConcrete: " + str;
}
}

因為沒有實現接口該類無法使用JDK代理,通過CGLIB代理實現如下:

  1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
  2. 然後在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理對象。

// CGLIB動態代理
// 1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
...
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("You said: " + Arrays.toString(args));
return proxy.invokeSuper(obj, args);
}
}
// 2. 然後在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理對象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());
HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

運行上述代碼輸出結果:


日誌信息: You said: [I love you!]
HelloConcrete: I love you!

上述代碼中,我們通過CGLIB的 Enhancer來指定要代理的目標對像、實際處理代理邏輯的對象,最終通過調用 create()方法得到代理對象,對這個對象所有非final方法的調用都會轉發給 MethodInterceptor.intercept()方法,在 intercept()方法裡我們可以加入任何邏輯,比如修改方法參數,加入日誌功能、安全檢查功能等;通過調用 MethodProxy.invokeSuper()方法,我們將調用轉發給原始對象,具體到本例,就是 HelloConcrete的具體方法。CGLIG中 MethodInterceptor的作用跟JDK代理中的 InvocationHandler很類似,都是方法調用的中轉站。

注意:對於從Object中繼承的方法,CGLIB代理也會進行代理,如 hashCode()equals()toString()等,但是 getClass()wait()等方法不會,因為它是final方法,CGLIB無法代理。

如果對CGLIB代理之後的對象類型進行深挖,可以看到如下信息:


# HelloConcrete代理對象的類型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces:
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

我們看到使用CGLIB代理之後的對象類型是 cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,這是CGLIB動態生成的類型;父類是 HelloConcrete,印證了CGLIB是通過繼承實現代理;同時實現了 net.sf.cglib.proxy.Factory接口,這個接口是CGLIB自己加入的,包含一些工具方法。

注意,既然是繼承就不得不考慮final的問題。我們知道final類型不能有子類,所以CGLIB不能代理final類型,遇到這種情況會拋出類似如下異常:

java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete

同樣的,final方法是不能重載的,所以也不能通過CGLIB代理,遇到這種情況不會拋異常,而是會跳過final方法只代理其他方法。

如果你還對代理類 cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52具體實現感興趣,它大致長這個樣子:


// CGLIB代理類具體實現
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
extends HelloConcrete
implements Factory
{
...
private MethodInterceptor CGLIB$CALLBACK_0; // ~~
...
public final String sayHello(String paramString)
{
...
MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
if (tmp17_14 != null) {
// 將請求轉發給MethodInterceptor.intercept()方法。
return (String)tmp17_14.intercept(this,
CGLIB$sayHello$0$Method,
new Object[] { paramString },
CGLIB$sayHello$0$Proxy);
}
return super.sayHello(paramString);
}
...
}

上述代碼我們看到,當調用代理對象的 sayHello()方法時,首先會嘗試轉發給 MethodInterceptor.intercept()方法,如果沒有 MethodInterceptor就執行父類的 sayHello()。這些邏輯沒什麼複雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。如何獲取CGLIB代理類字節碼可參考 Access the generated byte[] array directly

更多關於CGLIB的介紹可以參考Rafael Winterhalter的 cglib: The missing manual,一篇很深入的文章。

結語

本文介紹了Java兩種常見動態代理機制的用法和原理,JDK原生動態代理是Java原生支持的,不需要任何外部依賴,但是它只能基於接口進行代理;CGLIB通過繼承的方式進行代理,無論目標對像有沒有實現接口都可以代理,但是無法處理final的情況。

動態代理是 Spring AOP( Aspect Orient Programming, 面向切面編程)的實現方式,瞭解動態代理原理,對理解Spring AOP大有幫助。



原文出處: Java Proxy和CGLIB动态代理原理 - CarpenterLee - 博客园
前一個主題 | 下一個主題 | 頁首 | | |



Powered by XOOPS 2.0 © 2001-2008 The XOOPS Project|