Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)

http://www.jb51.net/article/105633.htm

Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)

作者:DiamondFsd 字体:[增加 减小] 类型:转载 时间:2017-02-15 我要评论

本篇文章主要介绍了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: "" } ... ]

下载地址:cms-admin-end_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

原文链接:https://my.oschina.net/u/2328699/blog/836727


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值