AOP的另一种实现----cglib
大家都知道,动态代理能够实现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