首页  编辑  

使用@JsonView过滤DTO字段输出不同内容

Tags: /Java/   Date Created:
在Spring开发当中,我们可能碰到需要对DTO的输出字段进行限制,不同情况下返回的字段内容数据是不一样的,例如权限控制,不同权限下返回的数据多少不相同,又或者在内部处理的时候,字段数量和发送消息给外部的时候(例如Kafka)字段内容是不一样的。对于这种情况,我们有几种解决方法:
  • 定义一个独立的DTO/POJO类,这样存在冗余,不简洁优雅
  • 不定义单独的DTO/POJO类,而是直接使用JSONObject或者Map对数据处理,字段少的时候可以用这个方法,缺点是代码多,且维护麻烦
  • 使用同一个DTO/POJO类,控制不同情况下的JSON序列化输出
如果我们单独定义另外一个DTO实体类,就很冗余了,维护和使用也不方便,使用 @JsonView注解我们可以轻松实现不同业务场景下的JSON输出控制。当然,这种方法有个前提,就是发送给Kafka的消息的字段名称定义必须与DTO/POJO类相同,如果不同的话如果所有视图下都要改变名称,可以使用@JsonProperty("s_field2")来修饰名称,如果在某些特定视图下才改名字,例如正常下是feildA,在Kafka下面名称变成 field_A,则需要使用 @JsonFilter配合FilterProvider来实现,过于复杂,不做演示,请自行百度。

网上有很多使用JsonView的方法,JsonIgnore则完全不输出到JSON当中,复杂用法请自行百度,本文简单介绍一下最常见用法及在Kafka等一些无法自动触发JsonView处理流程的时候的特殊处理方法。JSONView使用方法如下:
  • 定义不同的视图场景,注意,视图下面的 interface 继承关系,如果有继承关系,那么子类就可以包含父接口所有的字段输出!当然你可以把 Hide, Part, More等Interface定义直接放在User类内部:
public interface Views {
    interface Hide { }
    interface Part { }
    interface More extends Part { }
}
  • 实体类 User,对字段使用注解绑定场景:
import com.fasterxml.jackson.annotation.JsonView;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @JsonIgnore
    private String neverOutput;
    private String id;
    @JsonView(Views.Part.class)
    private String name;
    @JsonView(Views.More.class)
    private String email;
    @JsonView(Views.Hide.class)
    @JsonProperty("phone")
    private String phone;
    private String sex;
}
  • 使用方法:
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/part")
    @JsonView(Views.Part.class)
    public User part() {
        return createUser();
    }

    @GetMapping("/more")
    @JsonView(Views.More.class)
    public User more() {
        return createUser();
    }

    @GetMapping("/full")
    public User full() {
        return createUser();
    }

    private User createUser() {
        return new User("0", "John Doe", "john@example.com", "123456789", "F");
    }
}
请注意上面代码中的 @JsonView 注解!该注解用的时AOP方法代理,所以只能用在每个类的入口调用方法上!不能用在类方法内部的二次调用上!就是说如果ClassA.methodA,调用了ClassA.methodB并且 @JsonView 注解在methodB上面,那么 @JsonView注解在 methodB上是不起作用的,但是如果直接调用 methodB那么 @JsonView 注解就能生效。

切记!

以上代码,输出效果:
Controller结果
/user/part{     "name": "John Doe" }
/user/more{     "name": "John Doe",     "email": "john@example.com" }
/user/full{     "id": "0",     "name": "John Doe",     "email": "john@example.com",     "phone": "123456789",     "sex": "F" }
如果要在Kafka中使用,需要一些额外的处理,因为Kafka消息发送本身不会触发 @JsonView的 相关逻辑。
例如:
原来的业务逻辑代码是: 
kafkaTemplate.send(topic, dto);
需要改成以下的模式:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
String json = objectMapper.writeWithView(Views.Part.class).writeValueAsString(dto);
kafkaTemplate.send(topic, JSON.parseObject(json));