Skip to content

面试速答(先看这里)

**一句话结论:**Apache的BeanUtils是非常好用的工具,可以帮我们实现对象之间的属性拷贝,但是因为他的设计上太力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。

60秒标准回答:

Apache的BeanUtils是非常好用的工具,可以帮我们实现对象之间的属性拷贝,但是因为他的设计上太力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重

市面上有很多类似的工具类,比较常用的有

1、Spring BeanUtils

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**1、Spring BeanUtils
  • **要点2:**3、Apache BeanUtils
  • **要点3:**阿里巴巴Java开发手册中提到禁止使用Apache BeanUtils,主要是因为性能问题。
  • **要点4:**综上,我们基本可以得出结论,在性能方面,Spring BeanUtils和Cglib BeanCopier表现比较不错,而Apache PropertyUtils、Apache BeanUtils以及Dozer则表现的很不好。
  • **要点5:**所以,如果考虑性能情况的话,建议大家不要选择Apache PropertyUtils、Apache BeanUtils以及Dozer等工具类。

**记忆锚点:**Apache → BeanUtils → PropertyUtils → BeanCopier → Spring → Cglib

关键取舍:

  • Apache的BeanUtils是非常好用的工具,可以帮我们实现对象之间的属性拷贝,但是因为他的设计上太力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。
  • 市面上有很多类似的工具类,比较常用的有 1、Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 阿里巴巴Java开发手册中提到禁止使用Apache BeanUtils,主要是因为性能问题。
  • 所以,如果考虑性能情况的话,建议大家不要选择Apache PropertyUtils、Apache BeanUtils以及Dozer等工具类。

易错提醒:

  • 所以,如果考虑性能情况的话,建议大家不要选择Apache PropertyUtils、Apache BeanUtils以及Dozer等工具类。

追问准备:

  • 围绕「Apache」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「BeanUtils」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「PropertyUtils」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

Apache的BeanUtils是非常好用的工具,可以帮我们实现对象之间的属性拷贝,但是因为他的设计上太力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。

市面上有很多类似的工具类,比较常用的有

1、Spring BeanUtils

2、Cglib BeanCopier

3、Apache BeanUtils

4、Apache PropertyUtils

5、Dozer

阿里巴巴Java开发手册中提到禁止使用Apache BeanUtils,主要是因为性能问题。

实验如下 ​

首先定义一个PersonDO类:

java
public class PersonDO {
    private Integer id;
    private String name;
    private Integer age;
    private Date birthday;
    //省略setter/getter
}

在定义一个PersonDTO:

java
public class PersonDTO {
    private String name;
    private Integer age;
    private Date birthday;
}

然后进行测试类的编写,使用Spring BeanUtils进行属性拷贝:

java
private void mappingBySpringBeanUtils(PersonDO personDO, int times) {
    StopWatch stopwatch = new StopWatch();
    stopwatch.start();

    for (int i = 0; i < times; i++) {
        PersonDTO personDTO = new PersonDTO();
        org.springframework.beans.BeanUtils.copyProperties(personDO, personDTO);
    }

    stopwatch.stop();
    System.out.println("mappingBySpringBeanUtils cost :" + stopwatch.getTotalTimeMillis());
}

其中的StopWatch用于记录代码执行时间,方便进行对比。

再使用Cglib BeanCopier进行属性拷贝:

java
private void mappingByCglibBeanCopier(PersonDO personDO, int times) {
    StopWatch stopwatch = new StopWatch();

    stopwatch.start();

    for (int i = 0; i < times; i++) {
        PersonDTO personDTO = new PersonDTO();
        BeanCopier copier = BeanCopier.create(PersonDO.class, PersonDTO.class, false);
        copier.copy(personDO, personDTO, null);
    }

    stopwatch.stop();

    System.out.println("mappingByCglibBeanCopier cost :" + stopwatch.getTotalTimeMillis());
}

使用Apache BeanUtils进行属性拷贝:

java
private void mappingByApacheBeanUtils(PersonDO personDO, int times)
    throws InvocationTargetException, IllegalAccessException {
    StopWatch stopwatch = new StopWatch();
    stopwatch.start();
    for (int i = 0; i < times; i++) {
        PersonDTO personDTO = new PersonDTO();
        BeanUtils.copyProperties(personDTO, personDO);
    }
    stopwatch.stop();
    System.out.println("mappingByApacheBeanUtils cost :" + stopwatch.getTotalTimeMillis());
}

使用Apache PropertyUtils进行属性拷贝:

java
private void mappingByApachePropertyUtils(PersonDO personDO, int times)
    throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
    StopWatch stopwatch = new StopWatch();
    stopwatch.start();
    for (int i = 0; i < times; i++) {
        PersonDTO personDTO = new PersonDTO();
        PropertyUtils.copyProperties(personDTO, personDO);
    }
    stopwatch.stop();
    System.out.println("mappingByApachePropertyUtils cost :" + stopwatch.getTotalTimeMillis());
}

然后执行以下代码:

java
public static void main(String[] args)
    throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
    PersonDO personDO = new PersonDO();

    personDO.setName("Hollis");
    personDO.setAge(26);
    personDO.setBirthday(new Date());
    personDO.setId(1);

    MapperTest mapperTest = new MapperTest();

    mapperTest.mappingBySpringBeanUtils(personDO, 100);
    mapperTest.mappingBySpringBeanUtils(personDO, 1000);
    mapperTest.mappingBySpringBeanUtils(personDO, 10000);
    mapperTest.mappingBySpringBeanUtils(personDO, 100000);
    mapperTest.mappingBySpringBeanUtils(personDO, 1000000);
    mapperTest.mappingByCglibBeanCopier(personDO, 100);
    mapperTest.mappingByCglibBeanCopier(personDO, 1000);
    mapperTest.mappingByCglibBeanCopier(personDO, 10000);
    mapperTest.mappingByCglibBeanCopier(personDO, 100000);
    mapperTest.mappingByCglibBeanCopier(personDO, 1000000);
    mapperTest.mappingByApachePropertyUtils(personDO, 100);
    mapperTest.mappingByApachePropertyUtils(personDO, 1000);
    mapperTest.mappingByApachePropertyUtils(personDO, 10000);
    mapperTest.mappingByApachePropertyUtils(personDO, 100000);
    mapperTest.mappingByApachePropertyUtils(personDO, 1000000);
    mapperTest.mappingByApacheBeanUtils(personDO, 100);
    mapperTest.mappingByApacheBeanUtils(personDO, 1000);
    mapperTest.mappingByApacheBeanUtils(personDO, 10000);
    mapperTest.mappingByApacheBeanUtils(personDO, 100000);
    mapperTest.mappingByApacheBeanUtils(personDO, 1000000);
}

得到结果如下:

工具类执行1000次耗时执行10000次耗时执行100000次耗时执行1000000次耗时
Spring BeanUtils5ms10ms45ms169ms
Cglib BeanCopier4ms18ms45ms91ms
Apache PropertyUtils60ms265ms1444ms11492ms
Apache BeanUtils138ms816ms4154ms36938ms
Dozer566ms2254ms11136ms102965ms

画了一张折线图更方便大家进行对比

15957453583511.jpg

综上,我们基本可以得出结论,在性能方面,Spring BeanUtils和Cglib BeanCopier表现比较不错,而Apache PropertyUtils、Apache BeanUtils以及Dozer则表现的很不好。

所以,如果考虑性能情况的话,建议大家不要选择Apache PropertyUtils、Apache BeanUtils以及Dozer等工具类。