一、自定义注解

先聊聊这个需求,我需要根据用户的权限对数据进行一些处理,但是痛点在哪里呢?用户的权限是在请求的时候知道的,我怎么把用户的权限传递给处理规则呢?想了以下几种方案:

  1. Mybatis 拦截器:如果你的权限参数可以渗透到 Dao 层,那么这是最好的处理方式,直接在 Dao 层数据返回的时候,根据权限做数据处理。
  2. Dubbo 过滤器:如果 Dao 层没办法实现的话,只好考虑在 service 层做数据处理了。
  3. 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 AOPJava 反射动态代理 的知识。

@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实现注解的动态赋值的更多相关文章

  1. AOP获取方法注解实现动态切换数据源

    AOP获取方法注解实现动态切换数据源(以下方式尚未经过测试,仅提供思路) ------ 自定义一个用于切换数据源的注解: package com.xxx.annotation; import org. ...

  2. Java注解如何对属性动态赋值

    学而不思则罔,思而不学则殆 前言 大家都用过Spring的@Value("xxx")注解,如果没有debug过源码的同学对这个操作还是一知半解,工作一年了学了反射学了注解,还是不会 ...

  3. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  4. Java注解--实现动态数据源切换

    当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换. 实现原理 在Spring 2.0.1中引入了Abstract ...

  5. 总结切面编程AOP的注解式开发和XML式开发

    有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...

  6. Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.

    可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...

  7. 二)Spring AOP编程思想与动态代理

    一.aop编程思想 1.面向切面,就是能够不动源码的情况下,从横切面切入新的代码功能. 2.实现原理是动态代理 动态代理的步骤 a.写生产厂家,实现接口,代理只能代理接口 b.动态代理类实现Invoc ...

  8. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  9. spring AOP自定义注解 实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

随机推荐

  1. PE 文件格式详解

    PE文件 是微软 Win32 环境下可执行文件的标准格式. 所谓的可执行文件并不仅仅是常见的 EXE 文件,DLL,SYS,VXD 等文件也都属于 PE 格式. |-------> DOS_MZ ...

  2. XSS扫盲到漏洞挖掘上手

    复习xss ,也总结一下XSS基础的点到进阶的知识  目录 0x01 XSS扫盲入门 0x02 XSS payload构造 0x03 XSS payload变形进阶   0x01 XSS扫盲入门 XS ...

  3. Kali Linux开启ssh服务设置自启

    几天没写 水一些今天遇到的问题 0x01 配置SSH参数 修改sshd_config文件,命令为:   vi /etc/ssh/sshd_config 将#PasswordAuthentication ...

  4. [Luogu3065][USACO12DEC]第一!First!

    题目描述 Bessie has been playing with strings again. She found that by changing the order of the alphabe ...

  5. Intel Ivy Bridge Microarchitecture Events

    This is a list of all Intel Ivy Bridge Microarchitecture performance counter event types. Please see ...

  6. Azure EventHub快速入门和使用心得

    Azure Event Hubs(事件中心)是一个大数据流式数据摄取服务平台,每秒接受数百万事件; EventHubs 是一个有数据保留期限的缓冲区,类似分布式日志:可缩放的关键在于[分区消费模型], ...

  7. JDK1.8 新特性详解

    一  引言 现在java 10都已经出来了,而自己对java 8的一些新特性都不了解,很是惭愧,而且许多面试都有问到java8的新特性,借此博客好好学习这些新特性 二  新特性 1 default关键 ...

  8. 回头看 vue-router

    回头看 vue-router 复习 我的github iSAM2016 目录 响应路由参数的变化 嵌套路由 函数式导航 路由的命名 示例:切换路由的时候可以修改页面的标题 导航钩子 全局钩子 实例:检 ...

  9. Nginx 热部署和日志切割,你学会了吗?

    上篇文章,我们已经安装好 Nginx,并且配置好 Nginx 文件后,这个时候我就需要操作 Nginx 的命令行了,这篇文章主要讲解 Nginx 命令行相关知识,并通过日常遇到的热部署.切割日志文件场 ...

  10. marquee滚动标签

    marquee语法    <marquee></marquee> 实例一<marquee>Hello, World</marquee> marquee常 ...