前段时间,因工作需要,要做一个WEB层,放在展示层(HTML,JS,移动端)和服务层(DubboX)中间,使用JSON暴露数据给展示层。经过一番调研,决定使用SpringMVC4.1.6+Jackson2.5.1来搭建此项目。
常规搭建略去不提,因为用的是SpringMVC4.1以上的版本,因此决定在返回JSON数据的时候,使用新注解@JsonView来支持同一个VO显示不同的JSON视图,其目的主要是隐藏VO中的敏感字段,节约流量,减少不必要字段输出,方便展示层(JS)处理。框架搭建过程很顺利,但在返回封装好的泛型JSON类的时候,出现了问题,即泛型属性对象在指定JsonView的视图之后渲染失败。经过对Jackson源码的初步浏览和跟踪,终于找到问题所在,现将解决过程总结在此。
问题描述:
JsonView使用分为三步:
1:定义view相关接口。
2:在VO的属性上使用JsonView注解,此为定义阶段,此步可以和第一步放在一起。
3:在Action的方法上使用JsonView注解,此为使用阶段。
首先,
定义VO如下,同时在VO里面定义了两个接口(IOnlyIdView ,IOnlyIdView)用于显示不同JSON视图:
- public final class Depot {
- public interface IOnlyIdView {}
- public interface IOnlyNameView{}
-
- @JsonView(IOnlyIdView.class)
- private long id;
-
- @JsonView(IOnlyNameView.class)
- private String name;
-
- private long num;
-
- public long getId() {
- return id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public long getNum() {
- return num;
- }
- public void setNum(long num) {
- this.num = num;
- }
- }
然后,定义Action的方法如下:
- @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
- @JsonView(Depot.IOnlyNameView.class)
- public Depot getDepotInfo(@PathVariable long id) {
- Depot depot = new Depot();
- depot.setId(100);
- depot.setName("北京001仓");
- depot.setNum(888);
- return depot;
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
则将项目运行之后结果打印(说明Depot的JsonView视图生效):
{"name":"北京001仓"}
将该方法getDepotInfo返回值改为List<Depot> ,方法注解不变,方法体改为
- Depot depot = new Depot();
- depot.setId(100);
- depot.setName("北京001仓");
- depot.setNum(888);
- List<Depot> ret = new ArrayList<>();
- ret.add(depot);
- return ret;
则将项目运行之后结果打印(说明放在集合容器中,JsonView视图生效):
[{"name":"北京001仓"}]
将该方法返回值改为JsonResult<Depot>,方法注解不变,方法体改为:
- depot.setId(100);
- depot.setName("北京001仓");
- depot.setNum(888);
- List<Depot> ret = new ArrayList<>();
- ret.add(depot);
- return new JsonResult(ret,true,"ok");
则将项目运行之后结果打印:
{}
JsonResult
是我们架构设计中统一返回的类。所有VO都要放到该对象中返回。
JsonResult的定义如下:
- public class JsonResult<T> implements Serializable {
- private static final long serialVersionUID = 3863559687276427577L;
-
- private boolean success = true;
-
- public String message;
-
- private String secure;
-
- private T data;
-
- public JsonResult() {
- }
- public JsonResult(T data, Boolean success, String message) {
-
- this.data = data;
- this.success = success;
- this.message = message;
- }
- public boolean isSuccess() {
- return success;
- }
- public void setSuccess(boolean success) {
- this.success = success;
- }
- public T getData() {
- return data;
- }
- public void setData(T data) {
- this.data = data;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public static <T> JsonResult<T> newResult() {
- return new JsonResult<T>();
- }
- public String getSecure() {
- return secure;
- }
- public void setSecure(String secure) {
- this.secure = secure;
- }
- }
问题解决:
SpringMVC 将VO本身以及和Action上的JsonView 注解内容传递给Jackson,由Jackson在渲染视图时进行处理。经过跟踪,Jackson在解析不同的java对象的时候,使用不同的Serializer来把对象转换为json串。比如当Action返回值为Depot对象的时候,Jackson使用基础的BeanSerializer来处理,而返回值为List<Depot>的时候,则使用IndexedListSerializer来解析。
我们也可以通过在类上加注解 @JsonSerialize(using=CustomSerializer.class)来自定义某个类的Serializer。本例中,经过初步的跟踪,发现BeanSerializer在处理JsonResult的时候,如果JsonResult的属性不含JsonView视图接口,则不去过滤该属性所包含的所有视图。所以,要为JsonResult属性也定义一个视图注解,并且,视图注解接口要被泛型vo里面的属性视图接口所继承,才能实现整体视图的正常渲染和过滤。有点拗口,看解决范例吧。呵呵
为JsonResult定义一个视图接口,如下:
- public class GeneralViews {
-
-
-
- public interface IErrorView{};
-
-
-
-
- public interface INormalView extends IErrorView{} ;
- }
则JsonResult类改造为:
- public class JsonResult<T> implements Serializable {
- private static final long serialVersionUID = 3863559687276427577L;
-
- @JsonView(GeneralViews.IErrorView.class)
- private boolean success = true;
-
- @JsonView(GeneralViews.IErrorView.class)
- public String message;
-
- @JsonView(GeneralViews.INormalView.class)
- private String secure;
-
- @JsonView(GeneralViews.INormalView.class)
- private T data;
-
- public JsonResult() {
- }
-
- public JsonResult(T data, Boolean success, String message) {
-
- this.data = data;
- this.success = success;
- this.message = message;
- }
-
- public boolean isSuccess() {
- return success;
- }
-
- public void setSuccess(boolean success) {
- this.success = success;
- }
-
- public T getData() {
- return data;
- }
-
- public void setData(T data) {
- this.data = data;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-
- public static <T> JsonResult<T> newResult() {
- return new JsonResult<T>();
- }
-
- public String getSecure() {
- return secure;
- }
-
- public void setSecure(String secure) {
- this.secure = secure;
- }
- }
最后,所用的业务VO改为:
- public final class Depot {
- public interface IOnlyIdView extends GeneralViews.INormalView{}
- public interface IOnlyNameView extends GeneralViews.INormalView{}
-
-
- @JsonView(IOnlyIdView.class)
- private long id;
-
-
- @JsonView(IOnlyNameView.class)
- private String name;
-
-
- private long num;
-
-
- public long getId() {
- return id;
- }
-
-
- public void setId(long id) {
- this.id = id;
- }
-
-
- public String getName() {
- return name;
- }
-
-
- public void setName(String name) {
- this.name = name;
- }
-
-
- public long getNum() {
- return num;
- }
-
-
- public void setNum(long num) {
- this.num = num;
- }
- }
再次运行Action,则结果为:
{"success":true,"message":"ok","secure":null,"data":[{"name":"北京001仓"}]}
最终,通过这样的方式,成功实现了JsonView视图在泛型属性对象上的传递。