http://www.jb51.net/article/105633.htm
Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)
这篇文章主要讲 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:
""
} ... ]
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
原文链接:https://my.oschina.net/u/2328699/blog/836727