天天看點

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

反射、BeanUtils、注解

學習目标

  1. 能夠通過反射技術擷取Class位元組碼對象(重點)
  2. 能夠通過反射技術擷取構造方法對象,并建立對象。(掌握)
  3. 能夠通過反射擷取成員方法對象,并且調用方法。(掌握)
  4. 能夠通過反射擷取屬性對象,并且能夠給對象的屬性指派和取值。(了解)
  5. 能夠使用Beanutils常用方法(populate方法)操作JavaBean對象(重點)
  6. 能夠說出注解的作用(了解)
  7. 能夠自定義注解和使用注解(掌握)
  8. 能夠說出常用的元注解及其作用(掌握)
  9. 能夠解析注解并擷取注解中的資料(掌握)
  10. 能夠完成注解的MyTest案例

第1章 反射

1.1 反射的基本概念

1.1.1 什麼是反射

​ 反射是一種機制,利用該機制可以在程式運作過程中對類進行解剖并操作類中的方法,屬性,構造方法等成員。

​ 反射又稱之為應用程式的自省:也就是擷取和操作自身的所有的屬性和行為。

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.1.2 反射在實際開發中的應用

  1. 開發IDE(內建開發環境)
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  • 以上的IDE内部都大量使用了反射機制,我們在使用這些IDE寫代碼也無時無刻的使用着反射機制,一個常用反射機制的地方就是當我們通過對象調用方法或通路屬性時,開發工具都會以清單的形式顯示出該對象所有的方法或屬性,以供友善我們選擇使用,如下圖:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  • 這些開發工具之所有能夠把該對象的方法和屬性展示出來就使用利用了反射機制對該對象所有類進行了解剖擷取到了類中的所有方法和屬性資訊,這是反射在IDE中的一個使用場景。
  1. 各種架構的設計
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  • 以上三個圖示上面的名字就是Java的三大架構,簡稱SSH.
  • 這三大架構的内部實作也大量使用到了反射機制,所有要想學好這些架構,則必須要求對反射機制熟練了。

1.1.3 使用反射機制解剖類的前提

​ 必須先要擷取到該類的位元組碼檔案對象,即Class類型對象。關于Class描述位元組碼檔案如下圖所示:

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

說明:

1)Java中使用Class類表示某個class檔案.

2)任何一個class檔案都是Class這個類的一個執行個體對象.

1.2 擷取Class對象的三種方式

  • 建立測試類:Student
public class Student {
    static {
        System.out.println("靜态代碼塊");
    }
    
  	{
        System.out.println("構造代碼塊");
    }
}
           

1.2.1 方式1:通過類名.class擷取

public class Demo01 {
    public static void main(String[] args) {
         // 獲得Student的Class的對象
        Class c = Student.class;
        // 列印輸出:class com.itheima.reflect.Student
        System.out.println(c);
    }
}
           

1.2.2 方式2:對像名調用getClass()方法擷取

public class Demo01 {
    public static void main(String[] args) {
        // 建立學生對象
        Student stu = new Student();
        // 獲得學生類的Class對象
        Class c = stu.getClass();
        // 列印輸出:class com.itheima.reflect.Student
        System.out.println(c);
    }
}
           

1.2.3 方式3:通過Class.forName(“全限定類名”)方法擷取

public class Demo01 {
    public static void main(String[] args) throws Exception {
        // 獲得字元串的Class對象
        Class c = Class.forName("java.lang.String");
        // 列印輸出:class java.lang.String
        System.out.println(c);
    }
 }
           

1.3 擷取Class對象的資訊

​ 知道怎麼擷取Class對象之後,接下來就介紹幾個Class類中常用的方法了。

1.3.1 Class對象相關方法

  1. String getSimpleName(); 獲得簡單類名,隻是類名,沒有包		
               
  2. String getName(); 擷取完整類名,包含包名+類名	
               
  3. T newInstance() ;建立此 Class 對象所表示的類的一個新執行個體。要求:類必須有public的無參數構造方法
               

1.3.2 擷取簡單類名

public class Demo02 {
    public static void main(String[] args) throws Exception {
         // 獲得字元串的Class對象
        Class c = Class.forName("java.lang.String");
        // 獲得簡單類名
        String name = c.getSimpleName();
        // 列印輸入:name = String
        System.out.println("name = " + name);
    }
}
           

1.3.3 擷取完整類名

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 獲得字元串的Class對象
        Class c = Class.forName("java.lang.String");
        // 獲得完整類名(包含包名和類名)
        String name = c.getName();
        // 列印輸入:name = java.lang.String
        System.out.println("name = " + name);
    }
}
           

1.3.4 建立對象

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 獲得字元串的Class對象
        Class c = Class.forName("java.lang.String");
        // 建立字元串對象
        String str = (String) c.newInstance();
        // 輸出str:空字元串 ""
        System.out.println(str);
    }
}
           

1.4 擷取Class對象的Constructor資訊

​ 一開始在闡述反射概念的時候,我們說到利用反射可以在程式運作過程中對類進行解剖并操作裡面的成員。而一般常操作的成員有構造方法,成員方法,成員變量等等,那麼接下來就來看看怎麼利用反射來操作這些成員以及操作這些成員能幹什麼,先來看看怎麼操作構造方法。而要通過反射操作類的構造方法,我們需要先知道一個Constructor類。

1.4.1 Constructor類概述

​ Constructor是構造方法類,類中的每一個構造方法都是Constructor的對象,通過Constructor對象可以執行個體化對象。

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.4.2 Class類中與Constructor相關方法

1. Constructor getConstructor(Class... parameterTypes) 
    根據參數類型擷取構造方法對象,隻能獲得public修飾的構造方法。
	如果不存在對應的構造方法,則會抛出 java.lang.NoSuchMethodException 異常。
	
2. Constructor getDeclaredConstructor(Class... parameterTypes)
  	根據參數類型擷取構造方法對象,包括private修飾的構造方法。
  	如果不存在對應的構造方法,則會抛出 java.lang.NoSuchMethodException 異常。
   
3. Constructor[] getConstructors() 
   	擷取所有的public修飾的構造方法
   
4. Constructor[] getDeclaredConstructors()
   	擷取所有構造方法,包括privat修飾的
           

1.4.3 Constructor類中常用方法

1. T newInstance(Object... initargs) 
  	根據指定參數建立對象。
2. void setAccessible(true)
  	暴力反射,設定為可以直接通路私有類型的構造方法。
           

1.4.4 示例代碼

  1. 學生類
/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima
 * @date 2018/1/25
 */
public class Student {
    // 姓名
    private String name;
    // 性别
    public String gender;
    // 年齡
    private int age;


    // public 有參構造方法
    public Student(String name, String gender, int age) {
        System.out.println("public 修飾有參數構造方法");
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    // public 無參構造方法
    public Student() {
        System.out.println("public 修飾無參數構造方法");
    }

    // private 有參構造方法
    private Student(String name,String gender){
        System.out.println("private 修飾構造方法");
        this.name = name;
        this.gender = gender;
    }

    // getter & setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // 普通方法
    public void sleep(){
        System.out.println("睡覺");
    }

    public void sleep(int hour){
        System.out.println("public修飾---sleep---睡" + hour + "小時");
    }

    private void eat(){
        System.out.println("private修飾---eat方法---吃飯");
    }

    // 靜态方法
    public static void study(){
        System.out.println("靜态方法---study方法---好好學習Java");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }
}
           
  1. 測試類
/**
 * @author pkxing
 * @version 1.0
 * @description 擷取Class對象的Constructor資訊
 * @date 2018/1/26
 */
public class Demo03 {
    public static void main(String[] args)throws Exception{
        test01();
        test02();
        test03();
        test04();
    }

    /**
     4. Constructor[] getDeclaredConstructors()
        擷取所有構造方法,包括privat修飾的
     */
    public static void test04() throws Exception{
        System.out.println("----------- test04() -----------");
        // 擷取Student類的Class對象
        Class c = Student.class;
        // 擷取所有的public修飾的構造方法
        Constructor[] cons = c.getDeclaredConstructors();
        // 周遊構造方法數組
        for(Constructor con:cons) {
            // 輸出con
            System.out.println(con);
        }
    }

    /**
     3. Constructor[] getConstructors()
        擷取所有的public修飾的構造方法
     */
    public static void test03() throws Exception{
        System.out.println("----------- test03() -----------");
        // 擷取Student類的Class對象
        Class c = Student.class;
        // 擷取所有的public修飾的構造方法
        Constructor[] cons = c.getConstructors();
        // 周遊構造方法數組
        for(Constructor con:cons) {
            // 輸出con
            System.out.println(con);
        }
    }

    /**
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
        根據參數類型擷取構造方法對象,包括private修飾的構造方法。
        如果不存在對應的構造方法,則會抛出 java.lang.NoSuchMethodException 異常。
     */
    public static void test02() throws Exception{
        System.out.println("----------- test02() -----------");
        // 擷取Student類的Class對象
        Class c = Student.class;
        // 根據參數擷取對應的private修飾構造方法對象
        Constructor cons = c.getDeclaredConstructor(String.class,String.class);
        // 注意:private的構造方法不能直接調用newInstance建立對象,需要暴力反射才可以
        // 設定取消權限檢查(暴力反射)
        cons.setAccessible(true);
        // 調用Constructor方法建立學生對象
        Student stu = (Student) cons.newInstance("林青霞","女");
        // 輸出stu
        System.out.println(stu);
    }
    /**
     1. Constructor getConstructor(Class... parameterTypes)
     根據參數類型擷取構造方法對象,隻能獲得public修飾的構造方法。
     如果不存在對應的構造方法,則會抛出 java.lang.NoSuchMethodException 異常。
     */
    public static void test01() throws Exception{
        System.out.println("----------- test01() -----------");
        // 擷取Student類的Class對象
        Class c = Student.class;
        // 根據參數擷取對應的構造方法對象
        Constructor cons = c.getConstructor(String.class,String.class,int.class);
        // 調用Constructor方法建立學生對象
        Student stu = (Student) cons.newInstance("張曼玉","女",28);
        // 輸出stu
        System.out.println(stu);
    }
}
           
  • 輸出結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.5 擷取Class對象的Method資訊

​ 操作完構造方法之後,就來看看反射怎麼操作成員方法了。同樣的在操作成員方法之前我們需要學習一個類:Method類。

1.5.1 Method類概述

​ Method是方法類,類中的每一個方法都是Method的對象,通過Method對象可以調用方法。

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.5.2 Class類中與Method相關方法

1. Method getMethod("方法名", 方法的參數類型... 類型) 
	根據方法名和參數類型獲得一個方法對象,隻能是擷取public修飾的
	
2. Method getDeclaredMethod("方法名", 方法的參數類型... 類型)
  	根據方法名和參數類型獲得一個方法對象,包括private修飾的
  	
3. Method[] getMethods()
  	擷取所有的public修飾的成員方法,包括父類中。

4. Method[] getDeclaredMethods()
  	擷取目前類中所有的方法,包含私有的,不包括父類中。
           

1.5.3 Method類中常用方法

1.  Object invoke(Object obj, Object... args) 
  	根據參數args調用對象obj的該成員方法	
  	如果obj=null,則表示該方法是靜态方法
  
2.  void setAccessible(boolean flag) 
  	暴力反射,設定為可以直接調用私有修飾的成員方法
           

1.5.4 示例代碼

/**
 * @author pkxing
 * @version 1.0
 * @description 擷取Class對象的Method資訊
 * @date 2018/1/26
 */
public class Demo04 {
    public static void main(String[] args)throws  Exception{
        // 獲得Class對象
        Class c = Student.class;
        // 快速建立一個學生對象
        Student stu = (Student ) c.newInstance();

        // 獲得public修飾的方法對象
        Method m1 = c.getMethod("sleep",int.class);
        // 調用方法m1
        m1.invoke(stu,8);

        // 獲得private修飾的方法對象
        Method m2 = c.getDeclaredMethod("eat");
        // 注意:private的成員方法不能直接調用,需要暴力反射才可以
        // 設定取消權限檢查(暴力反射)
        m2.setAccessible(true);
        // 調用方法m2
        m2.invoke(stu);

        // 獲得靜态方法對象
        Method m3 = c.getDeclaredMethod("study");
        // 調用方法m3
        // 注意:調用靜态方法時,obj可以為null
        m3.invoke(null);
      
        System.out.println("------------獲得所有public的方法,不包括private,包括父類的---------------");
        // 獲得所有public的方法,包括父類的
        Method[] ms = c.getMethods();
        // 周遊方法數組
        for(Method m : ms) {
            System.out.println(m);
        }

        System.out.println("-----------獲得所有方法,包括private,不包括父類-----------------------");
        // 獲得所有方法,包括private,不包括父
        Method[] ms2 = c.getDeclaredMethods();
        // 周遊方法數組
        for(Method m : ms2) {
            System.out.println(m);
        }
    }
}
           
  • 輸出結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.6 擷取Class對象的Field資訊

1.6.1 Field類概述

​ Field是屬性類,類中的每一個屬性(成員變量)都是Field的對象,通過Field對象可以給對應的成員變量指派和取值。

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.6.2 Class類中與Field相關方法

1. Field getDeclaredField(String name) 
   根據屬性名獲得屬性對象,包括private修飾的
 
2. Field getField(String name) 
   根據屬性名獲得屬性對象,隻能擷取public修飾的
   
3. Field[]	getFields() 
    擷取所有的public修飾的屬性對象,傳回數組。
    
4. Field[]	getDeclaredFields() 
  	擷取所有的屬性對象,包括private修飾的,傳回數組。
           

1.6.3 Field類中常用方法

void  set(Object obj, Object value) 
void setInt(Object obj, int i) 	
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z) 
void setDouble(Object obj, double d) 

Object get(Object obj)  
int	getInt(Object obj) 
long getLong(Object obj) 
boolean getBoolean(Object ob)
double getDouble(Object obj) 

void setAccessible(true);暴力反射,設定為可以直接通路私有類型的屬性。
Class getType(); 擷取屬性的類型,傳回Class對象。
           
  • setXxx方法都是給對象obj的屬性設定使用,針對不同的類型選取不同的方法。
  • getXxx方法是擷取對象obj對應的屬性值的,針對不同的類型選取不同的方法。

1.6.4 示例代碼

/**
 * @author pkxing
 * @version 1.0
 * @description 擷取Class對象的Field資訊
 * @date 2018/1/26
 */
public class Demo05 {
    public static void main(String[] args)throws Exception{
        // 獲得Class對象
        Class c = Student.class;
        // 快速建立一個學生對象
        Student stu = (Student ) c.newInstance();

        // 獲得public修飾Field對象
        Field f1 = c.getField("gender");
        // 通過f1對象給對象stu的gender屬性指派
        f1.set(stu,"風清揚");
        // 通過f1對象擷取對象stu的gender屬性值
        String gender = (String) f1.get(stu);
        System.out.println("性别:" + gender);

        // 獲得private修飾Field對象
        Field f2 = c.getDeclaredField("age");
        // 注意:private的屬性不能直接通路,需要暴力反射才可以
        // 設定取消權限檢查(暴力反射)
        f2.setAccessible(true);
        // 通過f1對象給對象stu的age屬性指派
        f2.setInt(stu,30);
        // 通過f2對象擷取對象stu的age屬性值
        int age = f2.getInt(stu);
        System.out.println("年齡:" + age);


        System.out.println("-------獲得所有public修飾的屬性--------");
        // 獲得所有public修飾的屬性
        Field[] fs1 = c.getFields();
        // 周遊數組
        for(Field f : fs1) {
            System.out.println(f);
        }

        System.out.println("-------獲得所有的屬性,包括private修飾--------");
        // 獲得所有的屬性,包括private修飾
        Field[] fs2 = c.getDeclaredFields();
        // 周遊數組
        for(Field f : fs2) {
            System.out.println(f);
        }
    }
}
           
  • 輸出結果
    反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

1.7 反射案例

1.7.1 案例說明

​ 編寫一個工廠方法可以根據配置檔案産任意類型的對象。

  • 例如有配置檔案stu.properties,存儲在項目的src檔案夾下,内容如下:

    class=com.itheima.reflect.Student

    name=rose

    gender=女

    age=18

  • 根據配置檔案資訊建立一個學生對象。

1.7.2 實作步驟分析

  1. 在項目src檔案中建立一個包:com.itheima.reflect,并在該包下建立Student類。
  2. Student類的屬性:String name,String gender,int age
  3. 定義一個工廠方法:createObject(),方法傳回值類型為:Object
  4. 建立Properties集合并讀取stu.properties檔案中的内容到集合中。
  5. 根據class獲得學生類全名,并通過反射技術獲得Class對象。
  6. 通過調用Class對象的方法建立學生對象。
  7. 周遊Properties集合,利用反射技術給學生成員變量指派。
  8. 傳回封裝好資料的學生對象。

1.7.3 案例代碼

/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima.reflect
 * @date 2018/1/26
 */
public class Demo06 {

    public static void main(String[] args){
        // 擷取對象
        Student stu = (Student) createObject();
        // 輸出對象
        System.out.println(stu);
    }

    /**
     * 根據配置檔案建立對象
     */
    public static Object createObject(){
        try {
            // 建立屬性集合
            Properties pro = new Properties();
            // 從檔案中加載内容到集合中
            pro.load(Demo06.class.getResourceAsStream("/stu.properties"));

            // 從集合中獲得類名
            String className = pro.getProperty("class");
            // 通過反射獲得Class對象
            Class c = Class.forName(className);
            // 快速建立對象
            Object obj = c.newInstance();
            // 周遊集合
            Set<String> names = pro.stringPropertyNames();
            for (String name : names) {
                // 判斷name是否class
                if (name.equals("class")) continue;
                // 獲得值
                String value = pro.getProperty(name);
                // name:成員變量名
                // 根據成員變量名獲得對應的Field對象
                Field f = c.getDeclaredField(name);
                // 暴力反射
                f.setAccessible(true);
                // 獲得成員變量的類型
                Class typeClass = f.getType();
                if(typeClass == int.class){  // 判斷成員變量的資料類型是否是int類型
                    f.setInt(obj, Integer.parseInt(value));
                } else {
                    // 給f對象的指派
                    f.set(obj, value);
                }
            }
            // 傳回對象
            return obj;
        }   catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
           

1.7.4 案例小結

​ 該反射案例的目的是讓同學們感受反射機制的強大之處,在後面即将學習的Spring架構中就會有大量根據配置檔案資訊建立對象的過程,其内部的原理和我們這個案例的原理是一樣,有這個案例做基礎,以後學到spring架構時就會容易了解了。

​ 可能有同學有這樣的想法,反射機制确實是強大,但是如果從每次配置檔案中讀取資訊給對象屬性指派時都需要寫這麼複雜的代碼,做這麼多條件判斷的話,那就會嚴重影響工作效率,有沒有更好的方式來更友善,更有效率的解決這個問題?答案就是:有,如果想使用更友善的方式給對象封裝資料的話,我們可以使用一個非常友善的第三方工具:BeanUtils。接下來就來看看BeanUtils是什麼以及怎麼使用。

第2章 BeanUtils

2.1 BeanUtils的基本概述

2.2.1 BeanUtils概述

​ BeanUtils是Apache commons元件的成員之一,主要用于簡化JavaBean封裝資料的操作。常用的操作有以下三個:

  • 對JavaBean的屬性進行指派和取值。
  • 将一個JavaBean所有屬性指派給另一個JavaBean對象中。
  • 将一個Map集合的資料封裝到一個JavaBean對象中。
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

2.2.2 JavaBean概述

  1. JavaBean就是一個類,但該類需要滿足以下三個條件:
    • 類必須使用public修飾。
    • 提供無參數的構造器。
    • 提供getter和setter方法通路屬性。
  2. JavaBean的兩個重要概念
    • 字段:就是成員變量,字段名就是成員變量名。
    • 屬性:屬性名通過setter/getter方法去掉set/get字首,首字母小寫獲得。
      • 比如:setName() --> Name --> name
      • 一般情況下,字段名稱和屬性名稱是一緻的。
  3. 字段名和屬性名不一緻的情況
// 成員變量
private String description;
// getter & setter 方法
public String getDesc(){
  	return this.description;
}
public void setDesc(String desc) {
  	this.descripiton = desc;
}
           
  • 此時字段名為:description,屬性名為:desc

2.2.3 BeanUtils相關Jar包

  1. 下載下傳位址:http://commons.apache.org/
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 相關的jar包
commons-beanutils-1.9.3.jar   // 工具核心包
commons-logging-1.2.jar       // 日志記錄包 
commons-collections-3.2.2.jar // 增強的集合包 
           

2.2 BeanUtils的基本使用

2.2.1 導入相關jar包

  1. 在項目根目錄下建立lib檔案夾,并将jar包複制到該檔案夾下,如下圖:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 添加依賴:關聯lib檔案夾,通知idea去到該檔案夾找jar。如下圖:
    反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  2. 如上圖,當執行到第5步時,在彈出對話框中找到項目中lib檔案夾,點選打開之後如下圖:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 點選OK完成導入jar操作,此時就可以在項目中使用BeanUtils工具類提供的功能了。

2.2.2 BeanUtils工具類中常用方法

  1. public static void setProperty(Object bean, String name, Object value)
    給指定對象bean的指定name屬性指派為指定值value。
    如果指定的屬性不存在,則什麼也不發生。
               
  2. public static String getProperty(Object bean, String name)
    擷取指定對象bean指定name屬性的值。
    如果指定的屬性不存在,則會抛出異常。
    注意;當屬性的類型是數組類型時,擷取到的值數組中的第一個值。
               
  3. public static void copyProperties(Object dest, Object orig)
    将對象orig的屬性值指派給對象dest對象對應的屬性
    注意:隻有屬性名名相同且類型一緻的才會指派成功。
               
  4. public static void populate(Object bean, Map<String, ? extends Object> properties)
    将一個Map集合中的資料封裝到指定對象bean中
    注意:對象bean的屬性名和Map集合中鍵要相同。
               

2.2.3 BeanUtils常用操作示範

2.2.3.1 建立一個JavaBean類:Student

/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima
 * @date 2018/1/25
 */
public class Student {
    // 姓名
    private String name;
    // 性别
    private String gender;
    // 年齡
    private int age;
    // 愛好
    private String[] hobbies;

    public Student() {
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String[] getHobbies() {
        return hobbies;
    }

    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", hobbies=" + Arrays.toString(hobbies) +
                '}';
    }
}
           

2.2.3.2 對JavaBean的屬性進行指派和取值

/**
 * @author pkxing
 * @version 1.0
 * @description 對JavaBean的屬性進行指派和取值。
 * @date 2018/1/25
 */
public class BeanUtilsDemo01 {
  	/**
  	 public static void setProperty(Object bean, String name, Object value)
		給指定對象bean的指定name屬性指派為指定值value。
		如果指定的屬性不存在,則什麼也不發生。
		
	public static String getProperty(Object bean, String name)
		擷取指定對象bean指定name屬性的值。
		如果指定的屬性不存在,則會抛出異常。
		注意;當屬性的類型是數組類型時,擷取到的值數組中的第一個值。
  	 */
    public static void main(String[] args) throws Exception {
        // 建立學生對象
        Student stu = new Student();
        // 調用BeanUtils工具類的方法給對象屬性指派
        BeanUtils.setProperty(stu,"name","風清揚");
        BeanUtils.setProperty(stu,"gender","男");
        BeanUtils.setProperty(stu,"age",40);
        BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代碼","打籃球"});
        // 輸出對象到控制台
        System.out.println(stu);
        // 調用BeanUtils工具類的方法擷取對象屬性值
        String name = BeanUtils.getProperty(stu,"name");
        String gender = BeanUtils.getProperty(stu,"gender");
        String age = BeanUtils.getProperty(stu,"age");
        String hobbies = BeanUtils.getProperty(stu,"hobbies");
        System.out.println("姓名:"+ name);
        System.out.println("性别:"+ gender);
        System.out.println("年齡:" + age);
        System.out.println("愛好:" + hobbies);
    }
}
           
  • 輸出結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

2.2.3.3 将一個JavaBean對象的屬性指派給另一個JavaBean對象。

  1. 相同類型的對象之間屬性指派
/**
 * @author pkxing
 * @version 1.0
 * @description 将一個JavaBean對象的屬性指派給另一個JavaBean對象。
 * @date 2018/1/25
 */
public class BeanUtilsDemo02 {
    /**
     	public static void copyProperties(Object dest, Object orig)
		将對象orig的屬性值指派給對象dest對象對應的屬性
		注意:隻有屬性名名相同且類型一緻的才會指派成功。
     * @throws Exception
     */
    public static void main(String[] args)throws Exception{
        // 建立學生對象
        Student stu = new Student();
        // 調用BeanUtils工具類的方法給對象屬性指派
        BeanUtils.setProperty(stu,"name","風清揚");
        BeanUtils.setProperty(stu,"gender","男");
        BeanUtils.setProperty(stu,"age",40);
        BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代碼","打籃球"});

        // 再建立一個學生對象
        Student newStu = new Student();
        // 調用BeanUtils工具類的方法将stu對象的屬性值指派給newStu對象
        BeanUtils.copyProperties(newStu,stu);
        // 輸出stu和newStu對象
        System.out.println(stu);
        System.out.println(newStu);
    }
}
           
  • 輸入結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 不同類型的對象之間屬性指派:隻有屬性名相同的才會被指派
  • 建立JavaBean使用者類:User
public class User {
    // 姓名
    private String name;
    // 性别
    private String gender;
    // 位址
    private String address;
    // 年齡
    private String age;

    public User() {

    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", address='" + address + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}
           
  • 示例代碼
/**
 * @author pkxing
 * @version 1.0
 * @description 将一個JavaBean對象的屬性指派給另一個JavaBean對象。
 * @date 2018/1/25
 */
public class BeanUtilsDemo03 {
    /**
     	public static void copyProperties(Object dest, Object orig)
		将對象orig的屬性值指派給對象dest對象對應的屬性
		注意:隻有屬性名名相同且類型一緻的才會指派成功。
     */
    public static void main(String[] args)throws Exception {
        // 建立學生對象
        Student stu = new Student();
        // 調用BeanUtils工具類的方法給對象屬性指派
        BeanUtils.setProperty(stu,"name","風清揚");
        BeanUtils.setProperty(stu,"gender","男");
        BeanUtils.setProperty(stu,"age",40);
        BeanUtils.setProperty(stu,"aaa","xxx");
        BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代碼","打籃球"});

        // 建立使用者對象
        User user = new User();
        // 調用BeanUtils工具類的方法将stu對象的屬性值指派給user對象
        BeanUtils.copyProperties(user,stu);
        // 輸出stu和user對象
        System.out.println(user);
        System.out.println(stu);
    }
}
           
  • 輸出結果
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

2.2.3.4 将一個Map集合的資料封裝到一個JavaBean對象中。(重點)

/**
 * @author pkxing
 * @version 1.0
 * @description 将一個Map集合的資料封裝到一個JavaBean對象中。
 * @date 2018/1/25
 */
public class BeanUtilsDemo04 {
    /**
     * 
     public static void populate(Object bean, Map<String, ? extends Object> properties)
     将一個Map集合中的資料封裝到指定對象bean中
     注意:對象bean的屬性名和Map集合中鍵要相同。
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        // 建立map集合
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("name","林青霞");
        map.put("gender","女");
        map.put("age","38");
        map.put("hobbies",new String[]{"唱歌","跳舞"});
        // 建立學生對象
        Student stu = new Student();
        System.out.println("封裝前:" + stu);
        // 調用BeanUtils工具類的方法将map資料封裝到stu中
        BeanUtils.populate(stu,map);
        // 輸出對象stu
        System.out.println("封裝後:" + stu);
    }
}
           
  • 輸出結果
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

第3章 注解

3.1 注解的概述

3.1.1 注解的概念

  • 注解是JDK1.5的新特性。
  • 注解相當一種标記,是類的組成部分,可以給類攜帶一些額外的資訊。
  • 标記(注解)可以加在包,類,字段,方法,方法參數以及局部變量上。
  • 注解是給編譯器或JVM看的,編譯器或JVM可以根據注解來完成對應的功能。

    注解(Annotation)相當于一種标記,在程式中加入注解就等于為程式打上某種标記,以後,javac編譯器、開發工具和其他程式可以通過反射來了解你的類及各種元素上有無何種 标記,看你的程式有什麼标記,就去幹相應的事,标記可以加在包、類,屬性、方法,方法的參數以及局部變量上。

3.1.2 注解的作用

注解的作用就是給程式帶入參數。

以下幾個常用操作中都使用到了注解:

  1. 生成幫助文檔:@author和@version
    • @author:用來辨別作者姓名。
    • @version:用于辨別對象的版本号,适用範圍:檔案、類、方法。
      • 使用**@author和@version注解就是告訴Javadoc工具**在生成幫助文檔時把作者姓名和版本号也标記在文檔中。如下圖:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 編譯檢查:@Override
    • @Override:用來修飾方法聲明。
      • 用來告訴編譯器該方法是重寫父類中的方法,如果父類不存在該方法,則編譯失敗。如下圖
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  1. 架構的配置(架構=代碼+配置)
    • 具體使用請關注架構課程的内容的學習。
    • 也就是代替架構中的配置檔案
  2. 作為标記,就比如說Junit單元測試中:@Test

3.1.3 常見注解

  1. @author:用來辨別作者名,eclipse開發工具預設的是系統使用者名。
  2. @version:用于辨別對象的版本号,适用範圍:檔案、類、方法。
  3. @Override :用來修飾方法聲明,告訴編譯器該方法是重寫父類中的方法,如果父類不存在該方法,則編譯失敗。

3.2 自定義注解

3.2.1 定義格式

public @interface 注解名{

}
如:定義一個名為Student的注解
public @interface Student {

} 
           
  • 以上定義出來的注解就是一個最簡單的注解了,但這樣的注解意義不大,因為注解中沒有任何内容,就好像我們定義一個類而這個類中沒有任何成員變量和方法一樣,這樣的類意義也是不大的,是以在定義注解時會在裡面添加一些成員來讓注解功能更加強大,這些成員就是屬性。接下來就看看怎麼給注解添加屬性。

3.2.2 注解的屬性

  1. 屬性的作用
    • 可以讓使用者在使用注解時傳遞參數,讓注解的功能更加強大。
  2. 屬性的格式
    • 格式1:資料類型 屬性名();
    • 格式2:資料類型 屬性名() default 預設值;
  3. 屬性定義示例
    public @interface Student {
      String name(); // 姓名
      int age() default 18; // 年齡
      String gender() default "男"; // 性别
    } 
    // 該注解就有了三個屬性:name,age,gender
               
  4. 屬性适用的資料類型
    • 八種基本資料類型(int,float,boolean,byte,double,char,long,short)
    • String類型,Class類型,枚舉類型,注解類型
    • 以上所有類型的一維數組

3.3 使用自定義注解

3.3.1 定義注解

  1. 定義一個注解:Book
    • 包含屬性:String value() 書名
    • 包含屬性:double price() 價格,預設值為 100
    • 包含屬性:String[] authors() 多位作者
  2. 代碼實作
    public @interface Book {
            // 書名
            String value();
            // 價格
            double price() default 100;
            // 多位作者
            String[] authors();
    }
               

3.3.2 使用注解

  1. 定義類在成員方法上使用Book注解
    /**
     * @author pkxing
     * @version 1.0
     * @description 書架類
     * @date 2018/1/26
     */
    public class BookShelf {
      
        @Book(value = "西遊記",price = 998,authors = {"吳承恩","白求恩"})
        public void showBook(){
    
        }
    }
               
  2. 使用注意事項
    • 如果屬性有預設值,則使用注解的時候,這個屬性可以不用指派。
    • 如果屬性沒有預設值,那麼在使用注解時一定要給屬性指派。

3.3.3 特殊屬性value

  1. 當注解中隻有一個屬性且名稱是value,在使用注解時給value屬性指派可以直接給屬性值,無論value是單值元素還是數組類型。
// 定義注解Book
public @interface Book {
        // 書名
        String value();
}

// 使用注解Book
public class BookShelf {
    @Book("西遊記")
    public void showBook(){

    }
}
或
public class BookShelf {
    @Book(value="西遊記")
    public void showBook(){

    }
}
           
  1. 如果注解中除了value屬性還有其他屬性,且至少有一個屬性沒有預設值,則在使用注解給屬性指派時,value屬性名不能省略。
// 定義注解Book
public @interface Book {
        // 書名
        String value();
  		// 價格
        double price() default 100;
        // 多位作者
        String[] authors();
}
// 使用Book注解:正确方式
@Book(value="紅樓夢",authors = "曹雪芹")
public class BookShelf {
  // 使用Book注解:正确方式
    @Book(value="西遊記",authors = {"吳承恩","白求恩"})
    public void showBook(){

    }
}

// 使用Book注解:錯誤方式
public class BookShelf {
    @Book("西遊記",authors = {"吳承恩","白求恩"})
    public void showBook(){

    }
}
// 此時value屬性名不能省略了。
           

3.3.4 問題分析

​ 現在我們已經學會了如何定義注解以及如何使用注解了,可能細心的同學會發現一個問題:我們定義的注解是可以使用在任何成員上的,比如剛剛Book注解的使用:

// 定義注解Book
public @interface Book {
        // 書名
        String value();
  		// 價格
        double price() default 100;
        // 多位作者
        String[] authors();
}
// 使用Book注解:正确方式
@Book(value="紅樓夢",authors = "曹雪芹")
public class BookShelf {
  // 使用Book注解:正确方式
    @Book(value="西遊記",authors = {"吳承恩","白求恩"})
    public void showBook(){

    }
}
           
  • 此時Book同時使用在了類定義上或成員方法上,編譯器也沒有報錯,因為預設情況下,注解可以用在任何地方,比如類,成員方法,構造方法,成員變量等地方。
  • 如果要限制注解的使用位置怎麼辦?那就要學習一個新的知識點**:元注解**。接下來就來看看什麼是元注解以及怎麼使用。

3.4 注解之元注解

3.4.1 元注解的概述

  • Java API提供的注解
  • 專門用來定義注解的注解。
  • 任何Java官方提供的非元注解的定義中都使用到了元注解。

3.4.2 常用元注解

  • @Target
  • @Retention

3.4.2.1 元注解之@Target

  • 作用:指明此注解用在哪個位置,如果不寫預設是任何地方都可以使用。
    • 可選的參數值在枚舉類ElemenetType中包括:
      TYPE: 用在類,接口上
       FIELD:用在成員變量上
       METHOD: 用在方法上
       PARAMETER:用在參數上
       CONSTRUCTOR:用在構造方法上
       LOCAL_VARIABLE:用在局部變量上
                 

3.4.2.2 元注解之@Retention

  • 作用:定義該注解的生命周期(有效範圍)。
    • 可選的參數值在枚舉類型RetentionPolicy中包括
      SOURCE:注解隻存在于Java源代碼中,編譯生成的位元組碼檔案中就不存在了。
      CLASS:注解存在于Java源代碼、編譯以後的位元組碼檔案中,運作的時候記憶體中沒有,預設值。
      RUNTIME:注解存在于Java源代碼中、編譯以後的位元組碼檔案中、運作時記憶體中,程式可以通過反射擷取該注解。
                 

3.4.3 元注解使用示例

@Target({ElementType.METHOD,ElementType.TYPE})
@interface Stu{
    String name();
}

// 類
@Stu(name="jack")
public class AnnotationDemo02 {

    // 成員變量
    @Stu(name = "lily")  // 編譯失敗
    private String gender;

    // 成員方法
    @Stu(name="rose")
    public void  test(){

    }

    // 構造方法
    @Stu(name="lucy") // 編譯失敗
    public AnnotationDemo02(){}
}
           
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

3.5 注解解析(難點)

3.4.1 什麼是注解解析

​ 通過Java技術擷取注解資料的過程則稱為注解解析。

3.4.2 與注解解析相關的接口

  • Anontation:所有注解類型的公共接口,類似所有類的父類是Object。
  • AnnotatedElement:定義了與注解解析相關的方法,常用方法以下四個:
    boolean isAnnotationPresent(Class annotationClass); 判斷目前對象是否有指定的注解,有則傳回true,否則傳回false。
    
    T getAnnotation(Class<T> annotationClass);  獲得目前對象上指定的注解對象。
    
    Annotation[] getAnnotations(); 獲得目前對象及其從父類上繼承的所有的注解對象。
    Annotation[] getDeclaredAnnotations();獲得目前對象上所有的注解對象,不包括父類的。
               

3.4.3 擷取注解資料的原理

​ 注解作用在那個成員上,就通過反射獲得該成員的對象來得到它的注解。

  • 如注解作用在方法上,就通過方法(Method)對象得到它的注解
    // 得到方法對象
     Method method = clazz.getDeclaredMethod("方法名"); 
     // 根據注解名得到方法上的注解對象
     Book book = method.getAnnotation(Book.class);  
               
  • 如注解作用在類上,就通過Class對象得到它的注解
    // 獲得Class對象
    Class c = 類名.class;
    // 根據注解的Class獲得使用在類上的注解對象
    Book book = c.getAnnotation(Book.class);
               

3.4.4 使用反射擷取注解的資料

3.4.4.1 需求說明

  1. 定義注解Book,要求如下:
    • 包含屬性:String value() 書名
    • 包含屬性:double price() 價格,預設值為 100
    • 包含屬性:String[] authors() 多位作者
    • 限制注解使用的位置:類和成員方法上
    • 指定注解的有效範圍:RUNTIME
  2. 定義BookStore類,在類和成員方法上使用Book注解
  3. 定義TestAnnotation測試類擷取Book注解上的資料

3.4.4.2 代碼實作

  1. 注解Book
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
        // 書名
        String value();
        // 價格
        double price() default 100;
        // 作者
        String[] authors();
}
           
  1. BookStore類
@Book(value = "紅樓夢",authors = "曹雪芹",,price = 998)
public class BookStore {

    @Book(value = "西遊記",authors = "吳承恩")
    public void buyBook(){

    }
}
           
  1. TestAnnotation類
/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima.annotation
 * @date 2018/1/26
 */
public class TestAnnotation {
    public static void main(String[] args)  throws Exception{
        System.out.println("---------擷取類上注解的資料----------");
        test01();
        System.out.println("---------擷取成員方法上注解的資料----------");
        test02();
    }

    /**
     * 擷取BookStore類上使用的Book注解資料
     */
    public static void test01(){
        // 獲得BookStore類對應的Class對象
        Class c = BookStore.class;
        // 根據注解Class對象擷取注解對象
        Book book = (Book) c.getAnnotation(Book.class);
        // 輸出book注解屬性值
        System.out.println("書名:" + book.value());
        System.out.println("價格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }

    /**
     * 擷取BookStore類成員方法buyBook使用的Book注解資料
     */
    public static void test02() throws Exception{
        // 獲得BookStore類對應的Class對象
        Class c = BookStore.class;
        // 獲得成員方法buyBook對應的Method對象
        Method m = c.getMethod("buyBook");
        // 根據注解Class對象擷取注解對象
        Book book = (Book) m.getAnnotation(Book.class);
        // 輸出book注解屬性值
        System.out.println("書名:" + book.value());
        System.out.println("價格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }
}
           
  • 輸入結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

3.4.4.3 存在問題分析

  • TestAnnotation類在擷取注解資料時處理得不夠嚴謹,假如出現下面的其中一種情況:
    1. 把BookStore類或成員方法buyBook上的注解删除。
    2. 将Book注解的有效範圍改為:CLASS。
    再運作TestAnnotation類代碼則會出現空指針異常,如下圖所示:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  • 原因分析如下圖
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解
  • 解決方案
    • 在擷取注解對象時,先判斷是否有使用注解,如果有,才擷取,否則就不用擷取。
    • 修改TestAnnotation類的代碼,修改後如下
    public class TestAnnotation {
        public static void main(String[] args)  throws Exception{
            System.out.println("---------擷取類上注解的資料----------");
            test01();
            System.out.println("---------擷取成員方法上注解的資料----------");
            test02();
        }
    
        /**
         * 擷取BookStore類上使用的Book注解資料
         */
        public static void test01(){
            // 獲得BookStore類對應的Class對象
            Class c = BookStore.class;
            // 判斷BookStore類是否使用了Book注解
            if(c.isAnnotationPresent(Book.class)) {
                // 根據注解Class對象擷取注解對象
                Book book = (Book) c.getAnnotation(Book.class);
                // 輸出book注解屬性值
                System.out.println("書名:" + book.value());
                System.out.println("價格:" + book.price());
                System.out.println("作者:" + Arrays.toString(book.authors()));
            }
        }
    
        /**
         * 擷取BookStore類成員方法buyBook使用的Book注解資料
         */
        public static void test02() throws Exception{
            // 獲得BookStore類對應的Class對象
            Class c = BookStore.class;
            // 獲得成員方法buyBook對應的Method對象
            Method m = c.getMethod("buyBook");
            // 判斷成員方法buyBook上是否使用了Book注解
            if(m.isAnnotationPresent(Book.class)) {
                // 根據注解Class對象擷取注解對象
                Book book = (Book) m.getAnnotation(Book.class);
                // 輸出book注解屬性值
                System.out.println("書名:" + book.value());
                System.out.println("價格:" + book.price());
                System.out.println("作者:" + Arrays.toString(book.authors()));
            }
        }
    }
               

3.6 注解案例

3.5.1 案例說明

​ 模拟Junit測試的@Test

3.5.2 案例分析

  1. 模拟Junit測試的注釋@Test,首先需要編寫自定義注解@MyTest,并添加元注解,保證自定義注解隻能修飾方法,且在運作時可以獲得。
  2. 然後編寫目标類(測試類),然後給目标方法(測試方法)使用 @MyTest注解,編寫三個方法,其中兩個加上@MyTest注解。
  3. 最後編寫調用類,使用main方法調用目标類,模拟Junit的運作,隻要有@MyTest注釋的方法都會運作。

3.5.3 案例代碼

  1. 注解MyTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
           
  1. 目标類MyTestDemo
public class MyTestDemo {
    @MyTest
    public  void  test01(){
        System.out.println("test01");
    }

    public  void  test02(){
        System.out.println("test02");
    }

    @MyTest
    public  void  test03(){
        System.out.println("test03");
    }
}
           
  1. 調用類TestMyTest
public class TestMyTest {
    public static void main(String[] args) throws  Exception{
        // 獲得MyTestDemo類Class對象
        Class c = MyTestDemo.class;
        // 獲得所有的成員方法對象
        Method[] methods = c.getMethods();
        // 建立MyTestDemo類對象
        Object obj = c.newInstance();
        // 周遊數組
        for (Method m:methods) {
            // 判斷方法m上是否使用注解MyTest
            if(m.isAnnotationPresent(MyTest.class)){
                // 執行方法m
                m.invoke(obj);
            }
        }
    }
}
           
  • 輸出結果:
反射、BeanUtils、注解(介紹篇)反射、BeanUtils、注解第1章 反射第2章 BeanUtils第3章 注解

繼續閱讀