使用 cglib_java动态代理(JDK和CGLIB原理解析与使用)
CGLIB的動態代理
原理
代理為控制要訪問的目標對象提供了一種途徑。當訪問對象時,它引入了一個間接的層。JDK自從1.3版本開始,就引入了動態代理,并且經常被用來動態地創建代理。JDK的動態代理用起來非常簡單,當它有一個限制,就是使用動態代理的對象必須實現一個或多個接口。如果想代理沒有實現接口的繼承的類,該怎么辦?現在我們可以使用CGLIB包。
二、什么是cglib
CGLIB是一個強大的高性能的代碼生成包。
1>它廣泛的被許多AOP的框架使用,例如:spring AOP和dynaop,為他們提供方法的interception(攔截);
2>hibernate使用CGLIB來代理單端single-ended(多對一和一對一)關聯(對集合的延遲抓取,是采用其他機制實現的);
3>EasyMock和jMock是通過使用模仿(moke)對象來測試java代碼的包。
它們都通過使用CGLIB來為那些沒有接口的類創建模仿(moke)對象。
三、底層
CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM(Java字節碼操控框架),來轉換字節碼并生成新的類。除了CGLIB包,腳本語言例如 Groovy和BeanShell,也是使用ASM來生成java的字節碼。當不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。所以cglib包要依賴于asm包,需要一起導入。下圖為cglib與一些框架和語言的關系(CGLIB Library and ASM Bytecode Framework)
Spring AOP和Hibernate同時使用JDK的動態代理和CGLIB包。Spring AOP,如果不強制使用CGLIB包,默認情況是使用JDK的動態代理來代理接口。
四、實例場景
1. 我們創建一個對Table操作的DAO類,提供了CRUD方法。
BookServiceBean.java
package com.tech.cglibx; public class BookServiceBean { public void create(){ System.out.println("create() is running !"); } public void query(){ System.out.println("query() is running !"); } public void update(){ System.out.println("update() is running !"); } public void delete(){ System.out.println("delete() is running !"); } }OK,它就是一個javaBean,提供了CRUD方法的javaBean。
下面我們創建一個DAO工廠,用來生成DAO實例。
package com.tech.cglibx; public class BookServiceFactory { private static BookServiceBean service = new BookServiceBean(); private BookServiceFactory() { } public static BookServiceBean getInstance() { return service; } }接下來我們創建客戶端,用來調用CRUD方法。
public class Client { public static void main(String[] args) { BookServiceBean service = BookServiceFactory.getInstance(); doMethod(service); } public static void doMethod(BookServiceBean service){ service.create(); service.update(); service.query(); service.delete(); } }OK,完成了,CRUD方法完全被調用了。
當然這里并沒有CGlib的任何內容。問題不會這么簡單的就結束,新的需求來臨了。
2. one day,Boss告訴我們這些方法不能開放給用戶,只有“boss”才有權使用。怎么辦,難道我們要在每個方法上面進行判斷嗎?好像這么做也太那啥了吧?對了,Proxy可能是最好的解決辦法。jdk的代理就可以解決了。 好了我們來動手改造吧。等等jdk的代理需要實現接口,這樣, 我們的dao類需要改變了。既然不想改動dao又要使用代理,我們這就請出CGlib。
我們只需新增一個權限驗證的方法攔截器。
package com.tech.cglibx; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.apache.log4j.Logger; public class MyCglibProxy implements MethodInterceptor{ private Logger log=Logger.getLogger(MyCglibProxy.class); public Enhancer enhancer = new Enhancer(); private String name; public MyCglibProxy(String name) { this.name = name ; } /** * 根據class對象創建該對象的代理對象 * 1、設置父類;2、設置回調 * 本質:動態創建了一個class對象的子類 * * @param cls * @return */ public Object getDaoBean(Class cls) { enhancer.setSuperclass(cls); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info("調用的方法是:" + method.getName()); //用戶進行判斷 if(!"張三".equals(name)){ System.out.println("你沒有權限!"); return null; } Object result = methodProxy.invokeSuper(object, args); return result; } }當然不能忘了對我們的dao工廠進行修改,我們提供一個使用代理的實例生成方法。上面的類中已經提供了一個通用的獲取代理實例的方法,沒有特殊需求(如下3)的方式可以使用上面的方式獲取代理對象。
public static BookServiceBean getProxyInstance(MyCglibProxy myProxy){ Enhancer en = new Enhancer(); //進行代理 en.setSuperclass(BookServiceBean.class); en.setCallback(myProxy); //生成代理實例 return (BookServiceBean)en.create(); }我們這就可以看看客戶端的實現了。添加了兩個方法用來驗證不同用戶的權限
BookServiceBean service = BookServiceFactory.getProxyInstance(new MyCglibProxy("boss")); service.create(); BookServiceBean service2 = BookServiceFactory.getProxyInstance(new MyCglibProxy("john")); service2.create();OK,"boss"的正常執行,"john"的沒有執行。
看到了嗎?簡單的aop就這樣實現了
難道就這樣結束了么?
3.grd Boss又來訓話了,不行不行,現在除了"boss"其他人都用不了了,現在不可以這樣。必須使用開放查詢功能。
哈哈,現在可難不倒我們了,因為我們使用了CGlib。當然最簡單的方式是去修改我們的方法攔截器,不過這樣會使邏輯變得復雜,且不利于維護。
還好CGlib給我們提供了方法過濾器(CallbackFilter),CallbackFilte可以明確表明,被代理的類中不同的方法,被哪個攔截器所攔截。
下面我們就來做個過濾器用來過濾query方法。
package com.tech.cglibx; import java.lang.reflect.Method; import net.sf.cglib.proxy.CallbackFilter; public class MyProxyFilter implements CallbackFilter { @Override public int accept(Method arg0) { if(!"query".equalsIgnoreCase(arg0.getName())) return 0; return 1; } }我們在工場中新增一個使用了過濾器的實例生成方法。
public static BookServiceBean getAuthInstanceByFilter(MyCglibProxy myProxy){ Enhancer en = new Enhancer(); en.setSuperclass(BookServiceBean.class); en.setCallbacks(new Callback[]{myProxy,NoOp.INSTANCE}); en.setCallbackFilter(new MyProxyFilter()); return (BookServiceBean)en.create(); }setCallbacks中定義了所使用的攔截器,其中NoOp.INSTANCE是CGlib所提供的實際是一個沒有任何操作的攔截器,
他們是有序的,一定要和CallbackFilter里面的順序一致。上面return返回(0/1)的就是返回的順序。也就是說如果調用query方法就使用NoOp.INSTANCE進行攔截。
現在看一下客戶端代碼。
BookServiceBean service = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon")); service.create(); BookServiceBean service2 = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon")); service2.query();ok,現在"李四"也可以使用query方法了,其他方法仍然沒有權限。
當然這個代理的實現沒有任何侵入性,無需強制讓dao去實現接口。
二、CGLIB使用
JDK實現動態代理需要實現類通過接口定義業務方法,對于沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
簡單的實現舉例:
這是一個需要被代理的類,也就是父類,通過字節碼技術創建這個類的子類,實現動態代理。
public class SayHello { public void say(){ System.out.println("hello everyone"); } }該類實現了創建子類的方法與代理的方法。getProxy(SuperClass.class)方法通過入參即父類的字節碼,通過擴展父類的class來創建代理對象。intercept()方法攔截所有目標類方法的調用,obj表示目標類的實例,method為目標類方法的反射對象,args為方法的動態入參,proxy為代理類實例。proxy.invokeSuper(obj, args)通過代理類調用父類中的方法。[java]
public class CglibProxy implements MethodInterceptor{ private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //設置需要創建子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通過字節碼技術動態創建子類實例 return enhancer.create(); } //實現MethodInterceptor接口方法 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; } }具體實現類:
public class DoCGLib { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); //通過生成子類的方式創建代理類 SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class); proxyImp.say(); } }輸出結果:
CGLib創建的動態代理對象性能比JDK創建的動態代理對象的性能高不少,但是CGLib在創建代理對象時所花費的時間卻比JDK多得多,所以對于單例的對象,因為無需頻繁創建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。同時,由于CGLib由于是采用動態創建子類的方法,對于final方法,無法進行代理。
三、JAVA的代理模式
代理模式
代理模式是常用的java設計模式,他的特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。代理類與委托類之間通常會存在關聯關系,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身并不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。
按照代理的創建時期,代理類可以分為兩種。
靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理:在程序運行時,運用反射機制動態創建而成。
首先看一下靜態代理:
1、Count.java package net.battier.dao; /** * 定義一個賬戶接口 * * @author Administrator * */ public interface Count { // 查看賬戶方法 public void queryCount(); // 修改賬戶方法 public void updateCount(); } 2、CountImpl.java package net.battier.dao.impl; import net.battier.dao.Count; /** * 委托類(包含業務邏輯) * * @author Administrator * */ public class CountImpl implements Count { @Override public void queryCount() { System.out.println("查看賬戶方法..."); } @Override public void updateCount() { System.out.println("修改賬戶方法..."); } } 、CountProxy.java package net.battier.dao.impl; import net.battier.dao.Count; /** * 這是一個代理類(增強CountImpl實現類) * * @author Administrator * */ public class CountProxy implements Count { private CountImpl countImpl; /** * 覆蓋默認構造器 * * @param countImpl */ public CountProxy(CountImpl countImpl) { this.countImpl = countImpl; } @Override public void queryCount() { System.out.println("事務處理之前"); // 調用委托類的方法; countImpl.queryCount(); System.out.println("事務處理之后"); } @Override public void updateCount() { System.out.println("事務處理之前"); // 調用委托類的方法; countImpl.updateCount(); System.out.println("事務處理之后"); } } 3、TestCount.java package net.battier.test; import net.battier.dao.impl.CountImpl; import net.battier.dao.impl.CountProxy; /** *測試Count類 * * @author Administrator * */ public class TestCount { public static void main(String[] args) { CountImpl countImpl = new CountImpl(); CountProxy countProxy = new CountProxy(countImpl); countProxy.updateCount(); countProxy.queryCount(); } }觀察代碼可以發現每一個代理類只能為一個接口服務,這樣一來程序開發中必然會產生過多的代理,而且,所有的代理操作除了調用的方法不一樣之外,其他的操作都一樣,則此時肯定是重復代碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那么此時就必須使用動態代理完成。
再來看一下動態代理:
JDK動態代理中包含一個類和一個接口:
InvocationHandler接口:
public interface InvocationHandler { public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; }參數說明:
Object proxy:指被代理的對象。
Method method:要調用的方法
Object[] args:方法調用時所需要的參數
可以將InvocationHandler接口的子類想象成一個代理的最終操作類,替換掉ProxySubject。
Proxy類:
Proxy類是專門完成代理的操作類,可以通過此類為一個或多個接口動態地生成實現類,此類提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
參數說明:
ClassLoader loader:類加載器
Class>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子類實例
Ps:類加載器
在Proxy類中的newProxyInstance()方法中需要一個ClassLoader類的實例,ClassLoader實際上對應的是類加載器,在Java中主要有一下三種類加載器;
Booststrap ClassLoader:此加載器采用C++編寫,一般開發中是看不到的;
Extendsion ClassLoader:用來進行擴展類的加載,一般對應的是jrelibext目錄中的類;
AppClassLoader:(默認)加載classpath指定的類,是最常使用的是一種加載器。
動態代理
與靜態代理類對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因為Java 反射機制可以生成任意類型的動態代理類。java.lang.reflect 包中的Proxy類和InvocationHandler 接口提供了生成動態代理類的能力。
動態代理示例:
1、BookFacade.java package net.battier.dao; public interface BookFacade { public void addBook(); } 2、BookFacadeImpl.java package net.battier.dao.impl; import net.battier.dao.BookFacade; public class BookFacadeImpl implements BookFacade { @Override public void addBook() { System.out.println("增加圖書方法。。。"); } } 、BookFacadeProxy.java package net.battier.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JDK動態代理代理類 * * @author student * */ public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 綁定委托對象并返回一個代理類 * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理對象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //要綁定接口(這是一個缺陷,cglib彌補了這一缺陷) } @Override /** * 調用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; System.out.println("事物開始"); //執行方法 result=method.invoke(target, args); System.out.println("事物結束"); return result; } } 3、TestProxy.java package net.battier.test; import net.battier.dao.BookFacade; import net.battier.dao.impl.BookFacadeImpl; import net.battier.proxy.BookFacadeProxy; public class TestProxy { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook(); } }但是,JDK的動態代理依靠接口實現,如果有些類并沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理了。
Cglib動態代理
JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,并覆蓋其中方法實現增強,但因為采用的是繼承,所以不能對final修飾的類進行代理。
示例
1、BookFacadeCglib.java package net.battier.dao; public interface BookFacade { public void addBook(); } 2、BookCadeImpl1.java package net.battier.dao.impl; /** * 這個是沒有實現接口的實現類 * * @author student * */ public class BookFacadeImpl1 { public void addBook() { System.out.println("增加圖書的普通方法..."); } } 3、BookFacadeProxy.java package net.battier.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 使用cglib動態代理 * * @author student * */ public class BookFacadeCglib implements MethodInterceptor { private Object target; /** * 創建代理對象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回調方法 enhancer.setCallback(this); // 創建代理對象 return enhancer.create(); } @Override // 回調方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("事物開始"); proxy.invokeSuper(obj, args); System.out.println("事物結束"); return null; } } 4、TestCglib.java package net.battier.test; import net.battier.dao.impl.BookFacadeImpl1; import net.battier.proxy.BookFacadeCglib; public class TestCglib { public static void main(String[] args) { BookFacadeCglib cglib=new BookFacadeCglib(); BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1()); bookCglib.addBook(); } }總結
以上是生活随笔為你收集整理的使用 cglib_java动态代理(JDK和CGLIB原理解析与使用)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进项税加计扣除10%会计分录
- 下一篇: 哪里能借10万