• 当JAVA注解、AOP、SpEL相遇,更多可能变为了现实


    常规情况下,我们可以通过业务定制化的注解,借助AOP机制来实现某些通用的处理策略。比如定义个@Permission注解,可以用于标识在具体的方法上,然后用来指定某个方法必须要指定角色的人才能够访问调用。

    
        // 标识只有管理员角色才能调用此接口
        @Permission(role = UserRole.ADMIN)
        public void deleteResource(DeleteResourceReqBody reqBody) {
            // do something here...
        }
    
    

    这里,注解里面传入的参数始终是编码的时候就可以确定下来的固定值(role = UserRole.ADMIN)。

    在业务开发中,也许你会遇到另一种场景:

    比如有个文档资源控制接口,你需要判断出当前用户操作的目标文档ID,然后去判断这个用户是否有此文档的操作权限。

    我们希望能够使用注解的方式来实现,需要能够将动态的文档ID通过注解传递,然后在Aspect处理类中获取到文档ID然后进行对应的权限控制。但是按照常规方式去写代码的时候,会发现并不支持直接传递一个请求对象到注解中。

    这个时候,就轮到我们的主角“SpEL表达式”上场了,借助EL表达式,可以让我们将上面的想法变为现实。

    下面讲一下具体的做法。

    1. 先定义一个业务注解,其中参数支持传入EL表达式
    
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface ResourceAccessPermission {
        /**
         * 操作的目标资源的唯一ID, 支持EL表达式
         *
         * @return  ID
         */
        String objectId();
    }
    
    
    1. 编写EL表达式的解析器,如下所示:
    
    public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
        private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
        private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
        private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
    
    
        public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
            Method targetMethod = getTargetMethod(targetClass, method);
            ExpressionRootObject root = new ExpressionRootObject(object, args);
            return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
        }
    
    
        public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
            return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
        }
    
        private Method getTargetMethod(Class<?> targetClass, Method method) {
            AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
            Method targetMethod = this.targetMethodCache.get(methodKey);
            if (targetMethod == null) {
                targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
                this.targetMethodCache.put(methodKey, targetMethod);
            }
            return targetMethod;
        }
    
    }
    
    @Getter
    @ToString
    @AllArgsConstructor
    public class ExpressionRootObject {
        private final Object object;
        private final Object[] args;
    }
    
    
    1. 编写对应的Aspect切换处理类,借助上面的EL解析器进行获取注解中的传入的EL表达式,然后获取方法的入参,读取EL表达式代表的真实的参数值,进而按照业务需要的逻辑进行处理。
    
    @Component
    @Aspect
    @Slf4j
    public class ResourceAccessPermissionAspect {
        private ExpressionEvaluator<String> evaluator = new ExpressionEvaluator<>();
    
        @Pointcut("@annotation(com.vzn.demo.ResourceAccessPermission)")
        private void pointCut() {
    
        }
    
        @Before("pointCut()")
        public void doPermission(JoinPoint joinPoint) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
             ResourceAccessPermission permission = method.getAnnotation(ResourceAccessPermission.class);
            if (joinPoint.getArgs() == null) {
                return;
            }
    
            // [重点]EL表达式的方式读取对应参数值
             EvaluationContext evaluationContext = evaluator.createEvaluationContext(joinPoint.getTarget(),
                    joinPoint.getTarget().getClass(), ((MethodSignature) joinPoint.getSignature()).getMethod(),
                    joinPoint.getArgs());
             AnnotatedElementKey methodKey =
                     new AnnotatedElementKey(((MethodSignature) joinPoint.getSignature()).getMethod(),
                            joinPoint.getTarget().getClass());
    
            // 读取objectID,如果以#开头则按照EL处理,否则按照普通字符串处理
            String objectId;
            if (StringUtils.startsWith(permission.objectId(), "#")) {
                objectId = evaluator.condition(permission.objectId(), methodKey, evaluationContext, String.class);
            } else {
                objectId = permission.objectId();
            }
    
            // TODO 对objectID进行业务自定义逻辑处理
        }
    }
    
    

    至此,通过EL表达式动态注解参数传递与解析处理的逻辑就都构建完成了。

    1. 具体业务使用的时候,直接通过EL表达式从请求体中动态的获取到对应的参数值然后传入到注解aspect切面处理逻辑中,按照定制的业务逻辑进行统一处理。
    
        @ResourceAccessPermission(objectId = "#reqBody.docUniqueId")
        public void deleteResource(DeleteResourceReqBody reqBody) {
            // do something here...
        }
    
    

    借助JAVA注解 + AOP + SpEL的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。


    我是悟道君,聊技术、又不仅仅聊技术~
    期待与你一起探讨,一起成长为更好的自己。

  • 相关阅读:
    Spring Event 观察者模式, 业务解耦神器
    idea搭建ssm项目全过程详解:
    小程序面试题:小程序兼容性问题
    实用网站大全
    Python手写人脸识别
    2051. The Category of Each Member in the Store
    用AI模型实现两种图像是否相同的验证
    【软件工程】期末重点
    react: scss使用样式
    (附源码)spring boot新闻管理系统 毕业设计 211113
  • 原文地址:https://www.cnblogs.com/softwarearch/p/16392134.html