SpEL + AOP实现注解的动态赋值
一、自定义注解
先聊聊这个需求,我需要根据用户的权限对数据进行一些处理,但是痛点在哪里呢?用户的权限是在请求的时候知道的,我怎么把用户的权限传递给处理规则呢?想了以下几种方案:
- Mybatis 拦截器:如果你的权限参数可以渗透到 Dao 层,那么这是最好的处理方式,直接在 Dao 层数据返回的时候,根据权限做数据处理。
- Dubbo 过滤器:如果 Dao 层没办法实现的话,只好考虑在 service 层做数据处理了。
- ResponseBodyAdvice :要是 service 层也没办法做到,只能在访问层数据返回的时候,根据权限做数据处理。(以下介绍的正是这种方式)
那么现在有个难点就是:我怎么把 request 的权限参数传递到 response 中呢?当然可以在 Spring 拦截器中处理,但是我不想把这段代码侵入到完整的鉴权逻辑中。突然想到,我能不能像 spring-data-redis 中 @Cacheable 一样,利用注解和 SpEL 表达式动态的传递权限参数呢?然后在 ResponseBodyAdvice 读取这个注解的权限参数,进而对数据进行处理。
首先,我们需要有个自定义注解,它有两个参数:key 表示 SpEL 表达式;userType 表示权限参数。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseSensitiveOverride {
/**
* SPEL 表达式
*
* @return
*/
String key() default "";
/**
* 1:主账号、2:子账号
*/
int userType() default 1;
}
然后,把这个注解放在路由地址上,key 写入获取权限参数的 SpEL 表达式:
@ResponseSensitiveOverride(key = "#driverPageParam.getUserType()")
@RequestMapping(value = "/queryPage", method = RequestMethod.POST)
public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) {
return driverService.queryPageAdmin(driverPageParam);
}
二、SpEl + AOP 注解赋值
现在 SpEL 表达式是有了,怎么把 SpEL 表达式的结果赋值给注解的 userType 参数呢?这就需要用 Spring AOP 、Java 反射 和 动态代理 的知识。
@Aspect
@Component
public class SensitiveAspect {
private SpelExpressionParser spelParser = new SpelExpressionParser();
/**
* 返回通知
*/
@AfterReturning("@annotation(com.yungu.swift.base.model.annotation.ResponseSensitiveOverride) && @annotation(sensitiveOverride)")
public void doAfter(JoinPoint joinPoint, ResponseSensitiveOverride sensitiveOverride) throws Exception {
//获取方法的参数名和参数值
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
List<Object> paramList = Arrays.asList(joinPoint.getArgs());
//将方法的参数名和参数值一一对应的放入上下文中
EvaluationContext ctx = new StandardEvaluationContext();
for (int i = 0; i < paramNameList.size(); i++) {
ctx.setVariable(paramNameList.get(i), paramList.get(i));
}
// 解析SpEL表达式获取结果
String value = spelParser.parseExpression(sensitiveOverride.key()).getValue(ctx).toString();
//获取 sensitiveOverride 这个代理实例所持有的 InvocationHandler
InvocationHandler invocationHandler = Proxy.getInvocationHandler(sensitiveOverride);
// 获取 invocationHandler 的 memberValues 字段
Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
// 因为这个字段是 private final 修饰,所以要打开权限
hField.setAccessible(true);
// 获取 memberValues
Map memberValues = (Map) hField.get(invocationHandler);
// 修改 value 属性值
memberValues.put("userType", Integer.parseInt(value));
}
}
通过这种方式,我们就实现了为注解动态赋值。
三、ResponseBodyAdvice 处理数据
现在要做的事情就是在 ResponseBody 数据返回前,对数据进行拦截,然后读取注解上的权限参数,从而对数据进行处理,这里使用的是 SpringMVC 的 ResponseBodyAdvice 来实现:
@Slf4j
@RestControllerAdvice
@Order(-1)
public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return SysUserDto.USER_TYPE_PRIMARY;
}
};
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
if (returnType.hasMethodAnnotation(ResponseSensitiveOverride.class)) {
ResponseSensitiveOverride sensitiveOverride = returnType.getMethodAnnotation(ResponseSensitiveOverride.class);
threadLocal.set(sensitiveOverride.userType());
return true;
}
return false;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body != null && SysUserDto.USER_TYPE_SUB.equals(threadLocal.get())) {
// 业务处理
}
return body;
}
}
题外话,其实我最后还是摈弃了这个方案,选择了 Dubbo 过滤器的处理方式,为什么呢?因为在做数据导出的时候,这种方式没办法对二进制流进行处理呀!汗~ 但是该方案毕竟耗费了我一个下午的心血,还是在此记录一下,可能有它更好的适用场景!
SpEL + AOP实现注解的动态赋值的更多相关文章
- AOP获取方法注解实现动态切换数据源
AOP获取方法注解实现动态切换数据源(以下方式尚未经过测试,仅提供思路) ------ 自定义一个用于切换数据源的注解: package com.xxx.annotation; import org. ...
- Java注解如何对属性动态赋值
学而不思则罔,思而不学则殆 前言 大家都用过Spring的@Value("xxx")注解,如果没有debug过源码的同学对这个操作还是一知半解,工作一年了学了反射学了注解,还是不会 ...
- 利用Spring AOP自定义注解解决日志和签名校验
转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...
- Java注解--实现动态数据源切换
当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换. 实现原理 在Spring 2.0.1中引入了Abstract ...
- 总结切面编程AOP的注解式开发和XML式开发
有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...
- Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.
可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...
- 二)Spring AOP编程思想与动态代理
一.aop编程思想 1.面向切面,就是能够不动源码的情况下,从横切面切入新的代码功能. 2.实现原理是动态代理 动态代理的步骤 a.写生产厂家,实现接口,代理只能代理接口 b.动态代理类实现Invoc ...
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- spring AOP自定义注解 实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
随机推荐
- CEILING保留n位小数向上取整
number=3.1415926 CEILING(number*POWER(10,n))/POWER(10,n) 思路为:乘以10的n次方得到要保留的小数部分并转换为整数,再用CEILING向上取整, ...
- Linux下聊天和文件传输软件
全平台聊天软件 米聊 官网地址: http://www.miliao.com 潮信 官网地址: https://www.chaoxin.com
- procdump64+mimikatz获取win用户hash密码
1.导出lsass.exe procdump64.exe -accepteula -ma lsass.exe lsass.dmp 2.执行mimikatz mimikatz.exe "sek ...
- [Luogu1379]八数码难题
题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了 ...
- [LUOGU1868] 饥饿的奶牛 - dp二分
题目描述 有一条奶牛冲出了围栏,来到了一处圣地(对于奶牛来说),上面用牛语写着一段文字. 现用汉语翻译为: 有N个区间,每个区间x,y表示提供的x~y共y-x+1堆优质牧草.你可以选择任意区间但不能有 ...
- .NET Core ORM 类库Petapoco中对分页Page添加Order By对查询的影响
最近一直在使用Petapoco+Entity Framework Core结合开发一套系统. 使用EFCore进行Code First编码,使用PMC命令生成数据库表的信息. 使用Petapoco进行 ...
- java读取存在src目录下和存在同级目录下的配置文件
如果我有个文件存在src下一级的地方和存在src同级的目录应该怎么用相对路径去获取如图: 一.如果存在src同级的地方应该是InputStream in = new BufferedInputStre ...
- Kubernetes1-K8s的简单介绍
一.简介 1.什么是Kubernetes 简称K8s,用8代替8个字符“ubernerte”而成的速写,K8s是一个开源的容器编排平台,它是一个跨主机集群的开源容器调度平台,用于管理云平台中多个主机上 ...
- netty源码解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage
PoolChunk用来分配大于或等于一个page的内存,如果需要小于一个page的内存,需要先从PoolChunk中分配一个page,然后再把一个page切割成多个子页-subpage,最后把内存以s ...
- vue-cli添加bootstrap
如何引入bootstrap npm install --save-dev bootstrap 在main.js中引入 import 'bootstrap/dist/css/bootstrap.min. ...