大家都知道,動態代理能夠實現AOP,但是它有一個缺點,就是所有被代理的對象必須實現一個接口,否則就會報異常。那么如果被代理對象沒有實現接口那該如何實現AOP呢?當然是能的,使用CGlib就可以實現。
1、什么是CGlib
CGlib是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口。 然這些實際的功能是asm所提供的,asm又是什么?Java字節碼操控框架,具體是什么大家可以上網查一查,畢竟我們這里所要討論的是cglib, cglib就是封裝了asm,簡化了asm的操作,實現了在運行期動態生成新的class。 可能大家還感覺不到它的強大,現在就告訴你。 實際上CGlib為spring aop提供了底層的一種實現;為hibernate使用cglib動態生成VO/PO (接口層對象)。
它的原理就是用Enhancer生成一個原有類的子類,並且設置好callback , 則原有類的每個方法調用都會轉成調用實現了MethodInterceptor接口的proxy的intercept() 函數:
public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy)
在intercept()函數里,你可以在執行Object result=proxy.invokeSuper(o,args);來執行原有函數,在執行前后加入自己的東西,改變它的參數,也可以瞞天過海,完全干別的。說白了,就是AOP中的around advice。
2、如何使用CGlib
舉個例子:比如DAO層有對表的增、刪、改、查操作,如果要對原有的DAO層的增、刪、改、查增加權限控制的話,修改代碼是非常痛苦的。所以可以用AOP來實現。但是DAO層沒有使用接口,動態代理不可用。這時候CGlib是個很好的選擇。
TableDao.java:
1 package com.cglib; 2 3 public class TableDao { 4 public void create(){ 5 System.out.println("create() is running..."); 6 } 7 public void delete(){ 8 System.out.println("delete() is running..."); 9 } 10 public void update(){ 11 System.out.println("update() is running..."); 12 } 13 public void query(){ 14 System.out.println("query() is running..."); 15 } 16 }
實現了MethodInterceptor接口的AuthProxy.java:用來對方法進行攔截,增加方法訪問的權限控制,這里只允許張三訪問。
1 package com.cglib; 2 3 import java.lang.reflect.Method; 4 5 import net.sf.cglib.proxy.MethodInterceptor; 6 import net.sf.cglib.proxy.MethodProxy; 7 //方法攔截器 8 public class AuthProxy implements MethodInterceptor { 9 private String userName; 10 AuthProxy(String userName){ 11 this.userName = userName; 12 } 13 //用來增強原有方法 14 public Object intercept(Object arg0, Method arg1, Object[] arg2, 15 MethodProxy arg3) throws Throwable { 16 //權限判斷 17 if(!"張三".equals(userName)){ 18 System.out.println("你沒有權限!"); 19 return null; 20 } 21 return arg3.invokeSuper(arg0, arg2); 22 } 23 }
TableDAOFactory.java:用來創建TableDao的子類的工廠類
1 package com.cglib; 2 3 import net.sf.cglib.proxy.Callback; 4 import net.sf.cglib.proxy.Enhancer; 5 import net.sf.cglib.proxy.NoOp; 6 7 public class TableDAOFactory { 8 private static TableDao tDao = new TableDao(); 9 public static TableDao getInstance(){ 10 return tDao; 11 } 12 public static TableDao getAuthInstance(AuthProxy authProxy){ 13 Enhancer en = new Enhancer(); //Enhancer用來生成一個原有類的子類 14 //進行代理 15 en.setSuperclass(TableDao.class); 16 //設置織入邏輯 17 en.setCallback(authProxy); 18 //生成代理實例 19 return (TableDao)en.create(); 20 } 21 }
測試類Client.java:
1 package com.cglib; 2 3 public class Client { 4 5 public static void main(String[] args) { 6 // haveAuth(); 7 haveNoAuth(); 8 } 9 public static void doMethod(TableDao dao){ 10 dao.create(); 11 dao.query(); 12 dao.update(); 13 dao.delete(); 14 } 15 //模擬有權限 16 public static void haveAuth(){ 17 TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("張三")); 18 doMethod(tDao); 19 } 20 //模擬無權限 21 public static void haveNoAuth(){ 22 TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四")); 23 doMethod(tDao); 24 } 25 }
這樣就能夠對DAO層的方法進行權限控制了。但是如果又改需求了,要把DAO層的query方法讓所有用戶都可以訪問,而其他方法照樣有權限控制,該如何實現呢?這可難不倒我們了,因為我們使用了CGlib。當然最簡單的方式是去修改我們的方法攔截器,不過這樣會使邏輯變得復雜,且不利於維護。還好CGlib給我們提供了方法過濾器(CallbackFilter),CallbackFilte可以明確表明,被代理的類中不同的方法, 被哪個攔截器所攔截。下面我們就來做個過濾器用來過濾query方法。
AuthProxyFilter.java:
1 package com.cglib; 2 3 import java.lang.reflect.Method; 4 5 import net.sf.cglib.proxy.CallbackFilter; 6 import net.sf.cglib.proxy.NoOp; 7 8 public class AuthProxyFilter implements CallbackFilter { 9 10 public int accept(Method arg0) { 11 /* 12 * 如果調用的不是query方法,則要調用authProxy攔截器去判斷權限 13 */ 14 if(!"query".equalsIgnoreCase(arg0.getName())){ 15 return 0; //調用第一個方法攔截器,即authProxy 16 } 17 /* 18 * 調用第二個方法攔截器,即NoOp.INSTANCE,NoOp.INSTANCE是指不做任何事情的攔截器 19 * 在這里就是任何人都有權限訪問query方法,所以調用默認攔截器不做任何處理 20 */ 21 return 1; 22 } 23 24 }
至於為什么返回0或者1,注釋講的很詳細。
在TableDAOFactory.java里添加如下方法:
1 public static TableDao getAuthInstanceByFilter(AuthProxy authProxy){ 2 Enhancer en = new Enhancer(); 3 en.setSuperclass(TableDao.class); 4 en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE}); //設置兩個方法攔截器 5 en.setCallbackFilter(new AuthProxyFilter()); 6 return (TableDao)en.create(); 7 }
這里得注意,en.setCallbacks()方法里的數組參數順序就是上面方法的返回值所代表的方法攔截器,如果return 0則使用authProxy攔截器,return 1則使用NoOp.INSTANCE攔截器,NoOp.INSTANCE是默認的方法攔截器,不做什么處理。
下面在測試類中添加如下方法:
1 //模擬權限過濾器 2 public static void haveAuthByFilter(){ 3 TableDao tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("張三")); 4 doMethod(tDao); 5 tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四")); 6 doMethod(tDao); 7 }
在main方法中調用該方法,程序運行結果如下:
create() is running...
query() is running...
update() is running...
delete() is running...
你沒有權限!
query() is running...
你沒有權限!
你沒有權限!
這樣的話,所有用戶都對query方法有訪問權限了,而其他方法只允許張三訪問。
參考資料:http://llying.iteye.com/blog/220452
