ⓘ
在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 { }
}
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));