Official account: Java Xiaokaxiu, website: Javaxks.com

The author: the building the building Lord, link: www.jianshu.com/p/357b55852…

background

In the recent project, we are coordinating an interface with a third party. We send HTTP requests to the other party and then receive the response from the other party. The codes are all old codes. According to the comment, there was a bug in the Request class written in the SDK of the other party that could not be serialized, so a new Request class was written here. The basic attributes are the same, but the important point is that one attribute is static inner class, and the other two are list attributes, similar to the following:

private List<Order> orders;
private AddRequest.Ticket ticket;
private List<Payment> payments;
Copy the code

AddRequest is the request class that we rewrite ourselves, and the request class in their SDK is MixAddRequest, After we have assembled the request parameters, we use Spring’s BeanUtils copyProperties method to copy the properties from AddRequest to MixAddRequest and send the request. That’s it. It’s supposed to be perfect

The request failed. Nani? The other party said that a necessary field was missing and the parameter verification failed. When I checked the field name, it was a field in the Ticket class. I immediately looked at the code, feeling confident about the old code, thinking that there must be some mistake, or they secretly changed the code and changed the field from optional to mandatory

Sure enough, I found a setting in the code, which should be their problem for sure, and opened a debugging, ready to sentence them to death. Turns out the request sent to them just doesn’t have this field… The method with only one Spring copy property in the middle felt weird at the time

Since there is only one line of code in the middle, there must be something wrong with it. I suspect that the two static inner classes are different, so I wrote my own Demo, and prepared to use the BeanUtils copyProperties method. I wrote two classes and a Main. @data and @toString are lombok plug-in annotations that are used to automatically generate getter and setter methods and ToString methods

@ToString @Data public class CopyTest1 { public String outerName; public CopyTest1.InnerClass innerClass; public List<CopyTest1.InnerClass> clazz; @ToString @Data public static class InnerClass { public String InnerName; }}Copy the code
@ToString @Data public class CopyTest2 { public String outerName; public CopyTest2.InnerClass innerClass; public List<CopyTest2.InnerClass> clazz; @ToString @Data public static class InnerClass { public String InnerName; }}Copy the code
        CopyTest1 test1 = new CopyTest1();
        test1.outerName = "hahaha";
        CopyTest1.InnerClass innerClass = new CopyTest1.InnerClass();
        innerClass.InnerName = "hohoho";
        test1.innerClass = innerClass;

        System.out.println(test1.toString());
        CopyTest2 test2 = new CopyTest2();
        BeanUtils.copyProperties(test1, test2);

        System.out.println(test2.toString());
Copy the code

Test2: @data = null; test2: @data = null; test2: @data = null; The base property (String) is copied over, but the inner class is still null in Test2. This verifies that it is really an internal class problem. I can’t believe my eyes, after all the code running online for so long.

If the inner class has a lot of bean properties or a lot of recursive bean properties, then you can encapsulate a method for recursive copying. I only have one layer here, so I can simply copy it once more

        CopyTest1 test1 = new CopyTest1();
        test1.outerName = "hahaha";
        CopyTest1.InnerClass innerClass = new CopyTest1.InnerClass();
        innerClass.InnerName = "hohoho";
        test1.innerClass = innerClass;

        System.out.println(test1.toString());
        CopyTest2 test2 = new CopyTest2();
        test2.innerClass = new CopyTest2.InnerClass();
        BeanUtils.copyProperties(test1, test2);
        BeanUtils.copyProperties(test1.innerClass, test2.innerClass);

        System.out.println(test2.toString());
Copy the code

Remember that inner class properties also need to have setter methods, otherwise copy will fail, remember I said there were two List properties, why did I mention that? Can you guess what

In fact, the two classes in the list are also rewrite inner classes, they are also different, they were copied over, why? Since Java generics only work at compile time, at run time, the list property is a collection of objects, and after copy, the Orders property of MixAddRequest is actually a collection of Order classes, but not a collection of its own inner classes. Is a collection of AddRequest’s inner class Order, but because the other side parses JSON, there is no error…

conclusion

1.Spring’s BeanUtils CopyProperties method requires getter and setter methods for corresponding properties; \2. If there are inner classes with identical attributes, but not the same inner class, i.e. belonging to separate inner classes, spring will consider the attributes different and will not copy them; \3. Generics only work at compile time. Generics cannot be relied on for runtime restrictions; \4. Finally, spring and Apache copy attribute method source and destination parameters are in opposite positions, so pay attention to package and call.

The last

GetWriteMethod is a JDK method that fetches methods that start with a set, so it doesn’t work without setters.

private static void copyProperties(Object source, Object target, @Nullable Class<? > editable, @Nullable String... ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); Class<? > actualEditable = target.getClass(); if (editable ! = null) { if (! editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List<String> ignoreList = ignoreProperties ! = null ? Arrays.asList(ignoreProperties) : null; PropertyDescriptor[] var7 = targetPds; int var8 = targetPds.length; for(int var9 = 0; var9 < var8; ++var9) { PropertyDescriptor targetPd = var7[var9]; Method writeMethod = targetPd.getWriteMethod(); if (writeMethod ! = null && (ignoreList == null || ! ignoreList.contains(targetPd.getName()))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd ! = null) { Method readMethod = sourcePd.getReadMethod(); if (readMethod ! = null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { try { if (! Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } Object value = readMethod.invoke(source); if (! Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, value); } catch (Throwable var15) { throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15); } } } } } }Copy the code