一、前言

最近有个需求,其实这个需求以前就有,比如定义了一个vo,包含了10个字段,

在接口A里,要返回全部字段;

但是在接口B里呢,需要复用这个 vo, 但是只需要返回其中8个字段。

可能呢,有些同学会选择重新定义一个新的vo,但这样,会导致vo类数量特别多;你说,要是全部字段都返回吧,则会给前端同学造成困扰。

针对需要排除部分字段,希望能达到下面这样的效果:

1、在controller上指定一个profile

2、在profile要应用到的class类型中,在field上添加注解

3、请求接口,返回的结果,如下:

4、如果注释掉注解那两行,则效果如下:

针对仅需要包含部分字段,希望能达到下面的效果:

1、在controller上指定profile

/**
* 测试include类型的profile,这里指定了:
* 激活profile为 includeProfile
* User中,对应的field将会被序列化,其他字段都不会被序列化
*/
@GetMapping("/test.do")
@ActiveFastJsonProfileInController(profile = "includeProfile",clazz = User.class)
public CommonMessage<User> test() {
User user = new User();
user.setId(111L);
user.setAge(8);
user.setUserName("kkk");
user.setHeight(165); CommonMessage<User> message = new CommonMessage<>();
message.setCode("0000");
message.setDesc("成功");
message.setData(user); return message;
}

2、在ActiveFastJsonProfileInController注解的clazz指定的类中,对需要序列化的字段进行注解:

@Data
public class User {
@FastJsonFieldProfile(profiles = {"includeProfile"},profileType = FastJsonFieldProfileType.INCLUDE)
private Long id; private String userName; private Integer age; @FastJsonFieldProfile(profiles = {"includeProfile"},profileType = FastJsonFieldProfileType.INCLUDE)
private Integer height;
}

3、请求结果如下:

{
code: "0000",
data: {
id: 111,
height: 165
},
desc: "成功"
}

二、实现思路

思路如下:

  1. 自定义注解,加在controller方法上,指定要激活的profile、以及对应的class
  2. 启动过程中,解析上述注解信息,构造出以下map:
  3. 添加 controllerAdvice,实现 org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice接口,对返回的responseBody进行处理
  4. controllerAdvice中,获取请求路径,然后根据请求路径,去第二步的map中,查询激活的profile和class信息
  5. 根据第四步获取到的:激活的profile和class信息,计算出对应的field集合,比如,根据profile拿到一个字段集合:{name,age},而这两个字段都是 exclude 类型的,所以不能对着两个字段进行序列化
  6. 根据第五步的field集合,对 responseBody对象进行处理,不对排除集合中的字段序列化

这么讲起来,还是比较抽象,具体可以看第一章的效果截图。

三、实现细节

使用fastjson进行序列化

spring boot版本为2.1.10,网上有很多文章,都是说的1.x版本时候的方法,在2.1版本并不适用。因为默认使用是jackson,所以我们这里将fastjson的HttpMessageConverter的顺序提前了:

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport { @Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
super.extendMessageConverters(converters);
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
Charset defaultCharset = Charset.forName("utf-8");
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(defaultCharset);
converter.setFastJsonConfig(fastJsonConfig); converter.setDefaultCharset(defaultCharset);
//将fastjson的消息转换器提到第一位
converters.add(0, converter);
}
}

读取controller上方法的注解信息,并构造map

这里,要点是,获取到RequestMapping注解上的url,以及对应方法上的自定义注解:

RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
//获取handlerMapping的map
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
for (HandlerMethod handlerMethod : handlerMethods.values()) {
Class<?> beanType = handlerMethod.getBeanType();//获取所在类
//获取方法上注解
RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
}

动态注册bean

上面构造了map后,本身可以直接保存到一个public static字段,但感觉不是很优雅,于是,构造了一个bean(包含上述的map),注册到spring中:

//bean的类定义
@Data
public class VoProfileRegistry {
private ConcurrentHashMap<String,ActiveFastJsonProfileInController> hashmap = new ConcurrentHashMap<String,ActiveFastJsonProfileInController>(); }
//动态注册到spring
applicationContext.registerBean(VoProfileRegistry.class);
VoProfileRegistry registry = myapplicationContext.getBean(VoProfileRegistry.class);
registry.setHashmap(hashmap);

在controllerAdvice中,对返回的responseBody进行处理时,根据请求url,从上述的map中,获取profile等信息:

org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite

@Override
public CommonMessage<Object> beforeBodyWrite(CommonMessage<Object> body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) { String requestPath = request.getURI().getPath();
log.info("path:{}",requestPath);
VoProfileRegistry voProfileRegistry = applicationContext.getBean(VoProfileRegistry.class);
ConcurrentHashMap<String, ActiveFastJsonProfileInController> hashmap = voProfileRegistry.getHashmap();
//从map中获取该url,激活的profile等信息
ActiveFastJsonProfileInController activeFastJsonProfileInControllerAnnotation = hashmap.get(requestPath);
if (activeFastJsonProfileInControllerAnnotation == null) {
log.info("no matched json profile,skip");
return body;
}
......//进行具体的对responseBody进行过滤
}

四、总结与源码

如果使用 fastjson的话,是支持propertyFilter的,具体可以了解下,也是对字段进行include和exclude,但感觉不是特别方便,尤其是粒度要支持到接口级别。

另外,本来,我也有另一个方案:在controllerAdvice里,获取到要排除的字段集合后,设置到ThreadLocal变量中,然后修改fastjson的源码,(fastjson对类进行序列化时,要获取class的field集合,可以在那个地方,对field集合进行处理),但是吧,那样麻烦不少,想了想就算了。

大家有什么意见和建议都可以提,也欢迎加群讨论。

源码在码云上(github太慢了):

https://gitee.com/ckl111/json-profile

fastjson自由:controller上指定active profile,让你想序列化什么字段就序列化什么字段的更多相关文章

  1. 如果在配置中将“system.serviceModel/serviceHostingEnvironment/multipleSiteBindingsEnabled”设置为 true,则需要终结点指定相对地址。如果在终结点上指定相对侦听 URI,则该地址可以是绝对地址。若要解决此问题,请为终结点“http://localhost/Service1.svc”指定相对 URI。

    问题: 如果在配置中将"system.serviceModel/serviceHostingEnvironment/multipleSiteBindingsEnabled"设置为 ...

  2. C#移除URL上指定的参数

    /// <summary>        /// 移除URL上指定的参数,不区分参数大小写        /// </summary>        public static ...

  3. Spring Boot 启动:No active profile set, falling back to default profiles: default

    启动 Spring Boot 失败,但是没有出现多余的异常信息: 检查之后发现是依赖的问题(之前依赖的是 spring-boot-starter),修改即可: 方法二: pom.xml加上下面两个依赖 ...

  4. SpringBoot Cmd运行Jar文件指定active文件的命令如下

    SpringBoot Cmd运行Jar文件指定active文件的命令如下 SpringBoot 命令行指定配置文件运行 ================================ ©Copyri ...

  5. No active profile set, falling back to default profiles: default

    No active profile set, falling back to default profiles: default 这个错误是由于idea没有设置默认启动环境,设置即可

  6. 为什么Domain controller上的time synchronization非常重要?

    虚拟机默认情况下所拥有的资源都是不同的, 比如说CPU clock. 在一个忙碌的系统中, 虚拟机甚至可能在很短的一段时间内被拒绝分配资源给它, 这种情况还可能发生在高系统负荷, VMotion, B ...

  7. spring boot 启动报错No active profile set, falling back to default profiles

    报错No active profile set, falling back to default profiles pom.xml加上下面两个依赖 <dependency> <gro ...

  8. springboot启动失败( No active profile set, falling back to default profiles: default)

    问题: springboot启动失败( No active profile set, falling back to default profiles: default) 解决方法 在pom.xml文 ...

  9. Java获取Linux上指定文件夹下所有第一级子文件夹

    说明:需要只获得第一级文件夹目录 package com.sunsheen.jfids.studio.monitor.utils; import java.io.BufferedReader; imp ...

随机推荐

  1. 阿里云DLA工具 查询tablestore数据

    OTS和DLA元信息映射逻辑 字段的映射关系 OTS DLA INTEGER(8bytes) bigint(8bytes) STRING varchar BINARY varbinary DOUBLE ...

  2. asp.net core刷新css缓存

    在非spa程序开发的时候.css经常会因为浏览器的缓存机制导致不刷新. 很多前端为了应对这个问题,都会引入webpack或者gulp等工具来处理css缓存的问题. 但是作为一个偏服务器端的程序员来说. ...

  3. 视频转换器 Wondershare Video Converter Ultimate v11.5.1 中文便携版

    Wondershare Video Converter Ultimate 是万兴公司出品的一款多功能音视频转换.DVD 刻录软件.视频下载软件.有了它,您可以随时随地观看.下载.编辑.转换.刻录视频, ...

  4. 自己写的Weblogic的poc

    """ 暂时只试用于Linux,先试试用一下反弹shell CVE-2017-10271的EXp """ import requests i ...

  5. JavaScript:如何获取某一天所在的星期

    我们会遇到的需求的是,获取今天或者某一天所在星期的开始和结束日期. 我们这里来获取今天所在星期的始末日期,我们可以通过(new Date).getDay()来获取今天是星期几,然后再通过这个减去或者加 ...

  6. SQL SERVER 还原误操作导致还原无法停止,处理办法

    昨天遇到运行库不知道单位哪个小伙子,把数据库还原了,导致单位业务全部瘫痪,主数据库一直显示正在还原,真的是不敢动,经过多方寻找,找到此脚本-------------------------数据库还原日 ...

  7. JS循环+循环嵌套+经典例题+图形题

    首先,了解一下循环嵌套的特点:外层循环转一次,内层循环转一圈. 在上一篇随笔中详细介绍了JS中的分支结构和循环结构,我们来简单的回顾一下For循环结构: 1.for循环有三个表达式,分别为: ①定义循 ...

  8. JQ获取元素属性值

    最近在学习JAVA Web,自己也是做个下列表左右选择的小案例. 获取某个元素的属性值一直以为是要调用atrr方法,不过好像获取元素的数组形式再遍历每个元素的时候想获取到它的属性值用attr方法有问题 ...

  9. openssl之aes对称加密

    AES:密码学中的高级加密标准(Advanced Encryption Standard,AES),又称 Rijndael加密法. 对称加密:用同一个密码  加密/解密  文件. 使用openssl中 ...

  10. Pycharm 常用快捷键大全

    Pycharm 常用快捷键 常用快捷键 快捷键 功能 Ctrl + Q 快速查看文档 Ctrl + F1 显示错误描述或警告信息 Ctrl + / 行注释(可选中多行) Ctrl + Alt + L ...