The property copy tool is not recommended. It is recommended to define transformation classes and methods using the IDEA plugin to automatically populate get/set functions.
The main reasons for not recommending it are:
- Some property copy tools have poor performance
- Some property copy tools are “buggy”
- There are some pitfalls to using the property copy tool (which will be covered in a later example)
The sample
First of all, the company has encountered a real case of Commons package BeanUtils with poor property copy performance, and then the colleague switched to Spring BeanUtils with much better performance, you can use the performance testing framework or benchmark framework to compare, not here.
Let’s look at the problem with Spring’s BeanUtils property copy:
import lombok.Data; import java.util.List; @Datapublic class A { private String name; private List<Integer> ids; }Copy the code
@Datapublic class B { private String name; private List<String> ids; }Copy the code
import org.springframework.beans.BeanUtils; import java.util.Arrays; public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3)); B second = new B(); BeanUtils.copyProperties(first, second); for(String each: second.getids ()) {system.out.println (each); }}}Copy the code
When you run the example above, a cast exception occurs.
As you can see from the break point, ids of type B second objects are still of type Integer after the property is copied:
If you print it without converting it to a string, no error will be reported.
A similar problem occurs when using CGlib without defining Converter:
import org.easymock.cglib.beans.BeanCopier; import java.util.Arrays; public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3)); B second = new B(); final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false); beanCopier.copy(first,second,null); for(String each: second.getids ()) {system.out.println (each); }}}Copy the code
Again, problems are exposed at run time.
Now let’s look at mapstruct:
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapperpublic interface Converter { Converter INSTANCE = Mappers.getMapper(Converter.class); B aToB(A car); }Copy the code
import java.util.Arrays; public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3)); B second = Converter.INSTANCE.aToB(first); for(String each: second.getids ()) {// Normal system.out.println (each); }}}Copy the code
List<Integer> in A can be successfully converted to List<String> in B.
Let’s take a look at the compiled Converter implementation class:
import java.util.ArrayList; import java.util.List; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value ="org.mapstruct.ap.MappingProcessor", comments = Version: 1.3.1.Final, Compiler: JavAC, Environment: Java 1.8.0_202 (Oracle Corporation))@Componentpublic class ConverterImpl implements Converter { @Override public B aToB(A car) { if ( car == null ) { return null; } B b = new B(); b.setName( car.getName() ); b.setIds( integerListToStringList( car.getIds() ) ); return b; } protected List<String> integerListToStringList(List<Integer> list) { if ( list == null ) { return null; } List<String> list1 = new ArrayList<String>( list.size() ); for ( Integer integer : list ) { list1.add( String.valueOf( integer)); }return list1; }}Copy the code
The automatic does the conversion for us, and we may not be aware that the types are inconsistent.
If we add A String Number attribute to class A and A Long number attribute to class B, use mapStruect to report.numberFormatException when number is set to A non-number type.
@Override public B aToB(A car) { if ( car == null ) { return null; } B b = new B(); b.setName( car.getName() ); if( car.getNumber() ! = null) {setNumber(long.parselong (car.getNumber())); } b.setIds(integerListToStringList( car.getIds() ) ); return b; }Copy the code
With cglib, the number attribute is not mapped by default, and the number in B is null.
If you define the converter manually, use IDEA plug-ins (such as generateO2O) for automatic conversion:
public final class A2BConverter { public static B from(A first) { B b = new B(); b.setName(first.getName()); b.setIds(first.getIds()); return b; }}Copy the code
This problem can be found very clearly in the coding phase:
conclusion
List<Integer> and List<String> are both List types at runtime and can be assigned normally. This makes compile-time errors less obvious when using many property mapping tools.
Mapstruct is a custom annotation processor that reads generic types on both sides of the map at compile time to map. But this mapping can also be scary, and sometimes we accidentally define the wrong type, which automatically helps us do the conversion, with a lot of side effects.
A simple comparison of the performance of various attribute mapping tools was performed earlier, and the results are as follows:
Therefore, be careful with the use of attribute conversion tools. If possible, it is recommended to customize the conversion class and use IDEA plug-in to fill it automatically, which is also very efficient. Any attribute type in A or B does not match, or even delete an attribute, and errors can be reported during compilation.