Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)
这篇文章主要讲SpringMVC如何动态的去返回Json数据在我们做Web接口开发的时候,经常会遇到这种场景。
两个请求,返回同一个对象,但是需要的返回字段并不相同。如以下场景
/**
*返回所有名称以及Id
*/
@RequestMapping("list")
@ResponseBody
publicList<Article>findAllNameAndId(){
returnarticleService.findAll();
}
/**
*返回所有目录详情
*/
@RequestMapping("list-detail")
@ResponseBody
publicList<Article>findAllDetail(){
returnarticleService.findAll();
}
SpringMVC默认使用转json框架是jackson。大家也知道,jackson可以在实体类内加注解,来指定序列化规则,但是那样比较不灵活,不能实现我们目前想要达到的这种情况。
这篇文章主要讲的就是通过自定义注解,来更加灵活,细粒化控制json格式的转换。
最终我们需要实现如下的效果:
@RequestMapping(value="{id}",method=RequestMethod.GET)
//返回时候不包含filter内的createTime,updateTime字段
@JSON(type=Article.class,filter="createTime,updateTime")
publicArticleget(@PathVariableStringid){
returnarticleService.get(id);
}
@RequestMapping(value="list",method=RequestMethod.GET)
//返回时只包含include内的id,name字段
@JSON(type=Article.class,include="id,name")
publicList<Article>findAll(){
returnarticleService.findAll();
}
jackson编程式过滤字段
jackson中,我们可以在实体类上加上@JsonFilter注解,并且通过ObjectMapper.setFilterProvider来进行过滤规则的设置。这里简单介绍一下setFilterProvider的使用
@JsonFilter("ID-TITLE")
classArticle{
privateStringid;
privateStringtitle;
privateStringcontent;
//...getter/setter
}
//Demo
classDemo{
publicvoidmain(Stringargs[]){
ObjectMappermapper=newObjectMapper();
//SimpleBeanPropertyFilter.filterOutAllExcept("id,title")
//过滤除了id,title以外的所有字段,也就是序列化的时候,只包含id和title
mapper.setFilterProvider(newSimpleFilterProvider().addFilter("ID-TITLE",
SimpleBeanPropertyFilter.filterOutAllExcept("id,title")));
StringfilterOut=mapper.writeValueAsString(newArticle());
mapper=newObjectMapper();
//SimpleBeanPropertyFilter.serializeAllExcept("id,title")
//序列化所有字段,但是排除id和title,也就是除了id和title之外,其他字段都包含进json
mapper.setFilterProvider(newSimpleFilterProvider().addFilter("ID-TITLE",
SimpleBeanPropertyFilter.serializeAllExcept(filter.split("id,title"))));
StringserializeAll=mapper.writeValueAsString(newArticle());
System.out.println("filterOut:"+filterOut);
System.out.println("serializeAll:"+serializeAll);
}
}
输出结果
filterOut:{id:"",title:""}
serializeAll:{content:""}
封装json转换
通过上面的代码,我们发现,可以使用setFilterProvider来灵活的处理需要过滤的字段。不过上面的方法还有一些缺陷就是,还是要在原来的model上加注解,这里我们使用ObjectMapper.addMixIn(Class<?>type,Class<?>mixinType)方法,这个方法就是讲两个类的注解混合,让第一个参数的类能够拥有第二个参数类的注解。让需要过滤的model和@JsonFilter注解解除耦合
packagediamond.cms.server.json;
importcom.fasterxml.jackson.annotation.JsonFilter;
importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.ObjectMapper;
importcom.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
importcom.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
*dependonjackson
*@authorDiamond
*/
publicclassCustomerJsonSerializer{
staticfinalStringDYNC_INCLUDE="DYNC_INCLUDE";
staticfinalStringDYNC_FILTER="DYNC_FILTER";
ObjectMappermapper=newObjectMapper();
@JsonFilter(DYNC_FILTER)
interfaceDynamicFilter{
}
@JsonFilter(DYNC_INCLUDE)
interfaceDynamicInclude{
}
/**
*@paramclazz需要设置规则的Class
*@paraminclude转换时包含哪些字段
*@paramfilter转换时过滤哪些字段
*/
publicvoidfilter(Class<?>clazz,Stringinclude,Stringfilter){
if(clazz==null)return;
if(include!=null&&include.length()>0){
mapper.setFilterProvider(newSimpleFilterProvider().addFilter(DYNC_INCLUDE,
SimpleBeanPropertyFilter.filterOutAllExcept(include.split(","))));
mapper.addMixIn(clazz,DynamicInclude.class);
}elseif(filter!=null&&filter.length()>0){
mapper.setFilterProvider(newSimpleFilterProvider().addFilter(DYNC_FILTER,
SimpleBeanPropertyFilter.serializeAllExcept(filter.split(","))));
mapper.addMixIn(clazz,DynamicFilter.class);
}
}
publicStringtoJson(Objectobject)throwsJsonProcessingException{
returnmapper.writeValueAsString(object);
}
}
我们之前的Demo可以变成:
//Demo
classDemo{
publicvoidmain(Stringargs[]){
CustomerJsonSerializercjs=newCustomerJsonSerializer();
//设置转换Article类时,只包含id,name
cjs.filter(Article.class,"id,name",null);
Stringinclude=cjs.toJson(newArticle());
cjs=newCustomerJsonSerializer();
//设置转换Article类时,过滤掉id,name
cjs.filter(Article.class,null,"id,name");
Stringfilter=cjs.toJson(newArticle());
System.out.println("include:"+include);
System.out.println("filter:"+filter);
}
}
输出结果
include:{id:"",title:""}
filter:{content:""}
自定义@JSON注解
我们需要实现文章开头的那种效果。这里我自定义了一个注解,可以加在方法上,这个注解是用来携带参数给CustomerJsonSerializer.filter方法的,就是某个类的某些字段需要过滤或者包含。
packagediamond.cms.server.json;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceJSON{
Class<?>type();
Stringinclude()default"";
Stringfilter()default"";
}
实现SpringMVC的HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandler接口SpringMVC用于处理请求返回值。看一下这个接口的定义和描述,接口有两个方法supportsReturnType用来判断处理类是否支持当前请求,handleReturnValue就是具体返回逻辑的实现。
//SpringMVC源码
packageorg.springframework.web.method.support;
importorg.springframework.core.MethodParameter;
importorg.springframework.web.context.request.NativeWebRequest;
publicinterfaceHandlerMethodReturnValueHandler{
booleansupportsReturnType(MethodParameterreturnType);
voidhandleReturnValue(ObjectreturnValue,MethodParameterreturnType,
ModelAndViewContainermavContainer,NativeWebRequestwebRequest)throwsException;
}
我们平时使用@ResponseBody就是交给RequestResponseBodyMethodProcessor这个类处理的
还有我们返回ModelAndView的时候,是由ModelAndViewMethodReturnValueHandler类处理的
要实现文章开头的效果,我实现了一个JsonReturnHandler类,当方法有@JSON注解的时候,使用该类来处理返回值。
packagediamond.cms.server.json.spring;
importjava.lang.annotation.Annotation;
importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.List;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.springframework.core.MethodParameter;
importorg.springframework.http.MediaType;
importorg.springframework.http.server.ServletServerHttpRequest;
importorg.springframework.http.server.ServletServerHttpResponse;
importorg.springframework.web.context.request.NativeWebRequest;
importorg.springframework.web.method.support.HandlerMethodReturnValueHandler;
importorg.springframework.web.method.support.ModelAndViewContainer;
importorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
importdiamond.cms.server.json.CustomerJsonSerializer;
importdiamond.cms.server.json.JSON;
publicclassJsonReturnHandlerimplementsHandlerMethodReturnValueHandler{
@Override
publicbooleansupportsReturnType(MethodParameterreturnType){
//如果有我们自定义的JSON注解就用我们这个Handler来处理
booleanhasJsonAnno=returnType.getMethodAnnotation(JSON.class)!=null;
returnhasJsonAnno;
}
@Override
publicvoidhandleReturnValue(ObjectreturnValue,MethodParameterreturnType,ModelAndViewContainermavContainer,
NativeWebRequestwebRequest)throwsException{
//设置这个就是最终的处理类了,处理完不再去找下一个类进行处理
mavContainer.setRequestHandled(true);
//获得注解并执行filter方法最后返回
HttpServletResponseresponse=webRequest.getNativeResponse(HttpServletResponse.class);
Annotation[]annos=returnType.getMethodAnnotations();
CustomerJsonSerializerjsonSerializer=newCustomerJsonSerializer();
Arrays.asList(annos).forEach(a->{
if(ainstanceofJSON){
JSONjson=(JSON)a;
jsonSerializer.filter(json.type(),json.include(),json.filter());
}
});
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Stringjson=jsonSerializer.toJson(returnValue);
response.getWriter().write(json);
}
}
通过这些,我们就可以最终实现以下效果。
classArticle{
privateStringid;
privateStringtitle;
privateStringcontent;
privateLongcreateTime;
//...getter/setter
}
@Controller
@RequestMapping("article")
classArticleController{
@RequestMapping(value="{id}",method=RequestMethod.GET)
@JSON(type=Article.class,filter="createTime")
publicArticleget(@PathVariableStringid){
returnarticleService.get(id);
}
@RequestMapping(value="list",method=RequestMethod.GET)
@JSON(type=Article.class,include="id,title")
publicList<Article>findAll(){
returnarticleService.findAll();
}
}
请求/article/{articleId}
{
id:"xxxx",
title:"xxxx",
content:"xxxx"
}
请求article/list
[{id:"xx",title:""},{id:"xx",title:""},{id:"xx",title:""}...]
下载地址:cms-admin-end_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。