java之通過反射生成并初始化對象
在博文 《java之的讀取檔案大全》 中讀取csv檔案後,需要自己将csv檔案的對象轉為自己的DO對象,那麼有沒有辦法我直接穿進去一個DO的class對象,内部實作生成對象,并利用 CSVRecord
對象對其進行初始化呢 ?
本篇主要是為了解決上面的這個問題,實作了一個非常初級轉換方法,然後會分析下大名鼎鼎的
BeanUtils
是如何實作這種功能的
1. CSVRecord
對象轉 xxxBO
對象
CSVRecord
xxxBO
在做之前,先把csv的讀取相關代碼貼出來,具體的實作邏輯詳解可以參考 《java之的讀取檔案大全》
CsvUtil.java
/**
* 讀取檔案
*/public static InputStream getStreamByFileName(String fileName) throws IOException { if (fileName == null) { throw new IllegalArgumentException("fileName should not be null!");
} if (fileName.startsWith("http")) { // 網絡位址
URL url = new URL(fileName); return url.openStream();
} else if (fileName.startsWith("/")) { // 絕對路徑
Path path = Paths.get(fileName); return Files.newInputStream(path);
} else { // 相對路徑
return FileUtil.class.getClassLoader().getResourceAsStream(fileName);
}
}/** * 讀取csv檔案, 傳回結構話的對象 * @param filename csv 路徑 + 檔案名, 支援絕對路徑 + 相對路徑 + 網絡檔案 * @param headers csv 每列的資料 * @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();
}
}
/**
* 讀取檔案
*/public static InputStream getStreamByFileName(String fileName) throws IOException { if (fileName == null) { throw new IllegalArgumentException("fileName should not be null!");
} if (fileName.startsWith("http")) { // 網絡位址
URL url = new URL(fileName); return url.openStream();
} else if (fileName.startsWith("/")) { // 絕對路徑
Path path = Paths.get(fileName); return Files.newInputStream(path);
} else { // 相對路徑
return FileUtil.class.getClassLoader().getResourceAsStream(fileName);
}
}/** * 讀取csv檔案, 傳回結構話的對象 * @param filename csv 路徑 + 檔案名, 支援絕對路徑 + 相對路徑 + 網絡檔案 * @param headers csv 每列的資料 * @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
檔案
dicId,"name",rootWord,weight1,"品質",true,0.12,"服務",true,0.23,"發貨",,0.14,"成本效益",false,0.45,"尺碼",true,0.4
dicId,"name",rootWord,weight1,"品質",true,0.12,"服務",true,0.23,"發貨",,0.14,"成本效益",false,0.45,"尺碼",true,0.4
測試用例
@Getter@Setter@ToStringstatic class WordDO { long dicId;
String name; Boolean rootWord; Float weight; public WordDO() {
}
}@Testpublic 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;
}
@Getter@Setter@ToStringstatic class WordDO { long dicId;
String name; Boolean rootWord; Float weight; public WordDO() {
}
}@Testpublic 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;
}
輸出結果
16:17:27.145 [main] INFO c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=品質, 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=服務, 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=發貨, 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=成本效益, 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=尺碼, rootWord=true, weight=0.4)
16:17:27.145 [main] INFO c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=品質, 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=服務, 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=發貨, 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=成本效益, 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=尺碼, rootWord=true, weight=0.4)
從上面的使用來看,每次都要自己對解析出來的
CsvRecord
進行對象轉換, 我們的目标就是把這個內建在
CsvUtil
内部去實作
設計思路
反射建立對象,擷取對象的所有屬性,然後在屬性前面加
set
表示設定屬性的方法(boolea類型的屬性可能是 isXXX格式), 通過反射設定方法的屬性值
- 建立對象:
T obj = clz.newInstance();
- 擷取所有屬性:
Field[] fields = clz.getDeclaredFields();
- 設定屬性值
- 方法名:
fieldSetMethodName = "set" + upperCase(field.getName());
- 屬性值,需要轉換對應的類型:
fieldValue = this.parseType(value, field.getType());
- 擷取設定屬性方法 :
Method method = clz.getDeclaredMethod(fieldSetMethodName, field.getType());
- 設定屬性:
method.invoke(obj, fieldValue);
實作代碼
基本結構如上,先貼出實作的代碼,并對其中的幾點做一下簡短的說明
private <T> T parseBO(CSVRecord csvRecord, Class<T> clz) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 建立BO對象
T obj = clz.newInstance(); // 擷取聲明的所有成員變量
Field[] fields = clz.getDeclaredFields(); // 儲存屬性對應的csvRecord中的值
String value;
String fieldSetMethodName;
Object fieldValue; for (Field field : fields) { // 設定為可通路
field.setAccessible(true); // 将value轉換為目标類型
value = csvRecord.get(field.getName()); if (value == null) { continue;
}
fieldValue = this.parseType(value, field.getType()); // 擷取屬性對應的設定方法名
fieldSetMethodName = "set" + upperCase(field.getName());
Method method = clz.getDeclaredMethod(fieldSetMethodName, field.getType()); // 設定屬性值
method.invoke(obj, fieldValue);
} return obj;
} // 首字母變大寫
private String upperCase(String str) { char[] ch = str.toCharArray();// 也可以直接用下面的記性轉大寫// ch[0] = Character.toUpperCase(ch[0]);
if (ch[0] >= 'a' && ch[0] <= 'z') {
ch[0] = (char) (ch[0] - 32);
} return new String(ch);
} /** * 類型轉換 * * @param value 原始資料格式 * @param type 期待轉換的類型 * @return 轉換後的資料對象 */
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];
} // 非基本類型,
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());
}
private <T> T parseBO(CSVRecord csvRecord, Class<T> clz) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 建立BO對象
T obj = clz.newInstance(); // 擷取聲明的所有成員變量
Field[] fields = clz.getDeclaredFields(); // 儲存屬性對應的csvRecord中的值
String value;
String fieldSetMethodName;
Object fieldValue; for (Field field : fields) { // 設定為可通路
field.setAccessible(true); // 将value轉換為目标類型
value = csvRecord.get(field.getName()); if (value == null) { continue;
}
fieldValue = this.parseType(value, field.getType()); // 擷取屬性對應的設定方法名
fieldSetMethodName = "set" + upperCase(field.getName());
Method method = clz.getDeclaredMethod(fieldSetMethodName, field.getType()); // 設定屬性值
method.invoke(obj, fieldValue);
} return obj;
} // 首字母變大寫
private String upperCase(String str) { char[] ch = str.toCharArray();// 也可以直接用下面的記性轉大寫// ch[0] = Character.toUpperCase(ch[0]);
if (ch[0] >= 'a' && ch[0] <= 'z') {
ch[0] = (char) (ch[0] - 32);
} return new String(ch);
} /** * 類型轉換 * * @param value 原始資料格式 * @param type 期待轉換的類型 * @return 轉換後的資料對象 */
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];
} // 非基本類型,
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. 字元串的首字母大寫
最直覺的做法是直接用String的内置方法
return str.substring(0,1).toUpperCase() + str.substring(1);
因為
substring
内部實際上會新生成一個String對象,是以上面這行代碼實際上新生成了三個對象(+号又生成了一個),而我們的代碼中, 則直接擷取String對象的字元數組,修改後重新生成一個String傳回,實際隻新生成了一個對象,稍微好一點
2. string 轉基本資料類型
注意一下将String轉換為基本的資料對象,封裝對象時, 需要對空的情況進行特殊處理
3. 幾個限制
BO對象必須是可執行個體化的
舉一個反例, 下面的這個
WordBO
對象就沒辦法通過反射建立對象
public class CsvUtilTest { @Getter
@Setter
@ToString
private static class WordBO { long dicId;
String name;
Boolean rootWord;
Float weight;// public WordDO() {// }
}
}
public class CsvUtilTest { @Getter
@Setter
@ToString
private static class WordBO { long dicId;
String name;
Boolean rootWord;
Float weight;// public WordDO() {// }
}
}
解決辦法是加一個預設的無參構造方法即可
BO對象要求
- 顯示聲明無參構造方法
- 屬性
的設定方法命名為 abc
setAbc(xxx)
- 屬性都是基本的資料結構 (若對象是以json字元串格式存csv檔案時,可利用json工具進行反序列化,這樣可能會更加簡單)
- BO對象的屬性名與
中的對象名相同CsvRecord
測試一發
@Testpublic 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);
}
}
@Testpublic 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);
}
}
輸出結果
17:17:14.640 [main] INFO c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=品質, 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=服務, 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=發貨, 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=成本效益, 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=尺碼, rootWord=true, weight=0.4)
17:17:14.640 [main] INFO c.h.h.q.file.test.FileUtilTest - the csv words: CsvUtilTest.WordDO(dicId=1, name=品質, 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=服務, 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=發貨, 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=成本效益, 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=尺碼, rootWord=true, weight=0.4)
注意這裡發貨這一個輸出的 rootWord為null, 而上面的是輸出false, 主要是因為解析邏輯不同導緻
2. BeanUtils
分析
BeanUtils
頂頂大名的BeanUtils, 目前流行的就有好多個 Apache的兩個版本:(反射機制) org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig) org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) Spring版本:(反射機制) org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties) cglib版本:(使用動态代理,效率高) net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)
本篇分析的目标放在
BeanUtils.copyProperties
上
先看一個使用的case
DoA.java
@Getter@Setter@ToString@AllArgsConstructor@NoArgsConstructorpublic class DoA { private String name; private long phone;
}
@Getter@Setter@ToString@AllArgsConstructor@NoArgsConstructorpublic class DoA { private String name; private long phone;
}
DoB.java
@Getter@Setter@ToString@AllArgsConstructor@NoArgsConstructorpublic class DoB { private String name; private long phone;
}
@Getter@Setter@ToString@AllArgsConstructor@NoArgsConstructorpublic class DoB { private String name; private long phone;
}
測試case
@Testpublic 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);
}
@Testpublic 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, 屬性拷貝邏輯
實際看下屬性拷貝的代碼,
- 擷取對象的屬性描述類
,PropertyDescriptor
- 然後周遊可以進行指派的屬性
getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)
- 擷取
屬性名 + 屬性值,執行指派 orgi
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); // 擷取源對象的 屬性名 + 屬性值, 調用 copyProperty方法實作指派
copyProperty(dest, name, value);
} catch (NoSuchMethodException e) { // Should not happen
}
}
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); // 擷取源對象的 屬性名 + 屬性值, 調用 copyProperty方法實作指派
copyProperty(dest, name, value);
} catch (NoSuchMethodException e) { // Should not happen
}
}
2. PropertyDescriptor
PropertyDescriptor
jdk說明: A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.
根據class得到這個屬性之後,基本上就get到各種屬性,以及屬性的設定方法了
内部的幾個關鍵屬性
// bean 的成員類型private Reference<? extends Class<?>> propertyTypeRef;// bean 的成員讀方法private final MethodRef readMethodRef = new MethodRef();// bean 的成員寫方法private final MethodRef writeMethodRef = new MethodRef();
// bean 的成員類型private Reference<? extends Class<?>> propertyTypeRef;// bean 的成員讀方法private final MethodRef readMethodRef = new MethodRef();// bean 的成員寫方法private final MethodRef writeMethodRef = new MethodRef();
MethodRef.java
, 包含了方法的引用
final class MethodRef { // 方法簽名 , 如 : public void com.hust.hui.quicksilver.file.test.dos.DoA.setName(java.lang.String)
private String signature; private SoftReference<Method> methodRef; // 方法所在的類對應的class
private WeakReference<Class<?>> typeRef;
}
final class MethodRef { // 方法簽名 , 如 : public void com.hust.hui.quicksilver.file.test.dos.DoA.setName(java.lang.String)
private String signature; private SoftReference<Method> methodRef; // 方法所在的類對應的class
private WeakReference<Class<?>> typeRef;
}
一個執行個體的截圖如下

如何擷取
PropertyDescriptor
對象呢 ? 通過
java.beans.BeanInfo#getPropertyDescriptors
即可, 順着
PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
, 一路摸到如何根據 class 擷取 BeanInfo對象, 貼一下幾個重要的節點
-
<--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();
在建立 `Introspector` 對象時, 會遞歸擷取class的超類,也就是說超類中的屬性也會包含進來, 構造方法中,調用了下面的方法 `findExplicitBeanInfo` , 這裡實際上借用的是jdk的 `BeanInfoFinder#find()` 方法/**
*
*/private static BeanInfo findExplicitBeanInfo(Class<?> beanClass) { return ThreadGroupContext.getContext().getBeanInfoFinder().find(beanClass);
}
```
```
beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
在建立 `Introspector` 對象時, 會遞歸擷取class的超類,也就是說超類中的屬性也會包含進來, 構造方法中,調用了下面的方法 `findExplicitBeanInfo` , 這裡實際上借用的是jdk的 `BeanInfoFinder#find()` 方法/**
*
*/private static BeanInfo findExplicitBeanInfo(Class<?> beanClass) { return ThreadGroupContext.getContext().getBeanInfoFinder().find(beanClass);
}
```
3. 屬性拷貝
上面通過内省擷取了Bean對象的基本資訊(成員變量 + 讀寫方法), 剩下的一個點就是源碼中的
copyProperty(dest, name, value);
實際的屬性值設定
- 屬性描述對象
descriptor = getPropertyUtils().getPropertyDescriptor(target, name);
- 參數類型
type = descriptor.getPropertyType();
- 屬性值的類型轉換
value = convertForCopy(value, type);
- 屬性值設定
getPropertyUtils().setSimpleProperty(target, propName, value);
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);
}
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. 小結
- 擷取 clas對應的
用了緩存,相當于一個class隻用反射擷取一次即可,避免每次都這麼幹BeanInfo
- 類型轉換,相比較我們上面原始到爆的簡陋方案,
使用的是專門做類型轉換的 BeanUtils
來實作,所有你可以自己定義各種類型的轉換,注冊進去後可以實作各種鬼畜的場景了Converter
- 各種異常邊界的處理 (單反一個開源的成熟産品,這一塊真心沒話說)
-
DynaBean
Map
這幾個類型單獨進行處理,上面也沒有分析Array
- 用内省來操作JavaBean對象,而非使用反射 參考博文《深入了解Java:内省(Introspector)》