返回正文
Jackson View Extension
一个基于 Jackson 的 Starter,提供比 @JsonView 更灵活的 JSON 序列化字段控制能力,支持细粒度的字段过滤和敏感数据脱敏。
📖 项目介绍
在 Spring Boot 项目中,Controller 层返回的数据往往需要根据不同场景进行定制化处理。传统的 @JsonView 注解虽然能实现视图控制,但在复杂场景下显得不够灵活。
本项目提供了一套更强大的解决方案:
- 细粒度字段控制:支持按类型、按路径精确控制序列化字段
- 嵌套路径支持:通过 . 分隔符定位嵌套对象中的特定字段
- 敏感数据脱敏:内置脱敏处理器,支持自定义扩展
- 优先级机制:include 优先级高于 exclude,规则清晰明确
- 零侵入设计:无需修改现有实体类,仅需在 Controller 方法上添加注解
- 多版本支持:兼容 Spring Boot 1.x、2.x 和 3.x
🎯 项目背景
在公司项目升级改造过程中,我们发现 Controller 层返回全量数据存在以下问题:
- 性能影响:大量无用字段增加网络传输开销
- 安全隐患:敏感信息可能被意外暴露
- 维护成本:通过定义 VO 实体进行改造过于繁琐
为了解决这些问题,我们开发了这个项目作为中间过渡方案,让您可以:
- 快速控制返回字段,无需创建大量 VO 类
- 平滑过渡到标准 VO 模式,降低改造风险
- 灵活配置不同接口的返回内容
🔌 推荐搭配:FastConvert IDEA 插件
在后续改造成 VO 实体的过程中,推荐使用我们开发的 IDEA 插件 FastConvert,它可以帮助您:
- 一键生成对象转换代码
- 智能匹配字段映射关系
- 相比 BeanUtils、MapStruct 更加便捷高效
🚀 快速开始
添加依赖
根据您的 Spring Boot 版本选择合适的 Starter:
Spring Boot 3.x(JDK 17+)
Maven:
xml
<groupId>io.github.vennarshulytz</groupId>
<artifactId>json-view-ext-spring-boot3-starter</artifactId>
<version>1.0.0</version>
</dependency>1
2
3
4
2
3
4
启用功能
在 Spring Boot 启动类上添加 @EnableJsonViewExt 注解:
java
@SpringBootApplication
@EnableJsonViewExt
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
📚 使用说明
实体类定义
以下示例中使用的实体类:
java
@Data
public class Department {
private String name;
private Employee manager1;
private List<Employee> managerList1;
}
@Data
public class Employee {
private String id;
private String name;
private String number;
private Address address1;
private List<Address> addressList1;
}
@Data
public class Address {
private String id;
private String province;
private String city;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
基础使用
场景一:Include - 仅包含指定字段
只返回 Employee 类的 name 和 number 字段:
java
@GetMapping("/employee")
@JsonViewExt(
include = {
@JsonFilterExt(clazz = Employee.class, props = {"name", "number"})
}
)
public Employee getEmployee() {
return employeeService.findById("1");
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
不加注解时的返回结果:
json
{
"id": "9ccb342f-18f2-46c1-872e-892c0c3f6d14",
"name": "张三",
"number": "EMP001",
"address1": {
"id": "a7ab2748-68ec-4e52-97c3-a241317ef347",
"province": "广东省",
"city": "深圳市"
},
"addressList1": [
{
"id": "c3b735e8-c8c7-4aab-b8e0-fddab5bcbe2e",
"province": "广东省",
"city": "广州市"
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
加注解后的返回结果:
json
{
"name": "张三",
"number": "EMP001"
}1
2
3
4
2
3
4
场景二:Exclude - 排除指定字段
返回 Address 类时排除 id 字段:
java
@GetMapping("/address")
@JsonViewExt(
exclude = {
@JsonFilterExt(clazz = Address.class, props = {"id"})
}
)
public Address getAddress() {
return addressService.findById("1");
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
不加注解时的返回结果:
json
{
"id": "a7ab2748-68ec-4e52-97c3-a241317ef347",
"province": "广东省",
"city": "深圳市"
}1
2
3
4
5
2
3
4
5
加注解后的返回结果:
json
{
"province": "广东省",
"city": "深圳市"
}1
2
3
4
2
3
4
场景三:多类型组合控制
同时控制多个类型的序列化字段:
java
@GetMapping("/department")
@JsonViewExt(
include = {
@JsonFilterExt(clazz = Department.class, props = {"name", "manager1"}),
@JsonFilterExt(clazz = Employee.class, props = {"name", "number"})
},
exclude = {
@JsonFilterExt(clazz = Address.class, props = {"id"})
}
)
public Department getDepartment() {
return departmentService.findById("1");
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
不加注解时的返回结果:
json
{
"name": "技术部",
"manager1": {
"id": "emp-001",
"name": "张三",
"number": "EMP001",
"address1": {
"id": "addr-001",
"province": "广东省",
"city": "深圳市"
},
"addressList1": []
},
"managerList1": []
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
加注解后的返回结果:
json
{
"name": "技术部",
"manager1": {
"name": "张三",
"number": "EMP001"
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
进阶使用
场景一:Field 路径匹配 - 针对特定位置的对象
使用 field 属性可以精确控制特定路径下对象的序列化:
java
@GetMapping("/department/detail")
@JsonViewExt(
include = {
@JsonFilterExt(clazz = Department.class, props = {"manager1", "managerList1"}),
// 针对 managerList1 中的 Employee,只返回 name 和 address1
@JsonFilterExt(clazz = Employee.class, field = "managerList1", props = {"name", "address1"}),
// 针对其他位置的 Employee(如 manager1),返回 name 和 number
@JsonFilterExt(clazz = Employee.class, props = {"name", "number"}),
// 针对 managerList1.address1 路径下的 Address,只返回 province
@JsonFilterExt(clazz = Address.class, field = "managerList1.address1", props = {"province"}),
// 其他位置的 Address,返回 province 和 city
@JsonFilterExt(clazz = Address.class, props = {"province", "city"})
}
)
public Department getDepartmentDetail() {
return departmentService.findById("1");
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
返回结果说明:
manager1(Employee 类型):返回name、numbermanagerList1中的 Employee:返回name、address1managerList1[*].address1:只返回province- 其他 Address:返回
province、city
场景二:敏感数据脱敏
使用 sensitives 属性对敏感字段进行脱敏处理:
java
@GetMapping("/employee/info")
@JsonViewExt(
include = {
@JsonFilterExt(
clazz = Employee.class,
props = {"name", "number", "address1"},
sensitives = {
@Sensitive(type = IdCardType.class, props = {"number"})
}
)
}
)
public Employee getEmployeeInfo() {
return employeeService.findById("1");
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
不加注解时的返回结果:
json
{
"id": "emp-001",
"name": "张三",
"number": "440301199001011234",
"address1": {
"id": "addr-001",
"province": "广东省",
"city": "深圳市"
},
"addressList1": []
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
加注解后的返回结果:
json
{
"name": "张三",
"number": "440301********1234",
"address1": {
"id": "addr-001",
"province": "广东省",
"city": "深圳市"
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
场景三:同类型不同路径的差异化脱敏
java
@GetMapping("/department/sensitive")
@JsonViewExt(
include = {
@JsonFilterExt(clazz = Department.class, props = {"manager1", "managerList1"}),
// managerList1 中的员工编号需要脱敏
@JsonFilterExt(
clazz = Employee.class,
field = "managerList1",
props = {"name", "number"},
sensitives = {@Sensitive(type = IdCardType.class, props = {"number"})}
),
// manager1 的员工编号不需要脱敏
@JsonFilterExt(clazz = Employee.class, props = {"name", "number"})
}
)
public Department getDepartmentSensitive() {
return departmentService.findById("1");
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
返回结果:
json
{
"manager1": {
"name": "张三",
"number": "440301199001011234"
},
"managerList1": [
{
"name": "李四",
"number": "440301********5678"
},
{
"name": "王五",
"number": "440301********9012"
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
场景四:完整的复杂示例
java
@GetMapping("/findById")
@JsonViewExt(
include = {
@JsonFilterExt(clazz = Department.class, props = {"manager1", "managerList1"}),
@JsonFilterExt(
clazz = Employee.class,
field = "managerList1",
props = {"name", "number", "address1"},
sensitives = {@Sensitive(type = IdCardType.class, props = {"number"})}
),
@JsonFilterExt(clazz = Employee.class, props = {"number", "address1", "addressList1"}),
@JsonFilterExt(clazz = Address.class, field = "managerList1.address1", props = {"id", "province"})
},
exclude = {
@JsonFilterExt(clazz = Address.class, props = {"id"})
}
)
public Department findById(@RequestParam("id") String id) {
return departmentService.findById(id);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
不加注解时的返回结果:
json
{
"name": "name",
"manager1": {
"id": "9ccb342f-18f2-46c1-872e-892c0c3f6d14",
"name": "name1",
"number": "123456789012345678",
"address1": {
"id": "a7ab2748-68ec-4e52-97c3-a241317ef347",
"province": "province1",
"city": "city1"
},
"addressList1": [
{"id": "c3b735e8-c8c7-4aab-b8e0-fddab5bcbe2e", "province": "province2", "city": "city2"},
{"id": "e8f30e87-53b4-47f8-ba5a-1b49b6bca3a2", "province": "province3", "city": "city3"}
]
},
"managerList1": [
{
"id": "19cdb3dc-3877-4a45-a296-c71fae143040",
"name": "name2",
"number": "123456789012345679",
"address1": {
"id": "d724f421-a78a-494e-87d1-5f2e1622e3ba",
"province": "province4",
"city": "city4"
},
"addressList1": [
{"id": "f9d53824-9f98-4eec-ba84-708113c804d8", "province": "province5", "city": "city5"},
{"id": "22f0997d-9bc5-4415-bdd1-63d302c629ce", "province": "province6", "city": "city6"}
]
},
{
"id": "e9e99b53-14cf-42d6-a70b-1595517a6671",
"name": "name3",
"number": "123456789012345670",
"address1": {
"id": "8fe894dd-d61d-4821-ae13-5bddd54839dc",
"province": "province7",
"city": "city7"
},
"addressList1": [
{"id": "4dbb776a-22d0-4222-9c0b-83d7391a3eec", "province": "province8", "city": "city8"},
{"id": "83c545ce-c7fd-4339-8c62-4ae3ceda32ec", "province": "province9", "city": "city9"}
]
}
]
}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
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
加注解后的返回结果:
json
{
"manager1": {
"number": "123456789012345678",
"address1": {
"province": "province1",
"city": "city1"
},
"addressList1": [
{"province": "province2", "city": "city2"},
{"province": "province3", "city": "city3"}
]
},
"managerList1": [
{
"name": "name2",
"number": "123456********5679",
"address1": {
"id": "d724f421-a78a-494e-87d1-5f2e1622e3ba",
"province": "province4"
}
},
{
"name": "name3",
"number": "123456********5670",
"address1": {
"id": "8fe894dd-d61d-4821-ae13-5bddd54839dc",
"province": "province7"
}
}
]
}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
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
自定义脱敏处理器
项目内置了常用的脱敏处理器,如 IdCardType(身份证)、PhoneType(手机号)等。如需自定义脱敏规则,只需实现 SensitiveType 接口:
java
/**
* 自定义银行卡号脱敏处理器
*/
public class BankCardType implements SensitiveType {
@Override
public String desensitize(String value) {
if (value == null || value.length() < 8) {
return value;
}
// 保留前4位和后4位,中间用*替换
return value.substring(0, 4)
+ "****"
+ "****"
+ "****"
+ value.substring(value.length() - 4);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用自定义脱敏处理器:
java
@JsonViewExt(
include = {
@JsonFilterExt(
clazz = Account.class,
props = {"bankCard", "balance"},
sensitives = {@Sensitive(type = BankCardType.class, props = {"bankCard"})}
)
}
)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
📋 规则说明
| 规则 | 说明 |
|---|---|
| include 优先级高于 exclude | 当同时配置 include 和 exclude 时,include 规则优先生效 |
| field 精确匹配优先 | 带 field 的规则优先于不带 field 的通用规则 |
| 后定义覆盖先定义 | 多个相同 clazz 和 field 的规则,后定义的生效 |
嵌套路径使用 . 分隔 | 如 managerList1.address1 表示 managerList1 下的 address1 属性 |
V 0.12.10 |
基于 MIT Licensed版权所有 © 2009- 2026 CMONO.NET
本站访客数
--次 本站总访问量
--人次 