Javabean 拷贝工具有不少,常见的有 Apache Commons 和 Spring 的 BeanUtils,以及 CGlib 性能强劲的 BeanCopier。它们可能足够好,尤其是 BeanCopier,甚至可以提供媲美直接调用 getters/setters 的性能…
但是…它们提供的功能要么不够用,要么不易用。于是我从自身需求出发,完成了下面这个 Javabean 拷贝工具,名字姑且就叫做 BeanCopyUtils。由于对我来说,性能的优先级暂时还不是很高,所以它可能并不适合看重性能的使用者。
GitHub 地址在这里,欢迎大家体验使用,如果发现 Bug 或是有什么好的改进想法,欢迎提 PR。
以下是 README 的中文版本。
特性
- 深拷贝.
- 支持按条件拷贝指定的字段。
- 支持不同名或不同类型字段间的拷贝。
- 支持拷贝 Bean 的数组或者集合(Collection)。
使用
基础
例如有两个类,一个作为源,另一个作为目标。
public class Source {
private long id = 1;
private int[] numbers = { 1, 2, 3 };
public String getName() {
return "source";
}
}
public class Target {
private long id;
private int[] numbers;
private String name;
}
想要拷贝一个源对象到目标对象,只需要使用:
Target target = BeanCopyUtils.copy(new Source(), Target.class);
或是,
Target target = new Target();
BeanCopyUtils.copy(new Source(), target);
如你所见,getters/setters 并不是必需的,然而,一个合法的 Javabean 应该具备它们。
高级
字段重映射
默认情况下,两个 Javabean 字段间是通过字段名来进行对应的。对于那些名称不同的字段,可以使用 @AliasFor
注解来指明被注解的字段是另一个 Javabean 中某个字段的别名,如下所示:
public class Source {
private long id;
}
public class Target {
@AliasFor("id")
private long userId;
}
转换器
通过 @Converter
注解来提供一个自定义的转换器类,自定义的转换器类必须实现 Converter
接口。例如:
public class TimestampToDateConverter implements Converter<Long, Date> {
@Override
public Date convert(Long timestamp) {
return new Date(timestamp);
}
}
public class Source {
private long timestamp;
}
public class Target {
@AliasFor("timestamp")
@Converter(TimestampToDateConverter.class)
private Date date;
}
条件拷贝
通过 @CopyIgnore
来指明当(或除了)某种条件满足时,被注解的字段应该在拷贝时被忽略。此外,你也可以指定是否要忽略值为 null
或空的字段。
空字符串、数组、集合或 0 都被认为是一个空值,包括
null
。
来看看下面的类:
public class Bean {
public static final String COPY_WITH_ID = "copyWithId";
public static final String COPY_WITHOUT_NAME = "copyWithoutName";
@CopyIgnore(except = COPY_WITH_ID)
private int id;
@CopyIgnore(when = COPY_WITHOUT_NAME, policy = IgnorePolicy.EMPTY)
private String name;
}
很明显,不难去理解上面这些注解做了些什么。字段 id
总会在拷贝时被忽略,除非当你想要 COPY_WITH_ID
, 而字段 name
只有当你想要 COPY_WITHOUT_NAME
或者它的值为空的时候才会被忽略。
两个字符串常量用来作为条件,可在拷贝的时候以数组的形式指定。例如:
// `id` 和 `name` 都会被拷贝
BeanCopyUtils.copy(new Bean(), Bean.class, new String[] { Bean.COPY_WITH_ID });
// `id` 和 `name` 都会被忽略
BeanCopyUtils.copy(new Bean(), Bean.class, new String[] { Bean.COPY_WITHOUT_NAME });
注意,由于条件都是简单的字符串常量,所以你可能需要保证它们的唯一性。
你也可以决定是否要忽略所有值为 null
或空的字段:
BeanCopyUtils.copy(new Bean(), Bean.class, IgnorePolicy.EMPTY);
然而,注解上的配置有更高的优先级,因此你可以针对某个字段进行一些额外配置。
集合
一般来说,对于 Collection
类型的目标字段,并不需要额外的配置。
如果目标字段的类型是接口,比如 List
,那么被拷贝的值会和源字段类型相同。如果这不是你想要的,你可以直接将目标字段声明为一个实现类, 比如 ArrayList
。鉴于使用接口类是一个好的做法,你可以通过 @ToCollection
来指定一个实现类。
public class Bean {
@ToCollection(ArrayList.class)
private List<Integer> numbers;
private ArrayList<String> names;
}
性能
性能其实并没有怎么测试,但应该能接受。
希望您能对此提供一些建议,甚至是亲自帮助改进!欢迎 PR。