常规情况下,我们可以通过业务定制化的注解,借助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的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。


我是悟道君,聊技术、又不仅仅聊技术~

期待与你一起探讨,一起成长为更好的自己。

当JAVA注解、AOP、SpEL相遇,更多可能变为了现实的更多相关文章

  1. [转帖]java注解核心知识总结

    java注解核心知识总结 2019-11-01 20:39:50 从事Java 阅读数 2  收藏 文章标签: java注解总结程序员 更多 分类专栏: java 注解   版权声明:本文为博主原创文 ...

  2. java之aop使用及自定义注解

    目的: 1.Java注解简介 2.Java元注解(重点) 3.自定义注解 案例一(获取类与方法上的注解值) 案例二(获取类属性上的注解属性值) 案例三(获取参数修饰注解对应的属性值) 4.Aop自定义 ...

  3. Java框架spring 学习笔记(十四):注解aop操作

    回见Java框架spring Boot学习笔记(十三):aop实例操作,这里介绍注解aop操作 首先编写一个切入点HelloWorld.java package com.example.spring; ...

  4. 从零开始学 Java - Spring AOP 实现主从读写分离

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...

  5. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  6. SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制

      通常我们可以在前端通过防抖和节流来解决短时间内请求重复提交的问题,如果因网络问题.Nginx重试机制.微服务Feign重试机制或者用户故意绕过前端防抖和节流设置,直接频繁发起请求,都会导致系统防重 ...

  7. 从零开始学 Java - Spring AOP 实现用户权限验证

    每个项目都会有权限管理系统 无论你是一个简单的企业站,还是一个复杂到爆的平台级项目,都会涉及到用户登录.权限管理这些必不可少的业务逻辑.有人说,企业站需要什么权限管理阿?那行吧,你那可能叫静态页面,就 ...

  8. java注解(Annotation)解析

    注解(Annotation)在java中应用非常广泛.它既能帮助我们在编码中减少错误,(比如最常见的Override注解),还可以帮助我们减少各种xml文件的配置,比如定义AOP切面用@AspectJ ...

  9. Java Spring AOP用法

    Java Spring AOP用法 Spring AOP Java web 环境搭建 Java web 项目搭建 Java Spring IOC用法 spring提供了两个核心功能,一个是IoC(控制 ...

随机推荐

  1. 初识ES6(JavaScript)

    初识ES6(JavaScript) 关于ES6: ES6即ECMAScript6,是一种规范,JavaScript遵循了这种规范. *优点:*代码比较简洁. *缺点:*浏览器的兼容性不好. 1.变量和 ...

  2. 【Azure API 管理】解决API Management添加AAD Group时遇见的 Failed to query Azure Active Directory graph due to error 错误

    问题描述 为APIM添加AAD Group时候,等待很长很长的时间,结果添加失败.错误消息为: Write Groups ValidationError :Failed to query Azure ...

  3. android软件简约记账app开发day06-将记账条目添加到数据库并且绘制备注页面

    android软件简约记账app开发day06-将记账条目添加到数据库并且绘制备注页面 首先写添加到数据库 在DBOpenHelper中添加创建记账表的语句 //创建记账表 sql = "c ...

  4. 001_iBase4J学习之环境搭建

    目录 序言 正文 第一关.拉取项目 第二关.导入数据库 第三关.修改 JDBC 配置文件 第四关.环境搭建,修改 nginx 设置 第五关.添加地址白名单 尾声 序言 大家好,我是白墨! 本次的目标是 ...

  5. ASP.NET Core 6.0 添加 JWT 认证和授权

    序言 本文将分别介绍 Authentication(认证) 和 Authorization(授权). 并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能. 相关 ...

  6. Python学习笔记: 通过type annotation来伪指定变量类型

    简介 通过annotation像强类型language那样指定变量类型,包括参数和返回值的类型 因为Python是弱类型语言,这种指定实际上无效的.所以这种写法叫annotation,就是个注释参考的 ...

  7. Java 线程安全 与 锁

    Java 线程安全 与 锁 多线程内存模型 线程私有栈内存 每个线程 私有的内存区域 进程公有堆内存 同一个进程 共有的内存区域 为什么会有线程安全问题? 多个线程同时具有对同一资源的操作权限,又发生 ...

  8. 深度优先搜索 DFS 学习笔记

    深度优先搜索 学习笔记 引入 深度优先搜索 DFS 是图论中最基础,最重要的算法之一.DFS 是一种盲目搜寻法,也就是在每个点 \(u\) 上,任选一条边 DFS,直到回溯到 \(u\) 时才选择别的 ...

  9. Windows下搭建redis 哨兵环境

    从 https://github.com/tporadowski/redis/releases 下载windows版的redis,自行下载解压. 关于哨兵模式的讲解,强烈推荐 [深入学习redis(4 ...

  10. 视网膜血管分割代码(Pytorch实现)

    创建日期: 2021-12-24 17:00:00 update log(2021.12.24):B站视频删除了,回放看了一下,讲的不太行......2333,时间过得真快,转眼就是2022年了啊 2 ...