fastjson自由:controller上指定active profile,让你想序列化什么字段就序列化什么字段

1、前言

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

在接口A里,要返回所有字段;java

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

可能呢,有些同窗会选择从新定义一个新的vo,但这样,会致使vo类数量特别多;你说,要是所有字段都返回吧,则会给前端同窗形成困扰。github

针对须要排除部分字段,但愿能达到下面这样的效果:

一、在controller上指定一个profile web

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

三、请求接口,返回的结果,以下:json

四、若是注释掉注解那两行,则效果以下:mvc

针对仅须要包含部分字段,但愿能达到下面的效果:

一、在controller上指定profileapp

/**
     * 测试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;
    }

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

@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;
}

三、请求结果以下:

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

2、实现思路

思路以下:

  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对象进行处理,不对排除集合中的字段序列化

这么讲起来,仍是比较抽象,具体能够看第一章的效果截图。

3、实现细节

使用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进行过滤
    }

4、总结与源码

若是使用 fastjson的话,是支持propertyFilter的,具体能够了解下,也是对字段进行include和exclude,但感受不是特别方便,尤为是粒度要支持到接口级别。

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

你们有什么意见和建议均可以提,也欢迎加群讨论。

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

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

相关文章
相关标签/搜索