Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Spring’s beanutils.copyproperties () method is often used in our projects to copyProperties between objects instead of tedious get() and set() methods. However, if you do not pay attention to this method, there will be unintended problems. Today we will talk about common pits and analyze the cause of the problem from a source point of view.

Common “pits”

1. If you do not declare the get and set methods of attributes, attributes will fail to be copied

The Lombok plugin’s @data annotation is often used in real-world projects to omit the GET and set methods

public class SourceBean { private int id; private String name; public SourceBean() { } public SourceBean(int id, String name) { this.id = id; this.name = name; }}Copy the code
public class TargetBean { private int id; private String name; public TargetBean() { } public TargetBean(int id, String name) { this.id = id; this.name = name; }}Copy the code

The target attribute was not copied successfully.

2. Copy is a shallow copy (reference to a copy object)
@Data
public class SourceBean {
    private int id;
    private String name;
    private Map<String, String> content;
}
Copy the code
@Data
public class TargetBean {
    private int id;
    private String name;
    private Map<String, String> content;
}
Copy the code
public class Main { public static void main(String[] args) { SourceBean sourceBean = new SourceBean(); Map<String, String> sourceContent = new HashMap<>(); sourceContent.put("name", "Bob"); sourceBean.setContent(sourceContent); TargetBean targetBean = new TargetBean(); BeanUtils.copyProperties(sourceBean, targetBean); System.out.println(targetbean.getContent ().get("name"))); // Only modify the sourceBean content sourcebean.getContent ().put("name", "Peter"); System.out.println(targetbean.getContent ().get("name"))); }} // The console outputs the result Bob PeterCopy the code
3. Different versions of Spring treat property generics differently

After Spring5.3, matching properties in source objects and target objects follows the generic type information, meaning that when copying properties, it determines whether the generic type of the properties is consistent. If not, the copy of the properties is ignored.

@Data
public class SourceBean {
    private List<Integer> ids;
}
Copy the code
@Data
public class TargetBean {
    private List<String> ids;
}
Copy the code

5.3.8 Runtime: IDS of targetBean is still null

5.2.10 Runtime: IDS of targetBean copies IDS of sourceBean

4. The actual type of the member attribute copied by TargetBean may differ from the declaration

Occurs when the property is generic and can cause a runtime error. Once again the Bean above, execute the following code and the runtime will throw an exception

public static void main(String[] args) {
    SourceBean sourceBean = new SourceBean();
    sourceBean.setIds(Arrays.asList(1, 2, 3));
    TargetBean targetBean = new TargetBean();
    BeanUtils.copyProperties(sourceBean, targetBean);
    String firstId = targetBean.getIds().get(0);
}
Copy the code

Beanutils.copyproperties () source code parsing

Looking at the source code for Spring’s beanutils.copyProperties () method, it’s easy to see why.

Source code core part (5.3.8 version) :

private static void copyProperties(Object source, Object target, @Nullable Class<? > editable, @Nullable String... ignoreProperties) throws BeansException { // ... Other Class <? > actualEditable = target.getClass(); / /... Other // Get all property descriptors of the target class (PropertyDescriptor, PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); / /... Other // Iterate over all properties, assign to each property for (PropertyDescriptor targetPd: TargetPds) {// Set Method writeMethod = targetpd.getwritemethod (); if (writeMethod ! = null && (ignoreList == null || ! Contains (targetPd. GetName ()))) {// gets the property description of the targetBean property corresponding to the sourceBean PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd ! ReadMethod = sourcepd.getreadMethod (); readMethod = sourcepd.getreadMethod (); if(readMethod ! = null) { ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0); // Check whether the attribute type is consistent, Including generics are consistent Boolean isAssignable = (sourceResolvableType. HasUnresolvableGenerics () | | targetResolvableType.hasUnresolvableGenerics() ? ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : targetResolvableType.isAssignableFrom(sourceResolvableType)); if (isAssignable) { try { if (! Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); Object value = readMethod.invoke(source); if (! Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); // Invoke (target, value); // Invoke (target, value); } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source  to target", ex); } } } } } }Copy the code

From the source can be seen:

  1. The properties of the sourceBean and targetBean are copied using Method in reflection, so there is no copy between properties if the Bean does not declare set and GET methods for properties.

  2. Method’s invoke Method simply sets the value obtained by the Get Method of the sourceBean to the set Method of the targetBean, so there is no deep copy involved, just a copy of the attribute reference.

  3. Above the source code, there is a step: attribute generic whether consistent judgment.

boolean isAssignable =
      (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
            targetResolvableType.isAssignableFrom(sourceResolvableType));
Copy the code

Prior to Spring 5.3.0, there was no such step.

ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()))
Copy the code
  1. Prior to 5.3.0, there was no generics judgment, so assignment by reflection could result in inconsistency between the actual type and the declared type.

conclusion

Beanutils.copyproperties () is more suitable for copying between simple beans. If the Bean properties are complex, it is easy to cause problems with shallow copying. Moreover, the implementation process of copyProperties method is not simple, compared with the direct use of GET and set method assignment, its performance overhead is higher.