CGlib:AOP的另一種實(shí)現(xiàn)
1、什么是CGlib
CGlib是一個(gè)強(qiáng)大的,高性能,高質(zhì)量的Code生成類庫。它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。然這些實(shí)際的功能是asm所提供的,asm又是什么?Java字節(jié)碼操控框架,具體是什么大家可以上網(wǎng)查一查,畢竟我們這里所要討論的是cglib,cglib就是封裝了asm,簡化了asm的操作,實(shí)現(xiàn)了在運(yùn)行期動(dòng)態(tài)生成新的class。可能大家還感覺不到它的強(qiáng)大,現(xiàn)在就告訴你。實(shí)際上CGlib為spring aop提供了底層的一種實(shí)現(xiàn);為hibernate使用cglib動(dòng)態(tài)生成VO/PO (接口層對象)。
它的原理就是用Enhancer生成一個(gè)原有類的子類,并且設(shè)置好callback , 則原有類的每個(gè)方法調(diào)用都會(huì)轉(zhuǎn)成調(diào)用實(shí)現(xiàn)了MethodInterceptor接口的proxy的intercept() 函數(shù):
public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy)
在intercept()函數(shù)里,你可以在執(zhí)行Object result=proxy.invokeSuper(o,args);來執(zhí)行原有函數(shù),在執(zhí)行前后加入自己的東西,改變它的參數(shù),也可以瞞天過海,完全干別的。說白了,就是AOP中的around advice。
2、如何使用CGlib
舉個(gè)例子:比如DAO層有對表的增、刪、改、查操作,如果要對原有的DAO層的增、刪、改、查增加權(quán)限控制的話,修改代碼是非常痛苦的。所以可以用AOP來實(shí)現(xiàn)。但是DAO層沒有使用接口,動(dòng)態(tài)代理不可用。這時(shí)候CGlib是個(gè)很好的選擇。
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...");
- }
- }
實(shí)現(xiàn)了MethodInterceptor接口的AuthProxy.java:用來對方法進(jìn)行攔截,增加方法訪問的權(quán)限控制,這里只允許張三訪問。
- 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;
- }
- //用來增強(qiáng)原有方法
- public Object intercept(Object arg0, Method arg1, Object[] arg2,
- MethodProxy arg3) throws Throwable {
- //權(quán)限判斷
- if(!"張三".equals(userName)){
- System.out.println("你沒有權(quán)限!");
- return null;
- }
- return arg3.invokeSuper(arg0, arg2);
- }
- }
TableDAOFactory.java:用來創(chuàng)建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用來生成一個(gè)原有類的子類
- //進(jìn)行代理
- en.setSuperclass(TableDao.class);
- //設(shè)置織入邏輯
- en.setCallback(authProxy);
- //生成代理實(shí)例
- 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();
- }
- //模擬有權(quán)限
- public static void haveAuth(){
- TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("張三"));
- doMethod(tDao);
- }
- //模擬無權(quán)限
- public static void haveNoAuth(){
- TableDao tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四"));
- doMethod(tDao);
- }
- }
這樣就能夠?qū)AO層的方法進(jìn)行權(quán)限控制了。但是如果又改需求了,要把DAO層的query方法讓所有用戶都可以訪問,而其他方法照樣有權(quán)限控制,該如何實(shí)現(xiàn)呢?這可難不倒我們了,因?yàn)槲覀兪褂昧薈Glib。當(dāng)然最簡單的方式是去修改我們的方法攔截器,不過這樣會(huì)使邏輯變得復(fù)雜,且不利于維護(hù)。還好CGlib給我們提供了方法過濾器(CallbackFilter),CallbackFilte可以明確表明,被代理的類中不同的方法,被哪個(gè)攔截器所攔截。下面我們就來做個(gè)過濾器用來過濾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) {
- /*
- * 如果調(diào)用的不是query方法,則要調(diào)用authProxy攔截器去判斷權(quán)限
- */
- if(!"query".equalsIgnoreCase(arg0.getName())){
- return 0; //調(diào)用第一個(gè)方法攔截器,即authProxy
- }
- /*
- * 調(diào)用第二個(gè)方法攔截器,即NoOp.INSTANCE,NoOp.INSTANCE是指不做任何事情的攔截器
- * 在這里就是任何人都有權(quán)限訪問query方法,所以調(diào)用默認(rèn)攔截器不做任何處理
- */
- return 1;
- }
- }
至于為什么返回0或者1,注釋講的很詳細(xì)。
在TableDAOFactory.java里添加如下方法:
- public static TableDao getAuthInstanceByFilter(AuthProxy authProxy){
- Enhancer en = new Enhancer();
- en.setSuperclass(TableDao.class);
- en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE}); //設(shè)置兩個(gè)方法攔截器
- en.setCallbackFilter(new AuthProxyFilter());
- return (TableDao)en.create();
- }
-
這里得注意,en.setCallbacks()方法里的數(shù)組參數(shù)順序就是上面方法的返回值所代表的方法攔截器,如果return 0則使用authProxy攔截器,return 1則使用NoOp.INSTANCE攔截器,NoOp.INSTANCE是默認(rèn)的方法攔截器,不做什么處理。
下面在測試類中添加如下方法:
- //模擬權(quán)限過濾器
- public static void haveAuthByFilter(){
- TableDao tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("張三"));
- doMethod(tDao);
- tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四"));
- doMethod(tDao);
- }
在main方法中調(diào)用該方法,程序運(yùn)行結(jié)果如下:
create() is running...
query() is running...
update() is running...
delete() is running...
你沒有權(quán)限!
query() is running...
你沒有權(quán)限!
你沒有權(quán)限!
這樣的話,所有用戶都對query方法有訪問權(quán)限了,而其他方法只允許張三訪問。