本文共 4751 字,大约阅读时间需要 15 分钟。
最近在写代码时,不可避免的会写成出进行大量的鉴权逻辑。 如下面代码所示,每一个方法都要添加一个鉴权逻辑(样例只有4个方法,实际工作中会很多)
class Action{ public void get() { checkRights(); //鉴权代码 //业务代码 } public void put() { checkRights(); //鉴权代码 //业务代码 } public void delete() { checkRights(); //鉴权代码 //业务代码 } public void post() { checkRights(); //鉴权代码 //业务代码 }}
作为一个善于偷懒的码农,看到如上代码应该会敏锐得意识到‘Don't repeat yourself’的编程箴言了,重复代码带来的问题,我就不再累述了,相信大家都身受其害。
那么该如何消除到这些大量烦人的重复逻辑呢。 有的同学可能已经想到了,可以在web框架的流水线中增加一个鉴权处理器,这的确是一个可行方案,我们这里就不详细解释了。 这里给出另外一个利用AOP进行重复代码消除的技术,那么什么是AOP呢?
这里说下个人比较粗浅的理解:AOP(Aspect-Oriented Programming)面向切面编程,是一种根据位置描述规则(Pointcut切点)把代码(Advice)添加(织入weav)到指定位置的技术。 通过 (位置描述规则 + 代码)把散落在代码中的重复逻辑提取出来,对重复代码完成的这形式的抽象就叫AOP。
AOP在天然支持元编程的语言里面(如lisp),很容易直接写代码实现。 但是在大部分不支持元编程的语言,就由各种工具或框架各显神通了,如C++里面有AspectC++等。 在java世界中有2个著名的实现一个是经典全能版的AspectJ、另外一个阉割版的Spring AOP。 我们的项目使用spring,一开始很自然的想使用 SpringAOP,但是很不幸 SpringAOP 仅支持容器管理对象的AOP,这无法满足我的需求。 所以只能使用AspectJ了。 AspectJ 有2种织入方式(使用acj编译器静态织入,使用javaagent动态织入),这里顺便提一下 javaagent技术,它允许用户在类加载前改变类的字节码,受益于java的动态能力,aspectJ可以优雅的实现动态织入技术。 AOP的核心思想是比较简单的,但是切点规则相当繁琐,不容易全面掌握,Pointcut表达式以后再详细写吧。 把AOP run起来,废了不少时间,解决了各种奇葩问题,因此现在这里写一下过程,供对AOP应用有兴趣的同学参考。
下面以项目为例详细说明实现过程,这里使用了动态织入的方式:
注意: aspectJ包的版本和java 版本一致,否则运行时会出现类被损坏的异常。 我们项目使用的java8,这个地方我掉过坑。
org.aspectj aspectjweaver 1.8.12 org.aspectj aspectjrt 1.8.12
配置很简单,一个切面类。 织入目标的包路径(注意要包含切面类的路径,否则切面类会缺少需要被织入的aspectOf()方法,这也是掉过坑的)
有2种方式
1. javaagent方式 (我使用的这种方式)java "-javaagent:/Users/qiyu/.m2/repository/org/aspectj/aspectjweaver/1.8.7/aspectjweaver-1.8.7.jar"2. tomcat server.xml配置文件增加
废话少说,放码过来!
2个注解类,一个切面类都贴出来了,供大家参考借鉴。
//权限位注解@Repeatable(AclRightAnnotations.class)@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface AclRightAnnotation { AclRightType value() ;}//可以添加多个权限位的注解 (java8新特性)@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface AclRightAnnotations { AclRightAnnotation[] value() ;}@Aspectpublic class AclRightAspect { //含有AclRightAnnotation注解的目标类且为public方法 切点定义 @Pointcut("@within(com.taobao.bright.service.system.auth.AclRightAnnotation) && execution(public * *(..))") public void classPointCut() {} //含有AclRightAnnotation注解的方法且为public方法 切点定义 @Pointcut("@annotation(com.taobao.bright.service.system.auth.AclRightAnnotation) && execution(public * *(..))") public void actionPointCut() {} // @Around("classPointCut() || actionPointCut()") public void checkPermission(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature methodSig = (MethodSignature) joinPoint.getSignature(); //通过getDeclaredAnnotationsByType反射方法,获取注解中的权限位 AclRightAnnotation[] methodAnnotations = methodSig.getMethod().getDeclaredAnnotationsByType(AclRightAnnotation.class); AclRightAnnotation[] classAnnotations = joinPoint.getTarget().getClass().getDeclaredAnnotationsByType(AclRightAnnotation.class); Listannotations = new ArrayList<>(); annotations.addAll(Arrays.asList(classAnnotations)); annotations.addAll(Arrays.asList(methodAnnotations)); boolean hasRight = false; String right = ""; for(AclRightAnnotation acl : annotations){ right= acl.value().getValue(); if(AclRightCache.checkCurrentUserRight(right)){ hasRight = true ; break; } } if(!hasRight){ Object[] methodArgs = joinPoint.getArgs(); for(Object arg: methodArgs){ if (arg instanceof Context) { //返回鉴权异常给客户端 Context context = (Context) arg; Result result = new Result<>(); result.addActionError("No permission!"); context.put(Result.KEY_SIGN, result); } } }else { joinPoint.proceed(); } }}
使用起来就很简单了,直接在类或方法上增加注解就可以了。 一行代码就可以搞定这个类的注释了。如果方法上需要特殊的权限,也可以增加注解。
//权限注解可以加到类或方法上。 加在类上,会对类中所有public方法都生效。 多个权限位之间是OR的关系,即有一个权限位验证通过即可。
@AclRightAnnotation(value = AclRightType.BRIGHT_STATION_VIP_MANAGE)class Action{ public void get() { //业务代码 } public void put() { //业务代码 } public void delete() { //业务代码 } public void post() { //业务代码 }}
AOP的使用过程大致如上,使用如上方法后消除了项目代码中大量的重复鉴权代码。欢迎各位参考交流。
转载地址:http://oktkx.baihongyu.com/