醉卧草庐听风雨

君子藏器于身,待时而动

0%

Spring MVC实现fastjson动态过滤字段

最近在公司编写内部调用接口时经常出现相同的对象在不同的请求下返回不同的json值,虽然Spring MVC已经给出了一些解决方案,但是无奈的是公司的项目所用的Spring的版本还停留在3.X的版本,所以在使用上只能另寻它法,最后通过对Spring MVC的分析得出一种解决方案,故写出来以做记录。

实现方式

实现上主要用了两部分:

  • 自定义注解,注解包含对什么类处理的哪些字段进行json序列化
  • 编写处理注解的HandlerMethodReturnValueHandler,使用fastjson

自定义注解

与@ResponseBody注解类似,这里定义了两个注解CustomJson以及CustomJsons,具体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CustomJson {
/**
* 目标类
*/
Class targetClass();

/**
* 包含字段
*/
String[] includes() default {};

/**
* 排除字段
*/
String[] excludes() default {};
}
1
2
3
4
5
6
7
8
9
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CustomJsons {
/**
* 组合自定义
*/
CustomJson[] value();
}

CustomJson是针对单个类做处理,CustomJsons则是针对返回值中的多个类处理。

自定义json处理器

注解定义之后就需要进行针对处理,这里需要用到Spring MVC的HandlerMethodReturnValueHandler接口,这是Spring MVC使用责任链模式对返回值处理的方式,接口方法只有两个:

  • supportsReturnType()是否支持被处理,责任链的标准形式
  • handleReturnValue()处理返回值,对返回值进行定制处理
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
55
56
57
58
59
60
61
62
63
64
65
66
public class JsonReturnValueHandler implements HandlerMethodReturnValueHandler {
/**
* 使用guava缓存已处理方法
*/
private LoadingCache<MethodParameter, SerializeFilter[]> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<MethodParameter, SerializeFilter[]>() {
@Override
public SerializeFilter[] load(MethodParameter key) {
return getPropertyFilter(key);
}
});

@Override
public boolean supportsReturnType(MethodParameter returnType) {
CustomJson customJson = returnType.getMethodAnnotation(CustomJson.class);
CustomJsons customJsons = returnType.getMethodAnnotation(CustomJsons.class);
return customJson != null || customJsons != null;
}

@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);

SerializeFilter[] filters = cache.get(returnType);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
String json = JSON.toJSONString(returnValue, filters);
// 写入结果
response.getWriter().write(json);
}

/**
* 根据方法获取对应的过滤器
*/
private SerializeFilter[] getPropertyFilter(MethodParameter returnType) {
SerializeFilter[] filters = new SerializeFilter[0];
CustomJson customJson = returnType.getMethodAnnotation(CustomJson.class);
if (customJson != null) {
filters = new SerializeFilter[]{processAnnotation(customJson)};
}
CustomJsons customJsons = returnType.getMethodAnnotation(CustomJsons.class);
if (customJsons == null) {
return filters;
}
CustomJson[] customJsonArray = customJsons.value();
int length = customJsonArray.length;
filters = new SerializeFilter[length];
for (int i = 0; i < length; i++) {
CustomJson json = customJsonArray[i];
filters[i] = processAnnotation(json);
}
return filters;
}

/**
* 提取单个过滤
*/
private SerializeFilter processAnnotation(CustomJson customJson) {
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(customJson.targetClass());
Collections.addAll(filter.getExcludes(), customJson.excludes());
Collections.addAll(filter.getIncludes(), customJson.includes());
return filter;
}
}

使用实例

在实际使用中就比较简单,理论上Spring容器会自动将自定义的JsonReturnValueHandler加入结果处理的责任链中,但是由于项目使用的是3.x的版本,因此需要配置使用

1
2
3
4
5
6
7
8
public class MeetingResourceSpringConfig extends WebMvcConfigurerAdapter {

@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
super.addReturnValueHandlers(returnValueHandlers);
returnValueHandlers.add(new JsonReturnValueHandler());
}
}

配置完毕后我们可以对方法直接使用自定义的注解即可,类似如下

1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "/instant", method = RequestMethod.POST)
@CustomJsons({
@CustomJson(targetClass = InstantMeetingInfo.class, excludes = {"password", "chairPassword", "userList"}),
@CustomJson(targetClass = UserInfo.class, includes = {"userId", "userName", "displayName"})})
public ClientResponse create(@RequestBody InstantMeetingInfo meetingInfo,
HttpServletRequest request) throws Exception {
InstantMeetingInfo meetingInfo;
// 省略相关信息
return ClientResponse.ok(meetingInfo);
}

这样可以实现对InstantMeetingInfo进行灵活的处理,同时也处理了UserInfo。

写在最后

这次改动前后不超过两百行,但是可以极大的改进后续的开发体验,如果没有之前对Spring MVC的学习了解,恐怕也没这次的改进。希望文中的内容能对各位有用。