Spring MVC 更靈活的控制 json 返回問(wèn)題(自定義過(guò)濾字段)
這篇文章主要講 Spring MVC 如何動(dòng)態(tài)的去返回 Json 數(shù)據(jù) 在我們做 Web 接口開(kāi)發(fā)的時(shí)候, 經(jīng)常會(huì)遇到這種場(chǎng)景。
兩個(gè)請(qǐng)求,返回同一個(gè)對(duì)象,但是需要的返回字段并不相同。如以下場(chǎng)景
/**
* 返回所有名稱以及Id
*/
@RequestMapping("list")
@ResponseBody
public List<Article> findAllNameAndId() {
return articleService.findAll();
}
/**
* 返回所有目錄詳情
*/
@RequestMapping("list-detail")
@ResponseBody
public List<Article> findAllDetail() {
return articleService.findAll();
}
Spring MVC 默認(rèn)使用轉(zhuǎn)json框架是 jackson。 大家也知道, jackson 可以在實(shí)體類(lèi)內(nèi)加注解,來(lái)指定序列化規(guī)則,但是那樣比較不靈活,不能實(shí)現(xiàn)我們目前想要達(dá)到的這種情況。
這篇文章主要講的就是通過(guò)自定義注解,來(lái)更加靈活,細(xì)粒化控制 json 格式的轉(zhuǎn)換。
最終我們需要實(shí)現(xiàn)如下的效果:
@RequestMapping(value = "{id}", method = RequestMethod.GET)
// 返回時(shí)候不包含 filter 內(nèi)的 createTime, updateTime 字段
@JSON(type = Article.class, filter="createTime,updateTime")
public Article get(@PathVariable String id) {
return articleService.get(id);
}
@RequestMapping(value="list", method = RequestMethod.GET)
// 返回時(shí)只包含 include 內(nèi)的 id, name 字段
@JSON(type = Article.class , include="id,name")
public List<Article> findAll() {
return articleService.findAll();
}
jackson 編程式過(guò)濾字段
jackson 中, 我們可以在實(shí)體類(lèi)上加上 @JsonFilter 注解,并且通過(guò) ObjectMapper.setFilterProvider 來(lái)進(jìn)行過(guò)濾規(guī)則的設(shè)置。 這里簡(jiǎn)單介紹一下 setFilterProvider 的使用
@JsonFilter("ID-TITLE")
class Article {
private String id;
private String title;
private String content;
// ... getter/setter
}
// Demo
class Demo {
public void main(String args[]) {
ObjectMapper mapper = new ObjectMapper();
// SimpleBeanPropertyFilter.filterOutAllExcept("id,title")
// 過(guò)濾除了 id,title 以外的所有字段,也就是序列化的時(shí)候,只包含 id 和 title
mapper.setFilterProvider(new SimpleFilterProvider().addFilter("ID-TITLE",
SimpleBeanPropertyFilter.filterOutAllExcept("id,title")));
String filterOut = mapper.writeValueAsString(new Article());
mapper = new ObjectMapper();
// SimpleBeanPropertyFilter.serializeAllExcept("id,title")
// 序列化所有字段,但是排除 id 和 title,也就是除了 id 和 title之外,其他字段都包含進(jìn) json
mapper.setFilterProvider(new SimpleFilterProvider().addFilter("ID-TITLE",
SimpleBeanPropertyFilter.serializeAllExcept(filter.split("id,title"))));
String serializeAll = mapper.writeValueAsString(new Article());
System.out.println("filterOut:" + filterOut);
System.out.println("serializeAll :" + serializeAll);
}
}
輸出結(jié)果
filterOut:{id: "", title: ""}
serializeAll:{content:""}
封裝json轉(zhuǎn)換
通過(guò)上面的代碼,我們發(fā)現(xiàn),可以使用 setFilterProvider 來(lái)靈活的處理需要過(guò)濾的字段。不過(guò)上面的方法還有一些缺陷就是,還是要在 原來(lái)的 model 上加注解,這里我們使用 ObjectMapper.addMixIn(Class<?> type, Class<?> mixinType) 方法,這個(gè)方法就是講兩個(gè)類(lèi)的注解混合,讓第一個(gè)參數(shù)的類(lèi)能夠擁有第二個(gè)參數(shù)類(lèi)的注解。讓需要過(guò)濾的 model 和 @JsonFilter 注解解除耦合
package diamond.cms.server.json;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
* depend on jackson
* @author Diamond
*/
public class CustomerJsonSerializer {
static final String DYNC_INCLUDE = "DYNC_INCLUDE";
static final String DYNC_FILTER = "DYNC_FILTER";
ObjectMapper mapper = new ObjectMapper();
@JsonFilter(DYNC_FILTER)
interface DynamicFilter {
}
@JsonFilter(DYNC_INCLUDE)
interface DynamicInclude {
}
/**
* @param clazz 需要設(shè)置規(guī)則的Class
* @param include 轉(zhuǎn)換時(shí)包含哪些字段
* @param filter 轉(zhuǎn)換時(shí)過(guò)濾哪些字段
*/
public void filter(Class<?> clazz, String include, String filter) {
if (clazz == null) return;
if (include != null && include.length() > 0) {
mapper.setFilterProvider(new SimpleFilterProvider().addFilter(DYNC_INCLUDE,
SimpleBeanPropertyFilter.filterOutAllExcept(include.split(","))));
mapper.addMixIn(clazz, DynamicInclude.class);
} else if (filter !=null && filter.length() > 0) {
mapper.setFilterProvider(new SimpleFilterProvider().addFilter(DYNC_FILTER,
SimpleBeanPropertyFilter.serializeAllExcept(filter.split(","))));
mapper.addMixIn(clazz, DynamicFilter.class);
}
}
public String toJson(Object object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
}
我們之前的 Demo 可以變成:
// Demo
class Demo {
public void main(String args[]) {
CustomerJsonSerializer cjs= new CustomerJsonSerializer();
// 設(shè)置轉(zhuǎn)換 Article 類(lèi)時(shí),只包含 id, name
cjs.filter(Article.class, "id,name", null);
String include = cjs.toJson(new Article());
cjs = new CustomerJsonSerializer();
// 設(shè)置轉(zhuǎn)換 Article 類(lèi)時(shí),過(guò)濾掉 id, name
cjs.filter(Article.class, null, "id,name");
String filter = cjs.toJson(new Article());
System.out.println("include: " + include);
System.out.println("filter: " + filter);
}
}
輸出結(jié)果
include: {id: "", title: ""}
filter: {content:""}
自定義 @JSON 注解
我們需要實(shí)現(xiàn)文章開(kāi)頭的那種效果。這里我自定義了一個(gè)注解,可以加在方法上,這個(gè)注解是用來(lái)攜帶參數(shù)給 CustomerJsonSerializer.filter 方法的,就是某個(gè)類(lèi)的某些字段需要過(guò)濾或者包含。
package diamond.cms.server.json;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JSON {
Class<?> type();
String include() default "";
String filter() default "";
}
實(shí)現(xiàn) Spring MVC 的 HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandler 接口 Spring MVC 用于處理請(qǐng)求返回值 。 看一下這個(gè)接口的定義和描述,接口有兩個(gè)方法supportsReturnType 用來(lái)判斷 處理類(lèi) 是否支持當(dāng)前請(qǐng)求, handleReturnValue 就是具體返回邏輯的實(shí)現(xiàn)。
// Spring MVC 源碼
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
我們平時(shí)使用 @ResponseBody 就是交給 RequestResponseBodyMethodProcessor 這個(gè)類(lèi)處理的
還有我們返回 ModelAndView 的時(shí)候, 是由 ModelAndViewMethodReturnValueHandler 類(lèi)處理的
要實(shí)現(xiàn)文章開(kāi)頭的效果,我實(shí)現(xiàn)了一個(gè) JsonReturnHandler類(lèi),當(dāng)方法有 @JSON 注解的時(shí)候,使用該類(lèi)來(lái)處理返回值。
package diamond.cms.server.json.spring;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import diamond.cms.server.json.CustomerJsonSerializer;
import diamond.cms.server.json.JSON;
public class JsonReturnHandler implements HandlerMethodReturnValueHandler{
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 如果有我們自定義的 JSON 注解 就用我們這個(gè)Handler 來(lái)處理
boolean hasJsonAnno= returnType.getMethodAnnotation(JSON.class) != null;
return hasJsonAnno;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 設(shè)置這個(gè)就是最終的處理類(lèi)了,處理完不再去找下一個(gè)類(lèi)進(jìn)行處理
mavContainer.setRequestHandled(true);
// 獲得注解并執(zhí)行filter方法 最后返回
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Annotation[] annos = returnType.getMethodAnnotations();
CustomerJsonSerializer jsonSerializer = new CustomerJsonSerializer();
Arrays.asList(annos).forEach(a -> {
if (a instanceof JSON) {
JSON json = (JSON) a;
jsonSerializer.filter(json.type(), json.include(), json.filter());
}
});
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
String json = jsonSerializer.toJson(returnValue);
response.getWriter().write(json);
}
}
通過(guò)這些,我們就可以最終實(shí)現(xiàn)以下效果。
class Article {
private String id;
private String title;
private String content;
private Long createTime;
// ... getter/setter
}
@Controller
@RequestMapping("article")
class ArticleController {
@RequestMapping(value = "{id}", method = RequestMethod.GET)
@JSON(type = Article.class, filter="createTime")
public Article get(@PathVariable String id) {
return articleService.get(id);
}
@RequestMapping(value="list", method = RequestMethod.GET)
@JSON(type = Article.class , include="id,title")
public List<Article> findAll() {
return articleService.findAll();
}
}
請(qǐng)求 /article/{articleId}
{
id: "xxxx",
title: "xxxx",
content: "xxxx"
}
請(qǐng)求 article/list
[ {id: "xx", title: ""}, {id: "xx", title: ""}, {id: "xx", title: ""} ... ]
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java調(diào)用openoffice將office系列文檔轉(zhuǎn)換為PDF的示例方法
本篇文章主要介紹了java使用openoffice將office系列文檔轉(zhuǎn)換為PDF的示例方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-11-11
關(guān)于springboot的接口返回值統(tǒng)一標(biāo)準(zhǔn)格式
這篇文章主要介紹了關(guān)于springboot的接口返回值統(tǒng)一標(biāo)準(zhǔn)格式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
IDEA連接MySQL數(shù)據(jù)庫(kù)的4種方法圖文教程
Java實(shí)現(xiàn)求子數(shù)組和的最大值算法示例
SpringBoot整合Redis實(shí)現(xiàn)高并發(fā)數(shù)據(jù)緩存的示例講解

