“This is the 10th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

preface

Hello everyone, MY name is Kano, a full stack full stick engineer!

I believe you must have encountered such trouble in daily development, A object and B object properties are different, need to copy the property value of A object to B object properties. The most common thing we do is getter/setter. Today let’s play a different game, with the proper use of reflection and annotations, one line of code to solve the property copy problem! We hope that the examples in this chapter will give you a deeper understanding of reflection and annotations. For the basics of reflection annotations, see the “Learn Java Again” column.

This article is based on the reflection, annotation, functional interface knowledge of the previous optimization.

This article has been added to the “Learn Java Again” and “Problem Solving” columns!

  • This column aims to refresh your Java knowledge and lay a solid foundation, including: Lambda, reflection, annotations, multithreading, and more.
  • [Problem Solutions] The column aims to provide relevant solutions for problems and bugs encountered in business, including BUG handling, front-end and back-end business problem solutions, etc
  • Small partners if you need to pay attention to oh, all columns continue to update ing

Problem is introduced into

Copy of different objects of the same property names, generally use the Spring to provide directly org. Springframework. Beans. BeanUtils. CopyProperties (Object source, the Object target) to copy, For different attribute names, the code is as follows:

/** * source user **@author : uu
 * @version : v1.0
 * @Date2022/2/12 * /
@Getter @Setter@ToString
public class OriginUser {
    /**id*/
    private Long originId;

    / * * name * /
    private String originName;
    
     / * * * / passwords
    private String password;

    /** Date of birth */
    private Date originBirthDay;

    /** Is healthy */
    private Boolean originHealth;
}

/** * target User *@author : uu
 * @version : v1.0
 * @Date2022/2/12 * /
@Getter@Setter@ToString
public class TargetUser {
    /**id*/
    private Long targetId;

    / * * name * /
    private String targetName;
    
     / * * * / passwords
    private String password;

    /** Date of birth */
    private Date targetBirthDay;

    /** Is healthy */
    private Boolean targetHealth;
}
Copy the code

The spring-provided utility class will be copied from the OriginUser object to generate the TargetUser object. The getter/setter code is as follows:

/** * Initialize the source user *@return* /
public OriginUser initOriginUser(a) {
    OriginUser originUser = new OriginUser();
    originUser.setOriginId(1L);
    originUser.setOriginName("Here comes Kano.");
    originUser.setPassword("123");
    originUser.setOriginBirthDay(new Date());
    originUser.setOriginHealth(Boolean.TRUE);
    return originUser;
}

@Test
@displayName ("getter/setter copy ")
public void test(a){
    OriginUser originUser = initOriginUser();
    
    // Copy the originUser attribute value to targetUser
    TargetUser targetUser = new TargetUser();
    targetUser.setTargetId(originUser.getOriginId());
    targetUser.setTargetName(originUser.getOriginName());
    targetUser.setTargetBirthDay(originUser.getOriginBirthDay());
    targetUser.setTargetHealth(originUser.getOriginHealth());
}
Copy the code

Getter /setter is simple, crude, easy to write, and difficult to expand. If the attribute is too many, certainly write vomit blood. What’s a good way? Small friends please continue to look down!

thinking

A copy of an object can be handled using reflection, but the problem with copying two objects with different properties is how do we associate two different property names. With this in mind, we can consider setting up a utility class to store property mappings between two objects. The problem arises when there are thousands of objects, and establishing a relational mapping is a huge task.

If you’ve used the fastJson utility class, you know the @jsonField annotation, which allows you to alias a property @jsonfield (name= “XXX”). You can also use this scheme when copying different property objects.

solution

  1. The statement@FieldAliasAnnotation marks the mapping between the actual attribute names to be copied on the class attribute to be operated;
  2. Construct the fieldName-FieldValue relationship between the actual attribute name of the source object and the GET method based on reflection.
  3. Obtain all the attributes of the target object based on reflection, and set the values of the attributes of the target object through the Fieldname-FieldValue relationship of the source object constructed in Step 2.

Next, let’s get into the code practice.

The code field

Declare the annotation FieldAlias

/** * Attribute alias annotation **@author : uu
 * @Date2022/2/12 * /
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAlias {
    // Attribute alias
    String value(a);
}
Copy the code

If the attribute uses the annotation, the actual attribute name is the value corresponding to value in the annotation; otherwise, the attribute name is the original attribute name

Adjust OriginUser class

We need to copy the OriginUser attributes into the TargetUser object, so we can modify the OriginUser class using the @FieldAlias annotation as follows:

@Getter @Setter@ToString
public class OriginUser {
    /**id*/
    @FieldAlias("targetId")
    private Long originId;

    / * * name * /
    @FieldAlias("targetName")
    private String originName;

    / * * * / passwords
    private String password;

    /** Date of birth */
    @FieldAlias("targetBirthDay")
    private Date originBirthDay;

    /** Is healthy */
    @FieldAlias("targetHealth")
    private Boolean originHealth;
}
Copy the code

Write copy utility classes

/** * beanUtil utility class **@author : uu
 * @version : v1.0
 * @Date2022/2/12 * /
public class BeanUtil {

    /** * <h3> Copy an attribute from one object to another </h3> * <p>@link FieldAlias}
     * </p>
     *
     * @paramOriginBean Source object *@paramTargetBean Target object */
    public static void copyBean(Object originBean, Object targetBean) {
        Map<String, Object> originFieldKeyWithValueMap = new HashMap<>(16);
        PropertyDescriptor propertyDescriptor = null;
        // Generate a dictionary of the properties of the source bean and their values
        operateBeanFieldWithValue(originBean,
                propertyDescriptor,
                originFieldKeyWithValueMap,
                originBean.getClass(),
                (bean, descriptor, realFieldName, fieldWithMethodMap)-> {
                    try {
                        // Get the current property's get method
                        Method method = descriptor.getReadMethod();
                        / / set the value
                        Object value = method.invoke(bean);
                        // Cache the source object values
                        fieldWithMethodMap.put(realFieldName, value);
                    } catch (IllegalAccessException e) {
                        System.err.println([source object] exception: + realFieldName + "Get method failed!");
                    } catch (InvocationTargetException e) {
                        System.err.println([source object] exception: + realFieldName + "Get method failed!"); }});// Set the property value of the target bean
        operateBeanFieldWithValue(targetBean, propertyDescriptor, originFieldKeyWithValueMap, targetBean.getClass(),
                (bean, descriptor, realFieldName, fieldWithMethodMap)-> {
                    try {
                        // Get the set method for the current property
                        Method method = descriptor.getWriteMethod();
                        method.invoke(bean, fieldWithMethodMap.get(realFieldName));
                    } catch (IllegalAccessException e) {
                        System.err.println([target object] Exception :" + realFieldName + "Set method failed!");
                    } catch (InvocationTargetException e) {
                        System.err.println([target object] Exception :" + realFieldName + "Set method failed!"); }}); }/** * Operation bean * For the source object: generates the dictionary attribute - attribute value that needs to be copied, recursively fetching the parent class attribute value * for the target object: sets the source object's attribute value *@paramBean The bean currently being operated on@paramDescriptor property descriptor, which gets properties and methods * in the bean@paramOriginFieldNameWithValueMap store attributes and attribute values to be copied@paramBeanClass The class being operated on */
    private static void operateBeanFieldWithValue(Object bean, PropertyDescriptor descriptor, Map
       
         originFieldNameWithValueMap, Class
         beanClass, CVFunction cvFunction )
       ,> {
        /** If no superclass exists, break out of the loop */
        if (beanClass.getSuperclass() == null) {
            return;
        }
        Field[] fieldList = beanClass.getDeclaredFields();
        for (Field field : fieldList) {
            try {
                /* Get the annotation on the property. If it does not exist, use the attribute name; if it does, use the annotation name */
                FieldAlias fieldAlias = field.getAnnotation(FieldAlias.class);
                String realFieldName = Objects.isNull(fieldAlias) ? field.getName() : fieldAlias.value();
                / / initialization
                descriptor = new PropertyDescriptor(field.getName(), beanClass);
                cvFunction.apply(bean, descriptor, realFieldName, originFieldNameWithValueMap);
            } catch (IntrospectionException e) {
                System.err.println([source object] exception: + field.getName() + "No corresponding get method exists, cannot participate in copy!"); }}// Generate the superclass attribute -valueoperateBeanFieldWithValue(bean, descriptor, originFieldNameWithValueMap, beanClass.getSuperclass(), cvFunction); }}Copy the code

In the above code, we use a custom functional interface. The interface is defined as follows:

/** * A functional interface to the property CV operation@author : uu
 * @version : v1.0
 * @Date2022/2/12 * /
@FunctionalInterface
public interface CVFunction {
    /** * Perform CV operation **@param bean
     * @param descriptor
     * @param realFieldName
     * @param fieldWithMethodMap
     */
    void apply(Object bean, PropertyDescriptor descriptor, String realFieldName, Map<String, Object> fieldWithMethodMap);
}
Copy the code

The test code

@Test
@displayName (" Annotation and reflection copy ")
public void test2(a){
    OriginUser originUser = initOriginUser();
    // OriginUser(originId=1, originName= 1, password=123, originBirthDay=Sat Feb 12 22:08:46 CST 2022, originHealth=true)
    System.out.println(originUser);
    
    // Copy the originUser attribute value to targetUser
    TargetUser targetUser = new TargetUser();
    
    // =========== Execute copy ================
    BeanUtil.copyBean(originUser, targetUser);
    // TargetUser(targetName= 1, targetName= 1, password=123, targetBirthDay=Sat Feb 12 22:08:46 CST 2022, targetHealth=true)
    System.out.println(targetUser);
}
Copy the code

The source code

  • 👉 source can be accessed here

conclusion

  • This chapter mainly uses the reflection, annotation, functional interface knowledge we learned before, to solve the object copy problem encountered in the business;
  • Property descriptors can easily obtain read and write methods, reducing the cost of obtaining methods through string concatenation.
  • The main purpose of this article is to provide solutions, the above tool class still has room for optimization, interested partners can obtain the source code according to the need to make adjustments.

The last

  • Thank you for your patience to see the end, if you feel this article is helpful, please give aPraise 👍orFocus on ➕;
  • Due to my limited technology, the article and code may be wrong, I hope you comment pointed out, very grateful 🙏;
  • At the same time, welcome everyone V I (uu2coding) to discuss learning front-end, Java knowledge, volume and progress together.