При ведении бизнеса, чтобы изолировать изменения, мы будемDAO
осведомилсяDO
и предоставляется на передний конецDTO
изолированные. возможно90%
, все они похожи по структуре, но мы не любим писать много многословныхb.setF1(a.getF1())
Такой код, поэтому нам нужно упростить метод копирования объекта.
Большую часть времени используя
Apache
илиSpring``BeanUtils
, сегодня рассмотрим более эффективный способ копирования свойств:BeanCopier
.
1. Предпосылки
1.1 Концепция копирования объекта
Java
В , типы данных делятся на типы значений (базовые типы данных) и ссылочные типы.int
,double
,byte
,boolean
,char
и другие простые типы данных, а ссылочные типы включают сложные типы, такие как классы, интерфейсы и массивы.
Копия объекта делится наМелкая копия (мелкий клон)иглубокая копия (глубокий клон).
- Разница между поверхностным и глубоким копированием
Классификация | мелкая копия | глубокая копия |
---|---|---|
разница | Создайте новый объект, затем скопируйте нестатические поля текущего объекта в новый объект, выполнив копирование поля, если поле имеет тип значения, или копирование поля, если поле является ссылочным типом.ссылка, но не копирование ссылочного объекта. Таким образом, исходный объект и его копия относятся к одному и тому же объекту. | Создайте новый объект, затем скопируйте нестатические поля текущего объекта в новый объект, независимо от того, имеют ли поля значение или ссылочный тип,сделать отдельную копию. Когда вы изменяете содержимое одного из объектов, это не влияет на содержимое другого объекта. |
1.2 Подготовка перед примером
- класс атрибутов исходного объекта
UserDO.class
(В следующем примере исходные объекты используют это)
@Data
public class UserDO {
private int id;
private String userName;
/**
* 以下两个字段用户模拟自定义转换
*/
private LocalDateTime gmtBroth;
private BigDecimal balance;
public UserDO(Integer id, String userName, LocalDateTime gmtBroth, BigDecimal balance) {
this.id = id;
this.userName = userName;
this.gmtBroth = gmtBroth;
this.balance = balance;
}
}
- инструменты для создания данных
DataUtil.class
public class DataUtil {
/**
* 模拟查询出一条数据
* @return
*/
public static UserDO createData() {
return new UserDO(1, "Van", LocalDateTime.now(),new BigDecimal(100L));
}
/**
* 模拟查询出多条数据
* @param num 数量
* @return
*/
public static List<UserDO> createDataList(int num) {
List<UserDO> userDOS = new ArrayList<>();
for (int i = 0; i < num; i++) {
UserDO userDO = new UserDO(i+1, "Van", LocalDateTime.now(),new BigDecimal(100L));
userDOS.add(userDO);
}
return userDOS;
}
}
2. BeanUtils для копирования объектов
Apache
иSpring
обаBeanUtils
Инструменты,Apache
изBeanUtils
Стабильность и эффективность не очень хороши;Spring
изBeanUtils
Он относительно стабилен, и затраты времени не будут значительно увеличиваться из-за большого количества, поэтому он обычно используется.Spring
изBeanUtils
.
2.1 Интерпретация исходного кода
Spring
серединаBeanUtils
, который реализован очень простым способом, заключающимся в простом выполнении простой операции над одноименными свойствами в двух объектах.get/set
, который только проверяет доступность свойства.
Как видите, назначение переменных-членов основано на списке членов целевого объекта и будет пропущено.ignore
и не существует в исходном объекте, поэтому этот метод безопасен и не вызовет ошибок из-за структурных различий между двумя объектами, но должен гарантировать, что две переменные-члены с одинаковыми именами имеют один и тот же тип.
2.2 Пример
@Slf4j
public class BeanUtilsDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
UserDO userDO = DataUtil.createData();
log.info("拷贝前,userDO:{}", userDO);
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(userDO,userDTO);
log.info("拷贝后,userDO:{}", userDO);
}
}
- результат
18:12:11.734 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)
18:12:11.917 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝后,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)
3. BeanCopier копии объекта
BeanCopier
используется в двухbean
Атрибуты копируются между ними.BeanCopier
Поддерживаются два способа:
- Один не использовать
Converter
кстати, только на двоихbean
Копировать между переменными с одинаковым именем и типом атрибута; - Другой представляет
Converter
, вы можете выполнять специальные операции над определенными значениями свойств.
3.1 Основное использование
- полагаться
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
**Примечание.** Эта зависимость не требуется, посколькуSpring
был интегрирован вcglib
, блогер используетorg.springframework.cglib.beans.BeanCopier
.
3.1.1 Имена и типы атрибутов совпадают
- класс атрибута целевого объекта
@Data
public class UserDTO {
private int id;
private String userName;
}
- Методы испытаний
/**
* 属性名称、类型都相同(部分属性不拷贝)
*/
private static void normalCopy() {
// 模拟查询出数据
UserDO userDO = DataUtil.createData();
log.info("拷贝前:userDO:{}", userDO);
// 第一个参数:源对象, 第二个参数:目标对象,第三个参数:是否使用自定义转换器(下面会介绍),下同
BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);
UserDTO userDTO = new UserDTO();
b.copy(userDO, userDTO, null);
log.info("拷贝后:userDTO:{}", userDTO);
}
- Результат: копирование выполнено успешно
18:24:24.080 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:24:24.077, balance=100)
18:24:24.200 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDTO:UserDTO(id=1, userName=Van)
3.1.2 Атрибуты с одинаковыми именами, но разными типами
- класс атрибута целевого объекта
@Data
public class UserEntity {
private Integer id;
private String userName;
}
- Методы испытаний
/**
* 属性名称相同、类型不同
*/
private static void sameNameDifferentType() {
// 模拟查询出数据
UserDO userDO = DataUtil.createData();
log.info("拷贝前:userDO:{}", userDO);
BeanCopier b = BeanCopier.create(UserDO.class, UserEntity.class, false);
UserEntity userEntity = new UserEntity();
b.copy(userDO, userEntity, null);
log.info("拷贝后:userEntity:{}", userEntity);
}
- результат
19:43:31.645 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:43:31.642, balance=100)
19:43:31.748 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userEntity:UserEntity(id=null, userName=Van)
- анализировать
Через журнал можно узнать:UserDO
изint
Типid
нельзя копировать вUserEntity
изInteger
изid
.
Раздел 3.1.3
BeanCopier
Копируются только свойства с тем же именем и типом.
Даже если исходный тип является примитивным типом (
int
,short
иchar
и т. д.), целевой тип — это его тип оболочки (Integer
,Short
иCharacter
д.), или наоборот: ни один из них не будет скопирован.
3.2 Пользовательский конвертер
пройти через3.1.2
Видно, что когда типы атрибутов исходного и целевого классов различны, атрибут не может быть скопирован.В это время мы можем достичьConverter
интерфейс для настройки конвертеров
3.2.1 Подготовка
- класс атрибута целевого объекта
@Data
public class UserDomain {
private Integer id;
private String userName;
/**
* 以下两个字段用户模拟自定义转换
*/
private String gmtBroth;
private String balance;
}
3.2.2 Не используетсяConverter
- Методы испытаний
/**
* 类型不同,不使用Converter
*/
public static void noConverterTest() {
// 模拟查询出数据
UserDO userDO = DataUtil.createData();
log.info("拷贝前:userDO:{}", userDO);
BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, false);
UserDomain userDomain = new UserDomain();
copier.copy(userDO, userDomain, null);
log.info("拷贝后:userDomain:{}", userDomain);
}
- результат
19:49:19.294 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:49:19.290, balance=100)
19:49:19.394 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDomain:UserDomain(id=null, userName=Van, gmtBroth=null, balance=null)
- анализировать
Сравнивая до и после печати журнала, поля с различными типами атрибутовid
,gmtBroth
,balance
Не скопировано.
3.2.3 ИспользованиеConverter
- выполнить
Converter
Интерфейс для настройки преобразования свойств
public class UserConverter implements Converter {
/**
* 时间转换的格式
*/
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 自定义属性转换
* @param value 源对象属性类
* @param target 目标对象里属性对应set方法名,eg.setId
* @param context 目标对象属性类
* @return
*/
@Override
public Object convert(Object value, Class target, Object context) {
if (value instanceof Integer) {
return value;
} else if (value instanceof LocalDateTime) {
LocalDateTime date = (LocalDateTime) value;
return dtf.format(date);
} else if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
return bd.toPlainString();
}
// 更多类型转换请自定义
return value;
}
}
- Методы испытаний
/**
* 类型不同,使用Converter
*/
public static void converterTest() {
// 模拟查询出数据
UserDO userDO = DataUtil.createData();
log.info("拷贝前:userDO:{}", userDO);
BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, true);
UserConverter converter = new UserConverter();
UserDomain userDomain = new UserDomain();
copier.copy(userDO, userDomain, converter);
log.info("拷贝后:userDomain:{}", userDomain);
}
- Результат: все копии
19:51:11.989 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:51:11.985, balance=100)
19:51:12.096 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo - 拷贝后:userDomain:UserDomain(id=1, userName=Van, gmtBroth=2019-11-02 19:51:11, balance=100)
Раздел 3.2.4
- однажды использованный
Converter
,BeanCopier
использовать толькоConverter
определенные правила для копирования атрибутов, поэтому вconvert()
В методе учитываются все свойства. - Однако, используя
Converter
Замедляет копирование объектов.
3.3 BeanCopier
Суммировать
- Когда имена и типы атрибутов исходного класса и целевого класса совпадают, копирование не представляет проблемы.
- Когда свойства исходного объекта и целевого объекта имеют одинаковое имя, но разные типы, свойства с одинаковым именем, но разными типами не будут скопированы. Обратите внимание, что примитивные типы (
int
,short
,char
), а их типы-оболочки рассматриваются здесь как разные типы, поэтому они не копируются. - исходный или целевой класс
setter
Сравниватьgetter
Меньше, копирование не проблема, в это времяsetter
Лишнее, но не сообщит об ошибке. - Исходный и целевой классы имеют одинаковые свойства (оба
getter
оба существуют), но целевой классsetter
не существует, он выкинетNullPointerException
.
4. Сравнение скорости между BeanUtils и BeanCopier
Не много ерунды, я сразу продемонстрирую здесь два инструмента
10000
Длительное сравнение копирования данных
4.1 BeanUtils
- тестовый код
private static void beanUtil() {
List<UserDO> list = DataUtil.createDataList(10000);
long start = System.currentTimeMillis();
List<UserDTO> dtoList = new ArrayList<>();
list.forEach(userDO -> {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(userDO,userDTO);
dtoList.add(userDTO);
});
log.info("BeanUtils cotTime: {}ms", System.currentTimeMillis() - start);
}
- Результат (затраты времени:
232ms
)
20:14:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanUtils cotTime: 232ms
4.2 BeanCopier
- тестовый код
private static void beanCopier() {
// 工具类生成10w条数据
List<UserDO> doList = DataUtil.createDataList(10000);
long start = System.currentTimeMillis();
List<UserDTO> dtoList = new ArrayList<>();
doList.forEach(userDO -> {
BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);
UserDTO userDTO = new UserDTO();
b.copy(userDO, userDTO, null);
dtoList.add(userDTO);
});
log.info("BeanCopier costTime: {}ms", System.currentTimeMillis() - start);
}
- Результат (затраты времени:
116ms
)
20:15:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanCopier costTime: 116ms
4.3 КэшBeanCopier
Экземпляры повышают производительность
BeanCopier
Скорость копирования высокая, а узкое место производительности возникает при созданииBeanCopier
в процессе пример. Итак, поместите созданныйBeanCopier
Экземпляр помещается в кеш и может быть получен непосредственно в следующий раз для повышения производительности.
- тестовый код
private static void beanCopierWithCache() {
List<UserDO> userDOList = DataUtil.createDataList(10000);
long start = System.currentTimeMillis();
List<UserDTO> userDTOS = new ArrayList<>();
userDOList.forEach(userDO -> {
UserDTO userDTO = new UserDTO();
copy(userDO, userDTO);
userDTOS.add(userDTO);
});
log.info("BeanCopier 加缓存后 costTime: {}ms", System.currentTimeMillis() - start);
}
public static void copy(Object srcObj, Object destObj) {
String key = genKey(srcObj.getClass(), destObj.getClass());
BeanCopier copier = null;
if (!BEAN_COPIERS.containsKey(key)) {
copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
BEAN_COPIERS.put(key, copier);
} else {
copier = BEAN_COPIERS.get(key);
}
copier.copy(srcObj, destObj, null);
}
private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
return srcClazz.getName() + destClazz.getName();
}
- Результат (затраты времени:
6ms
)
20:32:31.405 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo - BeanCopier 加缓存后 costTime: 6ms
Пять, резюме и исходный код
Сцены | Занимает много времени (10000 звонков) | принцип |
---|---|---|
BeanUtils | 232ms | отражение |
BeanCopier | 116ms | Изменить байт-код |
BeanCopier (плюс кеш) | 6ms | Изменить байт-код |
рекомендовать:Анализ исходного кода BeanCopier