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<?>) <--
- 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<?>)
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