一个简单易用的 Javabean 拷贝工具

本文介绍了BeanCopyUtils,一个针对Java Bean拷贝的工具库,旨在提供简单、灵活的复制功能。除了基本的深拷贝,它还支持字段重映射、自定义转换器、条件拷贝以及处理Bean的数组和集合。相比于Apache Commons和Spring的BeanUtils,BeanCopyUtils更注重易用性,虽然性能可能不那么极致,但能满足大部分日常需求。作者提供了GitHub链接,邀请社区参与改进和贡献。

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值