在Java项目开发中,DTO深度克隆是数据传递时的常见操作,Jackson作为主流的JSON序列化工具,常被用来实现对象克隆。但有时候我们需要忽略特定类型的数组属性,比如包含用户隐私信息的数组、仅用于当前请求的临时数组等,这时候就需要针对性的配置策略。

基于@JsonIgnore注解的基础忽略策略
如果某个数组属性是固定不需要克隆的,最直接的方式是使用Jackson提供的@JsonIgnore注解,在序列化阶段直接跳过该属性。
首先定义需要克隆的DTO类,假设我们有一个用户DTO,其中包含需要忽略的敏感权限数组:
import com.fasterxml.jackson.annotation.JsonIgnore;
public class UserDTO {
private String userId;
private String userName;
// 需要忽略的权限数组,标记为@JsonIgnore
@JsonIgnore
private String[] permissionArray;
// 不需要忽略的普通数组
private String[] tagArray;
// 省略getter和setter方法
}使用Jackson进行克隆的代码如下:
import com.fasterxml.jackson.databind.ObjectMapper;
public class CloneTest {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
UserDTO original = new UserDTO();
original.setUserId("1001");
original.setUserName("张三");
original.setPermissionArray(new String[]{"admin", "write"});
original.setTagArray(new String[]{"vip", "active"});
// 序列化再反序列化实现深度克隆
UserDTO cloned = objectMapper.readValue(
objectMapper.writeValueAsString(original),
UserDTO.class
);
System.out.println("原始对象权限数组:" + original.getPermissionArray());
System.out.println("克隆对象权限数组:" + cloned.getPermissionArray()); // 输出null,已被忽略
System.out.println("克隆对象标签数组:" + cloned.getTagArray()[0]); // 输出vip,正常克隆
}
}这种方式的优点是简单直接,缺点是不够灵活,只能静态忽略固定属性,无法根据数组类型动态判断是否忽略。
使用@JsonFilter实现动态类型过滤
如果需要根据数组的类型动态决定是否忽略,比如忽略所有Integer[]类型的数组,或者忽略特定泛型类型的数组,可以使用@JsonFilter结合过滤器实现。
首先给DTO类添加过滤器注解:
import com.fasterxml.jackson.annotation.JsonFilter;
@JsonFilter("dtoFilter")
public class OrderDTO {
private String orderId;
private String orderName;
private Integer[] priceArray; // 需要忽略的Integer数组
private String[] productArray; // 不需要忽略的String数组
// 省略getter和setter方法
}然后自定义过滤器逻辑,判断属性类型是否为需要忽略的数组类型:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Array;
public class DynamicFilterTest {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 定义过滤器,忽略Integer[]类型的数组属性
SimpleBeanPropertyFilter filter = new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
// 获取当前属性的类型
Class<?> propertyType = writer.getType().getRawClass();
// 判断是否为Integer数组类型,如果是则跳过
if (propertyType.isArray() && propertyType.getComponentType() == Integer.class) {
return;
}
// 其他属性正常序列化
writer.serializeAsField(pojo, jgen, provider);
}
};
// 注册过滤器
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("dtoFilter", filter);
objectMapper.setFilterProvider(filterProvider);
OrderDTO original = new OrderDTO();
original.setOrderId("ORD001");
original.setOrderName("测试订单");
original.setPriceArray(new Integer[]{100, 200});
original.setProductArray(new String[]{"商品A", "商品B"});
// 执行克隆
OrderDTO cloned = objectMapper.readValue(
objectMapper.writeValueAsString(original),
OrderDTO.class
);
System.out.println("克隆对象价格数组:" + cloned.getPriceArray()); // 输出null,已被忽略
System.out.println("克隆对象商品数组:" + cloned.getProductArray()[0]); // 输出商品A,正常克隆
}
}这种方式可以灵活根据属性类型动态过滤,适合需要按类型批量忽略数组的场景。
自定义序列化器实现精准控制
如果需要更复杂的判断逻辑,比如忽略长度超过10的数组,或者忽略包含特定元素的数组,可以自定义序列化器,在序列化阶段手动处理数组属性。
首先自定义针对数组类型的序列化器:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
// 自定义数组序列化器,忽略所有String[]类型的数组
public class IgnoreStringArraySerializer extends JsonSerializer<String[]> {
@Override
public void serialize(String[] value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 直接不输出任何内容,实现忽略效果
// 也可以在这里添加自定义判断逻辑,比如数组长度大于5才忽略
// if (value != null && value.length > 5) return;
}
}然后在DTO属性上使用@JsonSerialize指定自定义序列化器:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class ProductDTO {
private String productId;
private String productName;
// 使用自定义序列化器,忽略该String数组
@JsonSerialize(using = IgnoreStringArraySerializer.class)
private String[] imageUrlArray;
private Integer[] stockArray;
// 省略getter和setter方法
}克隆测试代码:
import com.fasterxml.jackson.databind.ObjectMapper;
public class CustomSerializerTest {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
ProductDTO original = new ProductDTO();
original.setProductId("P001");
original.setProductName("测试商品");
original.setImageUrlArray(new String[]{"url1", "url2"});
original.setStockArray(new Integer[]{50, 100});
ProductDTO cloned = objectMapper.readValue(
objectMapper.writeValueAsString(original),
ProductDTO.class
);
System.out.println("克隆对象图片数组:" + cloned.getImageUrlArray()); // 输出null,已被忽略
System.out.println("克隆对象库存数组:" + cloned.getStockArray()[0]); // 输出50,正常克隆
}
}这种方式的灵活性最高,可以编写任意复杂的忽略逻辑,适合特殊业务场景的定制需求。
不同策略的适用场景对比
为了帮助大家选择合适的策略,以下是三种方式的对比:
| 策略类型 | 适用场景 | 灵活性 | 实现复杂度 |
|---|---|---|---|
| @JsonIgnore注解 | 固定属性静态忽略 | 低 | 低 |
| @JsonFilter动态过滤 | 按类型、按条件批量过滤 | 中 | 中 |
| 自定义序列化器 | 复杂自定义判断逻辑 | 高 | 高 |
实际开发中可以根据需求选择,简单的静态忽略用注解即可,复杂动态场景优先选择过滤器或自定义序列化器,保证克隆逻辑既满足需求又易于维护。