1 background

In the previous column “Not recommended using the property copy tool”, it is recommended to define the transformation classes and methods directly and use the IDEA plug-in to automatically populate the GET/set functions.

The main reasons not to recommend it are:

Some of the property copying tools are a little bit poor some of the property copying tools have “bugs” and there are some pitfalls with using the property copying tools (we’ll see in the examples below)

Example 2

First of all, the company encountered a real case of Commons package BeanUtils with poor property copy performance, and then the colleague changed to Spring BeanUtils performance is much better, interested in you can use the performance testing framework or benchmarking framework to compare, here will not compare.

Let’s see what happens with Spring’s BeanUtils property copy:

`importlombok.Data; ` `importjava.util.List; ` `@Data`
`public class A {`
 `privateString name; ` `privateList<Integer> ids; ` `} ` `@Data`
`public class B {`
 `privateString name; ` `privateList<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 above example, a cast exception occurs.

The second object of type B is still of type Integer.

If you print it without converting it to a string, no error will be reported.

Using CGlib without defining a Converter runs into a similar problem:

`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 ()) {// Type conversion exception' 'system.out.println (each); ` `} ` `} ` ` `}Copy the code

Again, the problem only comes to light at run time.

So let’s look at mapstruct:

`import org.mapstruct.Mapper; ` `import org.mapstruct.factory.Mappers; ` `@Mapper` `public interface Converter {` `Converter INSTANCE = Mappers.getMapper(Converter.class); ` `B aToB(A car); ` `}` `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

You can successfully convert List in A to List in B.

Let’s take a look at the compiler generated 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, the compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)" ' ') ' '@Component' 'public 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 conversion is automatically done for us, and we may not be aware that the types are inconsistent.

If we add A String number attribute in class A and A Long number attribute in class B, using mapStruect we will 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) {' 'b.setnumber (long.parselong (car.getNumber())); ` `}` `b.setIds( integerListToStringList( car.getIds() ) ); ` `return b; ` ` `}Copy the code

By default, cglib does not map the number attribute, and the number in B is null.

If you define the converter manually, use the IDEA plug-in (e.g. GenerateO2O) to automatically convert:

`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 can be seen very clearly at the coding stage:

3 conclusion

Since Java generics are actually compile-time checks, and generics are erased after compilation, both List and List are of type List at runtime and can be assigned normally. This results in less noticeable errors at compile time when many attribute mapping tools are used.

Mapstruct has a custom annotation processor, which can read the generic types on both sides of the map during compilation, and then map. But this kind of mapping can also be scary. Sometimes we define the wrong type because of carelessness or other reasons, which automatically helps us to transform, with many side effects.

A simple comparison of the performance of various attribute mapping tools was made earlier. The results are as follows:

If possible, suggest A custom conversion class. Use the IDEA plug-in to automatically fill in, which is also very efficient. If any property type in A or B does not match, or even if A property is deleted, an error will be reported at compile time.

The most complete one of the factory interview questions

Follow the public number: IT brother, get a variety of Java materials (the above interview questions, just a small part of oh)