Java代理模式(3)一CGLib動態代理
目錄
前言
Java代理模式(2)一動態代理中提到Java的動態代理只侷限於實現介面的實現類(
RealSubject/RealSubject2
都實現ProblemInterface
),儘管比起靜態代理優點有很多,但是實際業務中不是所有的類都會實現一個介面,在Spring
、Hibernate
這些框架更是很明顯,所以它們都會用到了CGLib
對實現介面的類動態生成代理類
一、CGLib原理
1、什麼是CGLib?
CGLib
是一個強大的,高效能,高質量的程式碼生成類庫(Code Generation Library
),提供比反射更為強大方便的特性來實現比Java動態代理更為靈活的代理。它為沒有實現介面的類提供代理,是JDK動態代理的一種補充與擴充。
2、CGLib原理
(1)動態生成一個代理類的子類,子類重寫要代理的類的所有方法(不包括final
修飾的,實際上是繼承了重寫了被代理類的所有方法,自然final
不能被重寫代理)。在子類中採用方法攔截的技術攔截intercept
所有父類方法的呼叫,同時織入橫切邏輯。
理解起來可能會有點晦澀,先進行CGLib
相關的方法介紹或許能理解:
net.sf.cglib.proxy.Enhancer
位元組碼增強器,可以很方便的對類進行拓展,使用Enhancer.create()
方法建立動態代理類返回。net.sf.cglib.proxy.MethodInterceptor
主要的方法攔截類,被代理物件RealSubject
method()
的呼叫都會轉向實現此介面的intercept()
方法。具體請看下圖。net.sf.cglib.proxy.MethodProxy
JDK
的java.lang.reflect.Method
類的代理類,可以方便的實現對目標物件方法的呼叫。
- 在
intercept()
中我們可以對被代理物件方法進行呼叫,在呼叫的前後還可以加入其它業務,也就是織入橫切邏輯
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
System.out.println("呼叫前相關操作...");
Object result = proxy.invokeSuper(obj, args); //呼叫業務類的方法
System.out.println("呼叫後相關操作...");
return result ;
}
- 當對
Proxy
代理中所有方法的呼叫時,都會轉向MethodInterceptor
型別的攔截intercept()
方法,在攔截方法中再呼叫真實業務RealSubject
物件相應的方法。
(2)它是通過底層ASM
位元組碼相關處理,轉為位元組碼生成新的代理類的子類,所以會比通過Java的反射機制建立動態代理更加有效率。關於ASM
可以檢視此部落格Java ASM介紹、ASM快速入門
二、完整例子程式碼實現
業務類RealSubject3
:
public class RealSubject3 {
public void resolve() {
System.out.println("真實業務類RealSubject3主要實現CRUD操作...");
}
}
實現MethodInterceptor
介面的MethodInterceptorImpl
類:
public class MethodInterceptorImpl implements MethodInterceptor{
private Object target;//業務類真實物件
//類似於JDK動態代理中的繫結
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
//設定代理類的父類,即RealSubject3為proxy的父類
enhancer.setSuperclass(this.target.getClass());
//設定回撥:表示對代理物件proxy的方法的呼叫都會回撥intercept()函式進行處理
enhancer.setCallback(this);
// 建立動態代理類物件並返回
return enhancer.create();
}
// 實現回撥方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("呼叫前相關操作...");
Object result = proxy.invokeSuper(obj, args); //呼叫真實業務類的方法
System.out.println("呼叫後相關操作...");
return result;
}
}
CGLibClient
測試類:
public class CGLibClient {
public static void main(String[] args) {
RealSubject3 realSubject3 = new RealSubject3();
MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl();
RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
proxy.resolve();
}
}
結果如下,成功實現了代理
呼叫前相關操作…
真實業務類RealSubject3主要實現CRUD操作…
呼叫後相關操作…
注意:在編寫過程中可能會遇到以下的錯誤:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:72)
at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:72)
...
這要麼是因為jar包衝突,要麼就是沒引對jar包,我們通常使用帶ASM
的cglib-nodep-3.2.6.jar
包,如果同時存在cglib-3.2.6.jar
會衝突。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.6</version>
</dependency>
現在我們再增加一個方法:add()
方法:
public class RealSubject3 {
public void resolve() {
System.out.println("真實業務類RealSubject3主要實現CRUD操作...");
}
public void add() {
System.out.println("add操作...");
}
}
然後我們可以進行在回撥處理函式intercept()
中進行對add()方法的許可權控制:
public class MethodInterceptorImpl implements MethodInterceptor{
private Object target;//業務類真實物件
private String name;
//傳入username
public MethodInterceptorImpl(String name) {
this.name = name;
}
//類似於JDK動態代理中的繫結
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass()); //設定代理類的父類,即RealSubject3為proxy的父類
//設定回撥:表示對代理物件proxy的方法的呼叫都會回撥intercept()函式進行處理
enhancer.setCallback(this);
// 建立動態代理類物件並返回
return enhancer.create();
}
// 實現回撥方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//System.out.println("呼叫前相關操作...");
if (!"小李".equals(name)) { //對add()方法進行許可權控制
System.out.println(name+"您不是管理員,只有管理員才有許可權");
return null;
}
System.out.println("管理員"+name+",歡迎您...");
Object result = proxy.invokeSuper(obj, args); //呼叫真實業務類的方法
//System.out.println("呼叫後相關操作...");
return result;
}
}
客戶端CGLibClient
:
public class CGLibClient {
public static void main(String[] args) {
RealSubject3 realSubject3 = new RealSubject3();
String username = "小張";
MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl(username);
RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
proxy.add();
String username2 = "小李";
MethodInterceptorImpl methodInterceptor2 = new MethodInterceptorImpl(username2);
RealSubject3 proxy2 = (RealSubject3)methodInterceptor2.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
proxy2.add();
}
}
結果:
小張您不是管理員,只有管理員才有許可權
管理員小李,歡迎您…
add操作…
總結與補充
1、在代理實現了介面的類時候,預設的情況下Spring
等框架都是使用JDK動態代理,當然也可以強制使用CGLib
動態代理實現介面的被代理類,但是要代理沒有實現介面的被代理類時,Spring
都會使用CGLib
實現動態代理。
2、其實我們是可以檢視動態生成代理類.class
檔案,之後反編譯檢視結果,首先我們CGLibClient
程式中中加入如下程式碼,表示CGLib
動態生成的.class
輸出到指定的路徑下。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\xxx\\xxx");
之後會生成.class檔案,利用反編譯工具進行檢視:
(1)生成的代理類extends RealSubject3
:
(2)還有重寫了父類RealSubject3
所有的方法(不包括final
)
(3)還有必備的toString
、equals
等方法