Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)
这篇文章主要讲 spring mvc 如何动态的去返回 json 数据 在我们做 web 接口开发的时候, 经常会遇到这种场景。
两个请求,返回同一个对象,但是需要的返回字段并不相同。如以下场景
/** * 返回所有名称以及id */ @requestmapping("list") @responsebody public list<article> findallnameandid() { return articleservice.findall(); } /** * 返回所有目录详情 */ @requestmapping("list-detail") @responsebody public list<article> findalldetail() { return articleservice.findall(); }
spring mvc 默认使用转json框架是 jackson。 大家也知道, jackson 可以在实体类内加注解,来指定序列化规则,但是那样比较不灵活,不能实现我们目前想要达到的这种情况。
这篇文章主要讲的就是通过自定义注解,来更加灵活,细粒化控制 json 格式的转换。
最终我们需要实现如下的效果:
@requestmapping(value = "{id}", method = requestmethod.get) // 返回时候不包含 filter 内的 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) // 返回时只包含 include 内的 id, name 字段 @json(type = article.class , include="id,name") public list<article> findall() { return articleservice.findall(); }
jackson 编程式过滤字段
jackson 中, 我们可以在实体类上加上 @jsonfilter 注解,并且通过 objectmapper.setfilterprovider 来进行过滤规则的设置。 这里简单介绍一下 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") // 过滤除了 id,title 以外的所有字段,也就是序列化的时候,只包含 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之外,其他字段都包含进 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); } }
输出结果
filterout:{id: "", title: ""} serializeall:{content:""}
封装json转换
通过上面的代码,我们发现,可以使用 setfilterprovider 来灵活的处理需要过滤的字段。不过上面的方法还有一些缺陷就是,还是要在 原来的 model 上加注解,这里我们使用 objectmapper.addmixin(class<?> type, class<?> mixintype) 方法,这个方法就是讲两个类的注解混合,让第一个参数的类能够拥有第二个参数类的注解。让需要过滤的 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 需要设置规则的class * @param include 转换时包含哪些字段 * @param filter 转换时过滤哪些字段 */ 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(); // 设置转换 article 类时,只包含 id, name cjs.filter(article.class, "id,name", null); string include = cjs.tojson(new article()); cjs = new customerjsonserializer(); // 设置转换 article 类时,过滤掉 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); } }
输出结果
include: {id: "", title: ""} filter: {content:""}
自定义 @json 注解
我们需要实现文章开头的那种效果。这里我自定义了一个注解,可以加在方法上,这个注解是用来携带参数给 customerjsonserializer.filter 方法的,就是某个类的某些字段需要过滤或者包含。
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 ""; }
实现 spring mvc 的 handlermethodreturnvaluehandler
handlermethodreturnvaluehandler 接口 spring mvc 用于处理请求返回值 。 看一下这个接口的定义和描述,接口有两个方法supportsreturntype 用来判断 处理类 是否支持当前请求, handlereturnvalue 就是具体返回逻辑的实现。
// 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; }
我们平时使用 @responsebody 就是交给 requestresponsebodymethodprocessor 这个类处理的
还有我们返回 modelandview 的时候, 是由 modelandviewmethodreturnvaluehandler 类处理的
要实现文章开头的效果,我实现了一个 jsonreturnhandler类,当方法有 @json 注解的时候,使用该类来处理返回值。
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 注解 就用我们这个handler 来处理 boolean hasjsonanno= returntype.getmethodannotation(json.class) != null; return hasjsonanno; } @override public void handlereturnvalue(object returnvalue, methodparameter returntype, modelandviewcontainer mavcontainer, nativewebrequest webrequest) throws exception { // 设置这个就是最终的处理类了,处理完不再去找下一个类进行处理 mavcontainer.setrequesthandled(true); // 获得注解并执行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); } }
通过这些,我们就可以最终实现以下效果。
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(); } }
请求 /article/{articleid}
{ id: "xxxx", title: "xxxx", content: "xxxx" }
请求 article/list
[ {id: "xx", title: ""}, {id: "xx", title: ""}, {id: "xx", title: ""} ... ]
下载地址:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。