CGlib:AOP的另一種實現
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:
- package com.cglib;
- public class TableDao {
- public void create(){
- System.out.println("create() is running...");
- }
- public void delete(){
- System.out.println("delete() is running...");
- }
- public void update(){
- System.out.println("update() is running...");
- }
- public void query(){
- System.out.println("query() is running...");
- }
- }
實現了MethodInterceptor接口的AuthProxy.java:用來對方法進行攔截,增加方法訪問的權限控制,這里只允許張三訪問。
- package com.cglib;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- //方法攔截器
- public class AuthProxy implements MethodInterceptor {
- private String userName;
- AuthProxy(String userName){
- this.userName = userName;
- }
- //用來增強原有方法
- public Object intercept(Object arg0, Method arg1, Object[] arg2,
- MethodProxy arg3) throws Throwable {
- //權限判斷
- if(!"張三".equals(userName)){
- System.out.println("你沒有權限!");
- return null;
- }
- return arg3.invokeSuper(arg0, arg2);
- }
- }
TableDAOFactory.java:用來創建TableDao的子類的工廠類
- package com.cglib;
- import net.sf.cglib.proxy.Callback;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.NoOp;
- public class TableDAOFactory {
- private static TableDao tDao = new TableDao();
- public static TableDao getInstance(){
- return tDao;
- }
- public static TableDao getAuthInstance(AuthProxy authProxy){
- Enhancer en = new Enhancer(); //Enhancer用來生成一個原有類的子類
- //進行代理
- en.setSuperclass(TableDao.class);
- //設置織入邏輯
- en.setCallback(authProxy);
- //生成代理實例
- return (TableDao)en.create();
- }
- }
測試類Client.java:
- package com.cglib;
- public class Client {
- public static void main(String[] args) {
- // haveAuth();
- haveNoAuth();
- }
- public static void doMethod(TableDao dao){
- dao.create();
- dao.query();
- dao.update();
- dao.delete();
- }
- //模擬有權限
- public static void haveAuth(){
- TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("張三"));
- doMethod(tDao);
- }
- //模擬無權限
- public static void haveNoAuth(){
- TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四"));
- doMethod(tDao);
- }
- }
這樣就能夠對DAO層的方法進行權限控制了。但是如果又改需求了,要把DAO層的query方法讓所有用戶都可以訪問,而其他方法照樣有權限控制,該如何實現呢?這可難不倒我們了,因為我們使用了CGlib。當然最簡單的方式是去修改我們的方法攔截器,不過這樣會使邏輯變得復雜,且不利于維護。還好CGlib給我們提供了方法過濾器(CallbackFilter),CallbackFilte可以明確表明,被代理的類中不同的方法,被哪個攔截器所攔截。下面我們就來做個過濾器用來過濾query方法。
AuthProxyFilter.java:
- package com.cglib;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.CallbackFilter;
- import net.sf.cglib.proxy.NoOp;
- public class AuthProxyFilter implements CallbackFilter {
- public int accept(Method arg0) {
- /*
- * 如果調用的不是query方法,則要調用authProxy攔截器去判斷權限
- */
- if(!"query".equalsIgnoreCase(arg0.getName())){
- return 0; //調用第一個方法攔截器,即authProxy
- }
- /*
- * 調用第二個方法攔截器,即NoOp.INSTANCE,NoOp.INSTANCE是指不做任何事情的攔截器
- * 在這里就是任何人都有權限訪問query方法,所以調用默認攔截器不做任何處理
- */
- return 1;
- }
- }
至于為什么返回0或者1,注釋講的很詳細。
在TableDAOFactory.java里添加如下方法:
- public static TableDao getAuthInstanceByFilter(AuthProxy authProxy){
- Enhancer en = new Enhancer();
- en.setSuperclass(TableDao.class);
- en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE}); //設置兩個方法攔截器
- en.setCallbackFilter(new AuthProxyFilter());
- return (TableDao)en.create();
- }
-
這里得注意,en.setCallbacks()方法里的數組參數順序就是上面方法的返回值所代表的方法攔截器,如果return 0則使用authProxy攔截器,return 1則使用NoOp.INSTANCE攔截器,NoOp.INSTANCE是默認的方法攔截器,不做什么處理。
下面在測試類中添加如下方法:
- //模擬權限過濾器
- public static void haveAuthByFilter(){
- TableDao tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("張三"));
- doMethod(tDao);
- tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四"));
- doMethod(tDao);
- }
在main方法中調用該方法,程序運行結果如下:
create() is running...
query() is running...
update() is running...
delete() is running...
你沒有權限!
query() is running...
你沒有權限!
你沒有權限!
這樣的話,所有用戶都對query方法有訪問權限了,而其他方法只允許張三訪問。