background
In our actual project development process, we often need to copy the attributes of two different object instances, so that we can carry out subsequent operations based on the attribute information of the source object without changing the attribute information of the source object, such as DTO data transfer object and data object DO. We need to copy the attributes of the DO object to DTO, but the object format is different, so we need to Write mapping code to convert attribute values in an object from one type to another.
The most original way of this transformation is to manually write a large number of get/set code, which is of course what we are unwilling to do in the development process, because it is really cumbersome. In order to solve this problem, some convenient class libraries have been created, such as Apache's BeanUtils,spring's BeanUtils, Dozer,Orika and other copy tools. This article mainly introduces Apache's BeanUtils and Spring's BeanUtils. Other frameworks will be introduced later
Object Copy
Before introducing two kinds of bean utils, let's add some basic knowledge. These two tools are essentially object copying tools, and object copying is divided into deep copy and shallow copy, which will be explained in detail below.
What is shallow copy and deep copy
In Java, in addition to the basic data type, there is the reference data type of the instance object of the class. When the "=" sign is used for assignment, the basic data type is actually the copied value, but for the object, the assignment is only the reference of the object, passing the reference of the original object to the past, and they actually point to the The same object for.
The difference between shallow copy and deep copy is based on this. If only the basic data type is copied when copying this object, and the reference data type is just the transfer of reference, but no real creation of a new object, it is considered as shallow copy. On the contrary, when copying the reference data type, a new object is created and its member variables are copied, which is considered as a deep copy.
In short:
Shallow copy: value transfer for basic data types and reference transfer for reference data types. This is a shallow copy
Deep copy: pass values to basic data types, create a new object to reference data types, and copy its contents. This is a deep copy.
BeanUtils
I've briefly talked about some knowledge of object copying. Let's take a look at two kinds of bean utils
Bean utils of apache
Let's start with a very simple example of bean utils
public class PersonSource {
private Integer id; private String username; private String password; private Integer age; // getters/setters omiited
}
public class PersonDest {
private Integer id; private String username; private Integer age; // getters/setters omiited
}
public class TestApacheBeanUtils {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { //The following is for individual testing only PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21); PersonDest personDest = new PersonDest(); BeanUtils.copyProperties(personDest,personSource); System.out.println("persondest: "+personDest); }
}
persondest: PersonDest{id=1, username='pjmike', age=21}
As can be seen from the above example, object copying is very simple. The most commonly used method of BeanUtils is:
//Copy values from the source object to the target object
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
By default, using org.apache.commons.beanautils.beanautils to copy complex objects is a reference, which is a shallow copy
However, due to the poor copy performance of BeanUtils under Apache, it is not recommended to use it. Moreover, it is also clearly pointed out in the Alibaba Java development specification plug-in:
Ali check | avoid using Apache beans to copy attributes
Commons beatutils adds a lot of tests to the object copy, including type conversion, and even the accessibility of the class to which the object belongs. It is quite complex, which also makes its poor performance. The specific implementation code is as follows:
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException { // Validate existence of the specified beans if (dest == null) { throw new IllegalArgumentException ("No destination bean specified"); } if (orig == null) { throw new IllegalArgumentException("No origin bean specified"); } if (log.isDebugEnabled()) { log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")"); } // Copy the properties, converting as necessary if (orig instanceof DynaBean) { final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties(); for (DynaProperty origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); // Need to check isReadable() for WrapDynaBean // (see Jira issue# BEANUTILS-61) if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) { final Object value = ((DynaBean) orig).get(name); copyProperty(dest, name, value); } } } else if (orig instanceof Map) { @SuppressWarnings("unchecked") final // Map properties are always of type <String, Object> Map<String, Object> propMap = (Map<String, Object>) orig; for (final Map.Entry<String, Object> entry : propMap.entrySet()) { final String name = entry.getKey(); if (getPropertyUtils().isWriteable(dest, name)) { copyProperty(dest, name, entry.getValue()); } } } else /* if (orig is a standard JavaBean) */ { final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig); for (PropertyDescriptor origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); if ("class".equals(name)) { continue; // No point in trying to set an object's class } if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) { try { final Object value = getPropertyUtils().getSimpleProperty(orig, name); copyProperty(dest, name, value); } catch (final NoSuchMethodException e) { // Should not happen } } } } }
Bean utils of spring
Use spring's BeanUtils for object copying:
public class TestSpringBeanUtils {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { //The following is for individual testing only PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21); PersonDest personDest = new PersonDest(); BeanUtils.copyProperties(personSource,personDest); System.out.println("persondest: "+personDest); }
}
In spring, BeanUtils are also copied using the copyProperties method, but its implementation is very simple, that is, to simply get/set the properties with the same name in two objects, and only check the accessibility of the properties. The specific implementation is as follows:
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); for (PropertyDescriptor targetPd : targetPds) { 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 ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } } } } } }
It can be seen that the member variable assignment is based on the member list of the target object, and ignore s and does not exist in the source object. Therefore, this method is safe and will not cause errors due to the structural differences between the two objects. However, it must be ensured that the two member variables with the same name have the same type
Summary
In the above brief analysis, two kinds of BeanUtils are not recommended because of the poor performance of BeanUtils under Apache. You can use Spring's BeanUtils, or use other copy frameworks, such as cglib BeanCopier, Orika based on javassist, etc. These are also excellent class libraries, which are worth trying, and some people have evaluated these Bean mapping tools. For specific analysis, please refer to: often See Bean mapping tool analysis and evaluation and Orika introduction
Reference material
Object copy in Java development
Detailed description of deep copy and shallow copy of Java: https://segmentfault.com/a/11...
Bean mapping tool: https://pjmike.github.io/2018...