注: 本文只是原理性质, 并不实用, 读者可用成熟稳定的开源权限系统如SpringSecurity. 但可参考实现自己的一些小框架. 
在上课的时候, 一位同学拿着一篇JavaEye的很长的CGLIB讲解代码来问我这是怎么回事?我看了下, 觉得那样的例子实在太让人难以一下子看懂了, 于是就自己重造了个轮子. 

Spring 2.5最大的亮点就是基于注解实现配置, 其实这个谈不上什么亮点, EJB3/JPA早就实现了, 而且在原理上也只是利用反射里面的method.getAnnotation(MyAnnotaion.class)即可. 
第二个一直大张旗鼓吹捧的就是AOP, 其实AOP就是个JDK接口方法拦截器/子类增强, 不过Spring加了个配置文件封装, 而且和它的一贯作风一致, 都是集成第三方的框架, 这里基于类增强的是CGLIB. 

CGLIB是什么呢? 

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime. See samples and API documentation to learn more about features. 

This library is free software, freely reusable for personal or commercial purposes. 

cglib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java 接口. 而CGLIB本身又用了ASM框架, 来避免直接解析字节码. 

OK, 废话少说, 我们来通过一个例子看看基于注解和AOP的权限系统(本代码只需要CGLIB及其依赖类库ASM, 或者直接下载cglib-nodep-2.2.jar http://sourceforge.net/project/showfiles.php?group_id=56933&package_id=98218&release_id=601998). 
先看业务层: 
Java代码   收藏代码
  1. public class MyClass {  
  2.     private String role;// 运行时角色  
  3.   
  4.     public String getRole() {  
  5.         return role;  
  6.      }  
  7.   
  8.     public void setRole(String role) {  
  9.         this.role = role;  
  10.      }  
  11.   
  12.      @Role(name="admin")  
  13.     public String adminMethod(String name) {  
  14.          System.out.println("MyClass.adminMethod()" + name);  
  15.         return "adminMethod 结果";  
  16.      }  
  17.   
  18.      @Role(name="guest")  
  19.     public String guestMethod(String name) {  
  20.          System.out.println("MyClass.guestMethod()" + name);  
  21.         return "guestMethod 结果";  
  22.      }  
  23.   
  24. }  

基本上就是普通的方法加上了角色注解, 当然注解类也是我们自己写的: 
Java代码   收藏代码
  1. import java.lang.annotation.*;  
  2.   
  3. @Documented  
  4. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})//指定目标, 必须包含方法  
  5. @Retention(RetentionPolicy.RUNTIME)//设置保持性  
  6. @Inherited  
  7. public @interface Role {  
  8.      String name();  
  9. }  


所有的Magic都在后台通过反射和CGLIB的方法拦截器来实现, 先通过JDK本身的反射来检查注解, 然后读取注解的值, 随后进行判断并决定是否执行此方法, 如果中间加上Spring容器, 那就是实现了所谓的专门的权限控制Bean了. 代码: 
Java代码   收藏代码
  1. import java.lang.reflect.Method;  
  2.   
  3. import net.sf.cglib.proxy.Enhancer;  
  4. import net.sf.cglib.proxy.MethodProxy;  
  5. import net.sf.cglib.proxy.MethodInterceptor;  
  6.   
  7. public class Main {  
  8.   
  9.     public static void main(String[] args) {  
  10.          Enhancer enhancer = new Enhancer();  
  11.   
  12.          enhancer.setSuperclass(MyClass.class);  
  13.          enhancer.setCallback( new MethodInterceptorImpl() );  
  14.   
  15.   
  16.          MyClass my = (MyClass)enhancer.create();  
  17.          my.setRole("admin");  
  18.   
  19.          System.out.println(my.adminMethod("名字"));// 这一行将会执行通过  
  20.          System.out.println(my.guestMethod("名字"));// 这一行将会执行失败  
  21.         /* --> new 父类 --> obj --> 
  22.           * MyClass_proxy 
  23.           * MyClass obj = new MyClass();// 本来父类 
  24.           * MethodInterceptor interceptor; 
  25.          public String method(String name) { 
  26.              Object value = interceptor.intercept(obj, ojb.getClass().getMethod("method"), new Object[] {name}, 
  27.              proxy); 
  28.  
  29.              if(value != null) { 
  30.                  return value; 
  31.              } 
  32.  
  33.              return obj.method(name); 
  34.          } 
  35.  
  36.         */  
  37.      }  
  38.   
  39.     private static class MethodInterceptorImpl implements MethodInterceptor {  
  40.         // obj => 父类的实例, method是被调用的方法, args = {String name}, proxy 专门执行方法的工具类  
  41.         public Object intercept(Object obj,  
  42.                                  Method method,  
  43.                                  Object[] args,  
  44.                                  MethodProxy proxy) throws Throwable {  
  45.   
  46.              System.out.println("即将调用方法" + method);  
  47.   
  48.             // 检查是否有Role注解 @Role(name="角色名")  
  49.              Role role = method.getAnnotation(Role.class);  
  50.   
  51.   
  52.             if(role != null) {  
  53.                 // 有注解  
  54.                  System.out.println("发现方法上" +method.getName() + " 有一个注解 Role, 它的值是" + role.name());  
  55.                 // 把被调用对象类型还原  
  56.                  MyClass my = (MyClass)obj;  
  57.   
  58.                if(!role.name().equals(my.getRole())) {   
  59. System.err.println("对不起, 您的权限不足, 不能调用此方法!");   
  60. throw new Exception("对不起, 您的权限不足, 不能调用此方法!");   
  61. }   
  62. else {  
  63.                     // ojb ==> MyClass my  
  64.                     // Before  
  65.                      Object value = proxy.invokeSuper(obj, args);// 正在调用方法  
  66.                     // invoke 等价于 String return = obj.method(args);  
  67.   
  68.                      System.out.println("已经调用了方法, 原本应该返回的值:" + value);  
  69.                     // After  
  70.   
  71.                     return value;  
  72.                  }  
  73.   
  74.              } else {  
  75.                  System.out.println("发现方法" +method.getName() + " 没有角色限制");  
  76.                  Object value = proxy.invokeSuper(obj, args);  
  77.                 return value;  
  78.              }  
  79.   
  80.   
  81.          }  
  82.      }  
  83.   
  84.     class A {  
  85.          String a() {  
  86.             return "";  
  87.          }  
  88.      }  
  89.   
  90.     class A_proxy extends A {  
  91.          A a = new A();  
  92.   
  93.          String a() {  
  94.              String oldValue = a.a();  
  95.   
  96.             return oldValue;  
  97.          }  
  98.      }  
  99.   
  100.   
  101. }  
  102. 另外注解其实是支持数组方式的变量定义的, 那样的话就可以写成:   
  103. @Role(name = {"guest""admin"})   

代码末尾的类A和A_proxy试图说明代理这种模式的实现方式. OK, 运行代码, 应该可以得到我们期望的结果了. admin方法执行成功, 而另一个则失败了. 
运行输出: 
即将调用方法public void MyClass.setRole(java.lang.String) 
发现方法setRole 没有角色限制 
即将调用方法public java.lang.String MyClass.adminMethod(java.lang.String) 
发现方法上adminMethod 有一个注解 Role, 它的值是admin 
即将调用方法public java.lang.String MyClass.getRole() 
发现方法getRole 没有角色限制 
MyClass.adminMethod()名字 
已经调用了方法, 原本应该返回的值:adminMethod 结果 
adminMethod 结果 
即将调用方法public java.lang.String MyClass.guestMethod(java.lang.String) 
发现方法上guestMethod 有一个注解 Role, 它的值是guest 
即将调用方法public java.lang.String MyClass.getRole() 
发现方法getRole 没有角色限制 
对不起, 您的权限不足, 不能调用此方法! 
Exception in thread "main" java.lang.Exception: 对不起, 您的权限不足, 不能调用此方法! 
at Main$MethodInterceptorImpl.intercept(Main.java:60) 
at MyClass$$EnhancerByCGLIB$$e6aea994.guestMethod(<generated>) 
at Main.main(Main.java:20) 


小结: 基于类的权限系统可通过CGLIB/AOP实现; 基于URL的权限系统可通过过滤器实现. 其实, Servlet的Filter也是AOP的一种基于Web的实现. 

后记: 最近网上有一种很普遍的论调, 当大家说XX不好的时候, 有人说XX不行, 有本事你也去跑啊, 去导啊, 不行你就闭嘴. 我想这是一种转移话题的谬论, 举个例子: 如果病人被庸医给看死了, 他的家人是不是还不能说这医生治的不好, 不应该追究责任, 因为他们不会看病啊? 吃菜的不需要会做菜, 但是吃菜的有权利评价菜是否好吃, 所以真正应该闭嘴的是说这种话的人. 所以我们评论框架好坏的时候, 有人就说了: 你说XXX框架不好, 那你有本事也自己去做一个啊; 这是相当荒谬的跑题的论调.

出自:http://jlins.iteye.com/blog/608166

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐