java generates and initializes objects by reflection

java generates and initializes objects by reflection

> In the blog Read File Complete of java _____________ After reading the csv file, we need to convert the object of the csv file to our own DO object. Is there any way that I can directly penetrate into a DO class object, realize the generation of the object internally, and initialize it using the CSVRecord object?

In order to solve this problem, this paper implements a very elementary transformation method, and then analyses how the famous Bean Utils implements this function.

1. CSVRecord object to xxxBO object

Before doing this, we should paste out the relevant code of csv reading. The detailed implementation logic can be referred to. Read File Complete of java _____________

CsvUtil.java

/**
 * read file
 */
public static InputStream getStreamByFileName(String fileName) throws IOException {
        if (fileName == null) {
            throw new IllegalArgumentException("fileName should not be null!");
        }

        if (fileName.startsWith("http")) { // network address
            URL url = new URL(fileName);
            return url.openStream();
        } else if (fileName.startsWith("/")) { // Absolute Path
            Path path = Paths.get(fileName);
            return Files.newInputStream(path);
        } else  { // Relative Path
            return FileUtil.class.getClassLoader().getResourceAsStream(fileName);
        }
    }

/**
* Read the csv file and return the object of structured speech
* @param filename csv Path + File Name, Supporting Absolute Path + Relative Path + Network File
* @param headers  csv Data for each column
* @return
* @throws IOException
*/
public static List<CSVRecord> read(String filename, String[] headers) throws IOException {
   try (Reader reader = new InputStreamReader(getStreamByFileName(fileName), Charset.forName("UTF-8"))) {
       CSVParser csvParser = new CSVParser(reader,
               CSVFormat.INFORMIX_UNLOAD_CSV.withHeader(headers)
       );

       return csvParser.getRecords();
   }
}

word.csv file

dicId,"name",rootWord,weight
1,"quality",true,0.1
2,"service",true,0.2
3,"Deliver goods",,0.1
4,"Cost performance",false,0.4
5,"Size",true,0.4

test case

@Getter
@Setter
@ToString
static class WordDO {
   long dicId;

   String name;

   Boolean rootWord;

   Float weight;

   public WordDO() {
   }
}

@Test
public void testCsvRead() throws IOException {
   String fileName = "word.csv";
   List<CSVRecord> list = CsvUtil.read(fileName, new String[]{"dicId", "name", "rootWord", "weight"});
   Assert.assertTrue(list != null && list.size() > 0);

   List<WordDO> words = list.stream()
           .filter(csvRecord -> !"dicId".equals(csvRecord.get("dicId")))
           .map(this::parseDO).collect(Collectors.toList());
   logger.info("the csv words: {}", words);
}

private WordDO parseDO(CSVRecord csvRecord) {
   WordDO wordDO = new WordDO();
   wordDO.dicId = Integer.parseInt(csvRecord.get("dicId"));
   wordDO.name = csvRecord.get("name");
   wordDO.rootWord = Boolean.valueOf(csvRecord.get("rootWord"));
   wordDO.weight = Float.valueOf(csvRecord.get("weight"));
   return wordDO;
}

Output results

16:17:27.145 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=quality, rootWord=true, weight=0.1)
16:17:27.153 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=2, name=service, rootWord=true, weight=0.2)
16:17:27.154 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=3, name=Deliver goods, rootWord=false, weight=0.1)
16:17:27.154 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=4, name=Cost performance, rootWord=false, weight=0.4)
16:17:27.154 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=5, name=Size, rootWord=true, weight=0.4)

From the above point of view, each time we have to transform the parsed CsvRecord by ourselves. Our goal is to integrate this into CsvUtil to achieve.

Design Thought

Reflect to create an object, get all the attributes of the object, and then add a set before the attributes to represent the method of setting the attributes (boolea type attributes may be in isXXX format). Reflect to set the attributes of the method.

  • Create object: T obj = clz.newInstance();
  • Get all attributes: Field[] fields = clz.getDeclaredFields();
  • Setting property values
    • Method name: fieldSetMethodName = set + upperCase (field. getName ());
    • Property value, need to convert the corresponding type: fieldValue = this.parseType(value, field.getType());
    • Get the method of setting attributes: Method = clz. getDeclared Method (fieldSetMethodName, field. getType ());
    • Setting properties: method. invoke (obj, field Value);

Implementation code

The basic structure is as follows. First, we post the code of the implementation, and give a brief description of some of them.

private <T> T parseBO(CSVRecord csvRecord, Class<T> clz) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        // Create BO objects
        T obj = clz.newInstance();

        // Gets all member variables declared
        Field[] fields = clz.getDeclaredFields();

        // Save values in csvRecord corresponding to attributes
        String value;
        String fieldSetMethodName;
        Object fieldValue;
        for (Field field : fields) {
            // Set to Accessible
            field.setAccessible(true);

            // Convert value to target type
            value = csvRecord.get(field.getName());
            if (value == null) {
                continue;
            }
            fieldValue = this.parseType(value, field.getType());

            // Gets the name of the setting method corresponding to the property
            fieldSetMethodName = "set" + upperCase(field.getName());
            Method method = clz.getDeclaredMethod(fieldSetMethodName, field.getType());

            // Setting property values
            method.invoke(obj, fieldValue);
        }

        return obj;
    }

    // Capitalize the initial letter
    private String upperCase(String str) {
        char[] ch = str.toCharArray();
//      You can also capitalize directly with the following memory
//      ch[0] = Character.toUpperCase(ch[0]);
        if (ch[0] >= 'a' && ch[0] <= 'z') {
            ch[0] = (char) (ch[0] - 32);
        }
        return new String(ch);
    }

    /**
     * Type Conversion
     * 
     * @param value Raw data format
     * @param type Types of Expected Conversion
     * @return Converted data objects
     */
    private Object parseType(String value, Class type) {

        if (type == String.class) {
            return value;
        } else if (type == int.class) {
            return value == null ? 0 : Integer.parseInt(value);
        } else if (type == float.class) {
            return value == null ? 0f : Float.parseFloat(value);
        } else if (type == long.class) {
            return value == null ? 0L : Long.parseLong(value);
        } else if (type == double.class) {
            return value == null ? 0D : Double.parseDouble(value);
        } else if (type == boolean.class) {
            return value != null && Boolean.parseBoolean(value);
        } else if (type == byte.class) {
            return value == null || value.length() == 0 ? 0 : value.getBytes()[0];
        } else if (type == char.class) {
            if (value == null || value.length() == 0) {
                return 0;
            }

            char[] chars = new char[1];
            value.getChars(0, 1, chars, 0);
            return chars[0];
        }

        // Non-basic types,
        if (StringUtils.isEmpty(value)) {
            return null;
        }

        if (type == Integer.class) {
            return Integer.valueOf(value);
        } else if (type == Long.class) {
            return Long.valueOf(value);
        } else if (type == Float.class) {
            return Float.valueOf(value);
        } else if (type == Double.class) {
            return Double.valueOf(value);
        } else if (type == Boolean.class) {
            return Boolean.valueOf(value);
        } else if (type == Byte.class) {
            return value.getBytes()[0];
        } else if (type == Character.class) {
            char[] chars = new char[1];
            value.getChars(0, 1, chars, 0);
            return chars[0];
        }

        throw new IllegalStateException("argument not basic type! now type:" + type.getName());
    }

1. Capitalization of strings

The most intuitive approach is to use String's built-in approach directly.

return str.substring(0,1).toUpperCase() + str.substring(1);

Because substring actually generates a String object, the above line of code actually generates three new objects (+sign generates another one). In our code, we get the character array of String object directly, modify it and regenerate a String return. In fact, only one new object is generated. A little better

2. string to Basic Data Type

Note that converting String to basic data objects requires special handling of empty situations when encapsulating objects.

3. Several limitations

BO objects must be instantiatable

For example, the following WordBO object cannot be created by reflection

public class CsvUtilTest {
    @Getter
    @Setter
    @ToString
    private static class WordBO {
        long dicId;

        String name;

        Boolean rootWord;

        Float weight;

//        public WordDO() {
//        }
    }
}

The solution is to add a default parametric construction method.

BO Object Requirements

  • Display declaration parametric-free construction method
  • The setting method of attribute abc is named setAbc(xxx)
  • Attributes are basic data structures (if the object is stored in a csv file in json string format, the json tool can be used for deserialization, which may be simpler)
  • The attribute names of BO objects are the same as those of objects in CsvRecord

Test 1

@Test
public void testCsvReadV2() throws IOException {
   String fileName = "word.csv";
   List<CSVRecord> list = CsvUtil.read(fileName, new String[]{"dicId", "name", "rootWord", "weight"});
   Assert.assertTrue(list != null && list.size() > 0);

   try {
       List<WordDO> words = new ArrayList<>(list.size() - 1);
       for (int i = 1; i < list.size(); i++) {
           words.add(parseDO(list.get(i), WordDO.class));
       }

       words.stream().forEach(
               word -> logger.info("the csv words: {}", word)
       );
   } catch (Exception e) {
       logger.error("parse DO error! e: {}", e);
   }
}

Output results

17:17:14.640 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=quality, rootWord=true, weight=0.1)
17:17:14.658 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=2, name=service, rootWord=true, weight=0.2)
17:17:14.658 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=3, name=Deliver goods, rootWord=null, weight=0.1)
17:17:14.659 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=4, name=Cost performance, rootWord=false, weight=0.4)
17:17:14.659 [main] INFO  c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=5, name=Size, rootWord=true, weight=0.4)

Note that the root Word of the output of shipping here is null, while the output above is false, mainly because of the different parsing logic.

2. BeanUtils analysis

> Bean Utils, the top name, are popular today. > Two versions of Apache: (Reflection mechanism) org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig) org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) Spring version: (reflection mechanism) org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties) cglib version: (using dynamic proxy, high efficiency) net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)

The goal of this analysis is to focus on BeanUtils.copyProperties

First look at a case in use

DoA.java

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class DoA {

    private String name;

    private long phone;
}

DoB.java

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class DoB {
    private String name;

    private long phone;
}

Test case

@Test
public void testBeanCopy() throws InvocationTargetException, IllegalAccessException {
   DoA doA = new DoA();
   doA.setName("yihui");
   doA.setPhone(1234234L);

   DoB doB = new DoB();
   BeanUtils.copyProperties(doB, doA);
   log.info("doB: {}", doB);

   BeanUtils.setProperty(doB, "name", doA.getName());
   BeanUtils.setProperty(doB, "phone", doB.getPhone());
   log.info("doB: {}", doB);
}

1. Attribute copy logic

Look at the code that actually copies the attributes.

  • Gets the property description class PropertyDescriptor of the object.
  • Then traverse the attribute getPropertyUtils (). isReadable (orig, name) & amp; & amp; getPropertyUtils (). isWriteable (dest, name)
  • Get orgi attribute name + attribute value, and perform assignment copyProperty(dest, name, value);
PropertyDescriptor[] origDescriptors =
      getPropertyUtils().getPropertyDescriptors(orig);
for (int i = 0; i < origDescriptors.length; i++) {
 String name = origDescriptors[i].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 {
         Object value =
             getPropertyUtils().getSimpleProperty(orig, name);
        // Get the attribute name + attribute value of the source object, and call the copyProperty method to realize the assignment.
         copyProperty(dest, name, value);
     } catch (NoSuchMethodException e) {
         // Should not happen
     }
 }

2. PropertyDescriptor

> JDK description: A Property Descriptor describes one property that a Java Bean exports via a pair of accessor methods.

Once you get this attribute from the class, you basically get to various attributes and how to set them.

Several key attributes within

// Member types of bean s
private Reference<? extends Class<?>> propertyTypeRef;
// Member Reading Method of Beans
private final MethodRef readMethodRef = new MethodRef();
// Member Writing Method for Beans
private final MethodRef writeMethodRef = new MethodRef();

MethodRef.java, which contains references to methods

final class MethodRef {
    // Method signature, such as: public void com.hust.hui.quicksilver.file.test.dos.DoA.setName(java.lang.String)
    private String signature;
    private SoftReference<Method> methodRef;
    // Class corresponding to the class in which the method resides
    private WeakReference<Class<?>> typeRef;
}

A screenshot of an example is as follows

How do I get the PropertyDescriptor object? With java. beans. BeanInfo getProperty Descriptors, follow Property Descriptor [] origDescriptors = getPropertyUtils (). getPropertyDescriptors (orig);, along the way, you can find out how to get BeanInfo objects according to class and paste several important nodes.

  • org.apache.commons.beanutils.PropertyUtilsBean#getPropertyDescriptors(java.lang.Class&lt;?&gt;) <--
  • org.apache.commons.beanutils.PropertyUtilsBean#getIntrospectionData <--
  • org.apache.commons.beanutils.PropertyUtilsBean#fetchIntrospectionData <--
  • org.apache.commons.beanutils.DefaultBeanIntrospector#introspect <--
  • java.beans.Introspector#getBeanInfo(java.lang.Class&lt;?&gt;)

    beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
    
    When an `Introspector'object is created, the superclass of the class is retrieved recursively, that is to say, the attributes in the superclass are also included. In the construction method, the following method `findExplicitBeanInfo', which is actually borrowed from jdk's `BeanInfoFinder#find()', is called.
    
    /**
     * 
     */
    private static BeanInfo findExplicitBeanInfo(Class<?> beanClass) {
        return ThreadGroupContext.getContext().getBeanInfoFinder().find(beanClass);
    }
    

3. Attribute Copy

The basic information of the Bean object (member variable + read and write method) is retrieved by introspection. The remaining point is the copyProperty(dest, name, value) in the source code; the actual property value setting

Look at the code, using a lot of seemingly tall things, excluding some of the concerns, the main thing is to do a few things.

  • Property description object descriptor = getPropertyUtils().getPropertyDescriptor(target, name);
  • Parameter type = descriptor.getPropertyType();
  • Type conversion value of attribute value = convertForCopy (value, type);
  • Property values set getPropertyUtils().setSimpleProperty(target, propName, value);

The source code of the final property setting is as follows, deleting a lot of unconcerned code, which is basically the same as our implementation above.

public void setSimpleProperty(Object bean,
                                         String name, Object value)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        // Retrieve the property setter method for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);

        Method writeMethod = getWriteMethod(bean.getClass(), descriptor);

        // Call the property setter method
        Object[] values = new Object[1];
        values[0] = value;

        invokeMethod(writeMethod, bean, values);

    }

4. Summary

apache's Bean Utils approach to property copying is similar to our previous design, so what's the difference? Looking closely at the BeaUtils source code, you can see that there are many optimization points.

  • BeanInfo for clas is cached, which is equivalent to a class being retrieved only once by reflection, to avoid doing so every time.
  • Type conversion. BeanUtils uses Converter, which specializes in type conversion. You can define all types of conversion by yourself. After registering, you can realize all kinds of ghost and animal scenarios.
  • Processing of various abnormal boundaries (SLR is an open source mature product, this piece is really speechless)
  • These types of DynaBean Map Array are handled separately and are not analyzed above.
  • Use introspection to manipulate JavaBean objects rather than reflective reference blogs Deep Understanding of Java: Introspector

Keywords: Java Attribute Apache network

Added by danoush on Mon, 01 Jul 2019 21:46:22 +0300