这篇文章主要讲 Spring MVC 如何动态的去返回 Json 数据 在我们做 Web 接口开发的时候, 经常会遇到这种场景。
两个请求,返回同一个对象,但是需要的返回字段并不相同。如以下场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/** * 返回所有名称以及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 格式的转换。
最终我们需要实现如下的效果:
1
2
3
4
5
6
7
8
9
10
11
12
|
@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 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@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); } } |
输出结果
1
2
|
filterOut:{id: "" , title: "" } serializeAll:{content: "" } |
封装json转换
通过上面的代码,我们发现,可以使用 setFilterProvider 来灵活的处理需要过滤的字段。不过上面的方法还有一些缺陷就是,还是要在 原来的 model 上加注解,这里我们使用 ObjectMapper.addMixIn(Class<?> type, Class<?> mixinType) 方法,这个方法就是讲两个类的注解混合,让第一个参数的类能够拥有第二个参数类的注解。让需要过滤的 model 和 @JsonFilter 注解解除耦合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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 可以变成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 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); } } |
输出结果
1
2
|
include: {id: "" , title: "" } filter: {content: "" } |
自定义 @JSON 注解
我们需要实现文章开头的那种效果。这里我自定义了一个注解,可以加在方法上,这个注解是用来携带参数给 CustomerJsonSerializer.filter 方法的,就是某个类的某些字段需要过滤或者包含。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 就是具体返回逻辑的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 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 注解的时候,使用该类来处理返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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); } } |
通过这些,我们就可以最终实现以下效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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}
1
2
3
4
5
|
{ id: "xxxx" , title: "xxxx" , content: "xxxx" } |
请求 article/list
1
|
[ {id: "xx" , title: "" }, {id: "xx" , title: "" }, {id: "xx" , title: "" } ... ] |
下载地址:cms-admin-end.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://my.oschina.net/u/2328699/blog/836727