天天看點

java之通過反射生成并初始化對象

java之通過反射生成并初始化對象

在博文 《java之的讀取檔案大全》 中讀取csv檔案後,需要自己将csv檔案的對象轉為自己的DO對象,那麼有沒有辦法我直接穿進去一個DO的class對象,内部實作生成對象,并利用 ​

​CSVRecord​

​ 對象對其進行初始化呢 ?

本篇主要是為了解決上面的這個問題,實作了一個非常初級轉換方法,然後會分析下大名鼎鼎的​

​BeanUtils​

​是如何實作這種功能的

1. ​

​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, 目前流行的就有好多個 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​

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;
}      

一個執行個體的截圖如下

java之通過反射生成并初始化對象

如何擷取 ​

​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對應的 ​

    ​BeanInfo​

    ​ 用了緩存,相當于一個class隻用反射擷取一次即可,避免每次都這麼幹
  • 類型轉換,相比較我們上面原始到爆的簡陋方案,​

    ​BeanUtils​

    ​使用的是專門做類型轉換的 ​

    ​Converter​

    ​ 來實作,所有你可以自己定義各種類型的轉換,注冊進去後可以實作各種鬼畜的場景了
  • 各種異常邊界的處理 (單反一個開源的成熟産品,這一塊真心沒話說)
  • ​DynaBean​

    ​​ ​

    ​Map​

    ​ ​

    ​Array​

    ​ 這幾個類型單獨進行處理,上面也沒有分析
  • 用内省來操作JavaBean對象,而非使用反射 參考博文《深入了解Java:内省(Introspector)》