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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。