zoukankan      html  css  js  c++  java
  • fastjson自由:controller上指定active profile,让你想序列化什么字段就序列化什么字段

    一、前言

    最近有个需求,其实这个需求以前就有,比如定义了一个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

  • 相关阅读:
    Sql Server 2008 为开发带来的新特性
    MSMQ 与ESB
    面向服务架构(SOA)和企业服务总线(ESB)
    IBM网站 构建您的SOA 系列文章
    Visual Studio 2008 和 .NET Framework 3.5 Training Kit
    模式与实践 系列发布WCF Security Guidance 1.0
    跟NHibernate之夏一起学习NHibernate
    ORACLE一些不常用的脚本
    ORACLE已建表能否创建分区
    Access数据库问题锦集
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11828582.html
Copyright © 2011-2022 走看看