Spring源碼解析(二)反射

目錄

反射概述

使用反射生成並操作對象

創建對象

調用方法

訪問成員變量值


反射概述

spring源碼中有很多地方涉及到反射的知識,這裏還是有必要再複習一下。

Java程序中的許多對象在運行時都會出現兩種類型,編譯時類型和運行時類型,例如代碼Person p = new Student(), 這行代碼將會生成一個p變量,該變量的編譯時類型爲Person,運行時類型爲Student,除此之外,還有更極端的情形,程序在運行時接收到外部傳入的一個對象,該對象的編譯時類型是Object,但程序又需要調用該對象運行時類型的方法。

解決上述問題最好的辦法就是利用反射。每個類被加載後,系統就會爲該類生成一個對應的對象,通過該Class對象就可以訪問到JVM中這個類,在Java程序中獲得Class對象通常有三種方式。

  • 使用Class類的forName(String clazzName) 靜態方法。該方法需要傳入字符串參數,該字符串參數的值是某個類的全限定類名
  • 調用某個類的class屬性來獲取該類對應的Class對象,例如Person.class將會返回Person類對應的Class對象
  • 調動某個對象的getClass()方法,該方法是java.lang.Object 類中一個方法,所有的Java對象都可以調用該方法,該方法將會返回該對象所屬對象所屬類的Class對象。

相比之下,第二種方式有如下兩種優勢

  • 代碼更安全,程序在編一階段就可以檢查需要訪問的Class對象是否存在
  • 程序性能更好,因爲這種方式無需調用方法

Class類提供了大量的實例方法來獲取該Class對象所對應類的詳細信息,具體可以通過查詢Api。Java-Api文檔

獲取構造方法:

獲取Method:

獲取Field:

使用反射生成並操作對象

創建對象

通過反射來生成對象需要先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建該Class對象對應類的實例,通過這種方式可以選擇使用指定的構造器來創建實例。

下面實現一個簡單的根據配置文件創建對象的操作:(代碼地址https://github.com/lizhjian/crazyJava.git    ObjectPoolFactory類)

public class ObjectPoolFactory {

    private Map<String, Object> objectPoool = new HashMap<>();

    private Object createObject(String clazzName) throws Exception {
        //根據類名進行實例化
        Class<?> clazz = Class.forName(clazzName);
        return clazz.getConstructor().newInstance();
    }

    public void initPool(String fileName) throws  Exception{
        try {
            //讀取和加載配置文件
            FileInputStream fis = new FileInputStream(fileName);
            Properties props = new Properties();
            props.load(fis);
            for (String name : props.stringPropertyNames()){
                //實例化類對應Spring框架中的beanDefinitionMap
                objectPoool.put(name, createObject(props.getProperty(name)));
            }
        }catch (Exception ex){
            System.out.println("讀取異常");
        }
    }

    public Object getObject(String name){
        return objectPoool.get(name);
    }

    public static void main(String[] args) throws  Exception{
        ObjectPoolFactory pf = new ObjectPoolFactory();
        pf.initPool("obj.txt");

        System.out.println(pf.getObject("a"));
        System.out.println(pf.getObject("b"));
    }

}

 配置文件如下:

a=java.util.Date
b=java.lang.String

輸出:

可以看到上面成功的實例化了配置文件中 a 及 b對應的類。

調用方法

當獲得某個類對應的Class對象後,就可以通過該class對象的getMethods()方法或者getMethod()方法來獲取全部方法或指定的方法。

具體代碼如下:(github:    https://github.com/lizhjian/crazyJava.git    ExtendedObjectPoolFactory類)

/**
 * 1.加載配置文件讀取key-value
 * 2.將value實例化對應的實體
 * 3.拼setTitle字符串
 * 4.根據setTitle字符串找到對應的setTitle的方法
 * 5.取出a對應的實例 及a%setitle對應的value 調用setTitle.invoke(a, value)
 */
public class ExtendedObjectPoolFactory {

    private Map<String, Object> objectPoool = new HashMap<>();
    private Properties config = new Properties();

    private Object createObject(String clazzName) throws Exception {
        Class<?> clazz = Class.forName(clazzName);
        return clazz.getConstructor().newInstance();
    }

    public void initPool(String fileName) throws  Exception{
        try {
            FileInputStream fis = new FileInputStream(fileName);

            config.load(fis);

            for (String name : config.stringPropertyNames()){
                if(!name.contains("%")){
                    objectPoool.put(name, createObject(config.getProperty(name)));
                }
            }
        }catch (Exception ex){
            System.out.println("讀取異常");
        }
    }
    public void  initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException{
        for(String name : config.stringPropertyNames()){
            if(name.contains("%")){
               //將配置文件中的key按照%分割
                String[] objAndProp = name.split("%");
                Object target = getObject(objAndProp[0]);
                // 獲取setXxx方法字符串
                String mtdName = "set"+objAndProp[1].substring(0, 1).toUpperCase()
                        + objAndProp[1].substring(1);

                Class<?> targetClass = target.getClass();
                Method mtd = targetClass.getMethod(mtdName, String.class);
                mtd.invoke(target, config.getProperty(name));
            }
        }
    }


    public Object getObject(String name){
        return objectPoool.get(name);
    }

    public static void main(String[] args) throws  Exception{
        ExtendedObjectPoolFactory pf = new ExtendedObjectPoolFactory();
        pf.initPool("objExt.txt");
        pf.initProperty();

        System.out.println(pf.getObject("a"));
    }

}

配置文件如下:

a=javax.swing.JFrame
b=javax.swing.JLabel
a%title=Test Title

過程分爲以下幾步

 1.加載配置文件讀取key-value
 2.將value實例化對應的實體
 3.拼setTitle字符串
 4.根據setTitle字符串找到對應的setTitle的方法
 5.取出a對應的實例 及a%setitle對應的value 調用setTitle.invoke(a, value)


結果:

Spring框架就是通過這種方式將成員變量值以及依賴對象等都放在配置文件中進行管理的,從而實現了很好的解耦,這也是Spring框架IoC的祕密。

訪問成員變量值

通過Class對象的getFields()和getField()方法可以獲取該類所包含的全部成員變量或指定成員變量,Field提供了兩組方法來設置和獲取成員變量

getXxx(Object o)和setXxx(Object o, value),當字段爲非8種基本類型時,去掉Xxx,代碼如下所示(https://github.com/lizhjian/crazyJava.git  FieldTest)

以上就是反射的全部內容。後一節我們將介紹基於反射的代理,萬丈高樓平地起,打好基礎纔是關鍵。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章