предисловие
Как показано на рисунке, во время разработки, будь то трехуровневая архитектура в стиле MVC или архитектура, управляемая доменом DDD. Всегда будут различные требования к преобразованию между DTO, DO, PO и VO. Поэтому мы часто определяем два слоя полей Объекта как непротиворечивые, что удобно для работы Ассемблера антикоррозийного слоя. Однако некоторые сложные отображения также будут встречаться в практических нуждах. Так как же нам выбрать подходящую структуру BeanCopy в зависимости от сценария?
Этот блог в основном организует структуру класса BeanCopy.
- Производительность каждого кадра
- как выбрать
Фреймворк BeanCopy
Кроме HardCopy (рукописный набор получается) Обычно используемые параметры BeanCopy следующие:
Я даю отчет о производительности напрямуюСравнение производительности фреймворка BeanCopyЗаключительная карта:
выбор кадра
Я в основном продвигаю две категории
- Аннотированный картограф на основе MapStruct*Selma MapStruct и Selma реализованы на основе процессоров аннотаций.Я напишу отдельное введение в блог о процессорах аннотаций и добавлю сюда ссылки. MapStruct — это процессор аннотаций Java, основанный на JSR 269. В процессе использования вам нужно только запустить mvn compile после завершения настройки, и вы обнаружите, что в целевой папке генерируется класс реализации интерфейса картографа. Когда вы откроете класс реализации, вы обнаружите, что файл с методами get и set, соответствующими полям один к одному, автоматически генерируется в классе сущностей. Например, я определяю интерфейс MapStruct (аннотация @Mapper поддерживает метод внедрения IOC, который я здесь не использовал)
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
/**
* source -> destination
*
* @param car
* @return
*/
@Mappings({
@Mapping(source = "middleName", target = "middle"),
@Mapping(target = "email", ignore = true)
})
PersonDestination sourceToDestination(PersonSource car);
}
После компиляции вы обнаружите, что у цели есть дополнительный класс реализации
По той же причине давайте посмотрим на Сельму
@Mapper
public interface SelmaPersonMapper {
SelmaPersonMapper INSTANCE = Selma.mapper(SelmaPersonMapper.class);
/**
* source -> destination
*
* @param car
* @return
* @Maps(withCustomFields = {
* @Field({"middleName", "middle"})
* }, withIgnoreFields = {"email"})
*/
@Maps(withCustomFields = {
@Field({"middleName", "middle"})
}, withIgnoreFields = {"email"})
PersonDestination sourceToDestination(PersonSource car);
}
Итак, Selma и MapStruct очень похожи, принцип тот же, аннотации и использование почти одинаковые.Я думаю, причина, по которой MapStruct лучше, в том, что сообщество более активно, интеграция со SpringBoot лучше, а сгенерированный код более стандартизирован, лаконичен и красив.
- Статический класс инструментов на основе Orika и JMapper (производительность бульдозера слишком низкая и отбрасывается) Просто инкапсулируйте следующий код.
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
Если это взаимное преобразование списка
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
List<PersonSource> sourceList = Lists.newArrayList(source);
List<PersonDestination> personDestinations = mapper.mapAsList(sourceList, PersonDestination.class);
Если имя поля сопоставлено
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(PersonSource.class, PersonDestination.class)
.field("firstName", "givenName")
.field("lastName", "sirName")
.byDefault()
.register();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination destination = mapper.map(source, PersonDestination.class);
эксперимент
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSourceComputer {
private String name;
private BigDecimal price;
}
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSourceSon {
private String sonName;
private List<PersonSourceComputer> computers;
}
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSource {
private String firstName;
private String middleName;
private String lastName;
private String email;
List<PersonSourceSon> son;
}
public class BeanCopyTest {
private static final Logger logger = LoggerFactory.getLogger(BeanCopyTest.class);
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
//static {
// mapperFactory.classMap(PersonSource.class, PersonDestination.class).byDefault().register();
//}
public static void main(String[] args) {
for (int i = 1; i < 11; i++) {
beanCopyTest(i);
}
}
private static void beanCopyTest(int i) {
PersonSource source = initAndGetPersonSource();
Stopwatch stopwatch = Stopwatch.createStarted();
// MapStruct
PersonDestination destination = PersonMapper.INSTANCE.sourceToDestination(source);
System.out.println(destination);
stopwatch.stop();
logger.info("第" + i + "次" + "MapStruct cost:" + stopwatch.toString());
// Selma
stopwatch = Stopwatch.createStarted();
PersonDestination selmaDestination = SelmaPersonMapper.INSTANCE.sourceToDestination(source);
System.out.println(selmaDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "Selma cost:" + stopwatch.toString());
// BeanUtils
stopwatch = Stopwatch.createStarted();
PersonDestination bUtilsDestination = new PersonDestination();
BeanUtils.copyProperties(source, bUtilsDestination);
System.out.println(bUtilsDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "BeanUtils cost:" + stopwatch.toString());
// BeanCopier
stopwatch = Stopwatch.createStarted();
BeanCopier beanCopier = BeanCopier.create(PersonSource.class, PersonDestination.class, false);
PersonDestination bcDestination = new PersonDestination();
beanCopier.copy(source, bcDestination, null);
System.out.println(bcDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "BeanCopier cost:" + stopwatch.toString());
// Orika
stopwatch = Stopwatch.createStarted();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
System.out.println(orikaDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "Orika cost:" + stopwatch.toString());
}
private static PersonSource initAndGetPersonSource() {
PersonSource source = new PersonSource();
// set some field values
source.setFirstName("firstName");
source.setMiddleName("middleName");
source.setLastName("lastName");
source.setEmail("email");
source.setSon(Lists.newArrayList(new PersonSourceSon(
"sonName", Lists.newArrayList(new PersonSourceComputer("macBook", BigDecimal.valueOf(15000)))
)));
return source;
}
}
17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
17:56:30.035 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Selma cost:2.727 ms
17:56:30.095 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanUtils cost:59.65 ms
17:56:30.139 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanCopier cost:43.52 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Selma cost:36.72 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanUtils cost:68.76 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanCopier cost:62.75 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Selma cost:71.12 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanUtils cost:81.64 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanCopier cost:68.01 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Selma cost:37.97 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanUtils cost:124.3 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanCopier cost:124.9 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Selma cost:50.03 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanUtils cost:75.00 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanCopier cost:50.83 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Selma cost:61.26 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanUtils cost:118.6 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanCopier cost:102.7 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Selma cost:52.06 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanUtils cost:86.51 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanCopier cost:101.3 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Selma cost:35.56 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanUtils cost:98.93 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanCopier cost:69.25 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Selma cost:31.90 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanUtils cost:96.19 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanCopier cost:77.15 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
PersonDestination(firstName=firstName, middle=middleName, lastName=lastName, email=null, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
Результаты экспериментов:
1. Независимо от того, какой фреймворк используется в эксперименте, абсолютное значение разницы в производительности не слишком велико (не первый запуск). 2. Отдельные фреймворки ссылаются на PersonSourceSon после копирования, а отдельные на PersonDestinationSon, указывая на то, что разные фреймворки реализуют разные схемы глубокого и поверхностного копирования. 3. При наличии требований к сопоставлению имен полей, игнорированию, форматированию и т. д. разные платформы поддерживают разные.
Суммировать
1. В ежедневной разработке требования BeanCopy не более чем три
- Простейшая копия с теми же полями
- Сложная копия (например, разные имена полей, игнорирование требований, требования к форматированию)
- Копировать с бизнес-логикой
Так что по вышеуказанным трем пунктам я думаю
- Первый в основном прост и эффективен.Я рекомендую использовать класс инструментов Orika напрямую.Реализация очень проста, а клиентский код очень мал.В принципе, просто закинуть в него исходный и целевой тип, что обеспечивает глубокое копирование и имеет производительность выше, чем у Дозера.Ожидание старых продуктов, и копирование между коллекциями тоже отлично. Например, BeanUtils и BeanCopier явно не так хороши, как Orika во многих сценариях, и будут различные проблемы, которые подвергались критике.
- Второе предложение — использовать мощный фреймворк MapStruct, преимущество которого в том, что он генерирует более интуитивно понятный и удобный для отладки код. Он также поддерживает множество мощных аннотаций, которые могут легко выполнять сопоставление полей между несколькими уровнями, игнорирование полей, форматирование даты, форматирование суммы и т. д. Также есть такие функции, как наследование, повторное использование и комбинирование шаблонов сопоставления. Существует также естественная поддержка внедрения Spring, интеграции SpringBoot и т. д. На данный момент, по сравнению с отображением xml в стиле Dozer, аннотации больше соответствуют современному программированию.
2. Компромиссы производительности
С помощью теста производительности мы можем обнаружить, что абсолютное значение производительности одной копии вышеуказанных фреймворков очень низкое после того, как оно было запущено один раз (отдельные фреймворки в основном основаны на трудоемком принципе кэширования и оптимизации горячего кода jvm). асм и др.) дольше). Таким образом, рассмотрение компромиссов производительности в основном основано на сценариях объема и системы. Если это особенно преувеличенный параллелизм, или действительно система достигла узкого места, необходимо оптимизировать библиотеку классов для повышения производительности. Относительная разница между такими низкими абсолютными значениями имеет смысл, потому что разница между одиночными временами составляет микросекунды, и если нет увеличения произведения количества, разницей в производительности можно пренебречь. Как правило, большинство компаний не предъявляют этого требования, и нет необходимости стремиться к такой экстремальной производительности, поэтому считается, что библиотека классов не только обеспечивает «высокую производительность» (абсолютное значение), но и делает вас очень удовлетворены в других аспектах.