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

相關文章
相關標籤/搜索