当前位置: 移动技术网 > IT编程>开发语言>Java > Spring MVC 更灵活的控制 json 返回问题(自定义过滤字段)

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

这篇文章主要讲 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: ""} ... ]

下载地址:

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网