小白學習Java反射與動態代理

1、反射介紹

1.0 動態語言和靜態語言
  1. 動態語言javascript

    • 是一類在運行是能夠改變其結構的語言:例如新的函數,對象,甚至代碼能夠被引進,已有的函數能夠被刪除或者是其餘結構上的變化。通俗點就是說能夠在運行時代碼能夠根據某些條件改變自身結構
    • 主要動態語言:Object-C、JavaScript、PHP、Python等
  2. 靜態語言java

    • 與動態語言向對應的,運行時結構不可變的語言就是靜態語言。如Java、C、C++
    • Java雖然不是動態語言,但Java能夠稱之爲「準動態語言」。即Java有必定的動態性,咱們能夠利用反射機制得到相似動態語言的特性。
1.1 什麼是反射

反射就是Reflection,Java的反射是指程序在運行期能夠拿到一個對象的全部信息。spring

正常狀況下,若是咱們要調用一個對象的方法,或者訪問一個對象的字段,一般會傳入對象實例:編程

// Main.java
import com.itranswarp.learnjava.Person;

public class Main {
    String getFullName(Person p) {
        return p.getFirstName() + " " + p.getLastName();
    }
}

可是,若是不能得到Person類,只有一個Object實例,好比這樣:數組

String getFullName(Object obj) {
    return ???
}

怎麼辦?有童鞋會說:強制轉型啊!安全

String getFullName(Object obj) {
    Person p = (Person) obj;
    return p.getFirstName() + " " + p.getLastName();
}

強制轉型的時候,你會發現一個問題:編譯上面的代碼,仍然須要引用Person類。否則,去掉import語句,你看能不能編譯經過?框架

因此,反射是爲了解決在運行期,對某個實例一無所知的狀況下,如何調用其方法。dom

1.2 對象編譯在JVM分佈狀況

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gges29pxjoj30tw0t2q34.jpg" alt="image-20200205095809050" style="zoom:50%;float:left" />編程語言

String類、Dog類在方法區中的類文件代碼中,存了屬性、構造方法、方法等信息,其實在Class中也就定義了Field、Constructor、Method這些信息,至關於定義一個對象的接口函數

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggerrn9w2xj30u00coaaq.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

1.3 Class類

除了int等基本類型外,Java的其餘類型所有都是class(包括interface)。例如:

  • String
  • Object
  • Runnable
  • Exception
  • ...

仔細思考,咱們能夠得出結論:class(包括interface)的本質是數據類型(Type)。無繼承關係的數據類型沒法賦值:

Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!

class是由JVM在執行過程當中動態加載的。JVM在第一次讀取到一種class類型時,將其加載進內存。

每加載一種class,JVM就爲其建立一個Class類型的實例,並關聯起來。注意:這裏的Class類型是一個名叫Classclass。它長這樣:

public final class Class {
    private Class() {}
}

String類爲例,當JVM加載String類時,它首先讀取String.class文件到內存,而後,爲String類建立一個Class實例並關聯起來:

Class cls = new Class(String);

這個Class實例是JVM內部建立的,若是咱們查看JDK源碼,能夠發現Class類的構造方法是private,只有JVM能建立Class實例,咱們本身的Java程序是沒法建立Class實例的。

因此,JVM持有的每一個Class實例都指向一個數據類型(classinterface):

┌───────────────────────────┐
│      Class Instance       │──────> String
├───────────────────────────┤
│name = "java.lang.String"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │──────> Random
├───────────────────────────┤
│name = "java.util.Random"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │──────> Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘

一個Class實例包含了該class的全部完整信息:

┌───────────────────────────┐
│      Class Instance       │──────> String
├───────────────────────────┤
│name = "java.lang.String"  │
├───────────────────────────┤
│package = "java.lang"      │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,...   │
├───────────────────────────┤
│method = indexOf()...      │
└───────────────────────────┘

因爲JVM爲每一個加載的class建立了對應的Class實例,並在實例中保存了該class的全部信息,包括類名、包名、父類、實現的接口、全部方法、字段等,所以,若是獲取了某個Class實例,咱們就能夠經過這個Class實例獲取到該實例對應的class的全部信息。

這種經過Class實例獲取class信息的方法稱爲反射(Reflection)。

1.4 如何獲取一個Class實例

如何獲取一個classClass實例?有三個方法:

  1. 直接經過一個class的靜態變量class獲取:

    Class cls = String.class;
  2. 經過該實例變量提供的getClass()方法

    String s = "Hello";
    Class cls = s.getClass();
  3. 經過靜態方法Class.forName()

    Class cls = Class.forName("java.lang.String");

由於Class實例在JVM中是惟一的,因此,上述方法獲取的Class實例是同一個實例。能夠用==比較兩個Class實例:

Class cls1 = String.class;

String s = "Hello";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true
1.5 Class實例比較和instanceof區別
Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,由於n是Integer類型
boolean b2 = n instanceof Number; // true,由於n是Number類型的子類

boolean b3 = n.getClass() == Integer.class; // true,由於n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,由於Integer.class!=Number.class

instanceof不但匹配指定類型,還匹配指定類型的子類。而用==判斷class實例能夠精確地判斷數據類型,但不能做子類型比較。

一般狀況下,咱們應該用instanceof判斷數據類型,由於面向抽象編程的時候,咱們不關心具體的子類型。只有在須要精確判斷一個類型是否是某個class的時候,咱們才使用==判斷class實例。

1.6 Class實例中的基本信息
public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
    }
}

注意到數組(例如String[])也是一種Class,並且不一樣於String.class,它的類名是[Ljava.lang.String。此外,JVM爲每一種基本類型如int也建立了Class,經過int.class訪問。

若是獲取到了一個Class實例,咱們就能夠經過該Class實例來建立對應類型的實例:

// 獲取String的Class實例:
Class cls = String.class;
// 建立一個String實例:
String s = (String) cls.newInstance();

上述代碼至關於new String()。經過Class.newInstance()能夠建立類實例,它的侷限是:只能調用public的無參數構造方法。帶參數的構造方法,或者非public的構造方法都沒法經過Class.newInstance()被調用。

1.7 動態加載

JVM在執行Java程序的時候,並非一次性把全部用到的class所有加載到內存,而是第一次須要用到class時才加載。例如:

// Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}

當執行Main.java時,因爲用到了Main,所以,JVM首先會把Main.class加載到內存。然而,並不會加載Person.class,除非程序執行到create()方法,JVM發現須要加載Person類時,纔會首次加載Person.class。若是沒有執行create()方法,那麼Person.class根本就不會被加載。

這就是JVM動態加載class的特性。

動態加載class的特性對於Java程序很是重要。利用JVM動態加載class的特性,咱們才能在運行期根據條件加載不一樣的實現類。例如,Commons Logging老是優先使用Log4j,只有當Log4j不存在時,才使用JDK的logging。利用JVM動態加載特性,大體的實現代碼以下:

2、反射主要功能

反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。

Java 反射主要提供如下功能:

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具備的成員變量和方法(經過反射甚至能夠調用private方法);
  • 在運行時調用任意一個對象的方法
2.1 訪問字段

咱們先看看如何經過Class實例獲取字段信息。Class類提供瞭如下幾個方法來獲取字段:

  • Field getField(name):根據字段名獲取某個public的field(包括父類)
  • Field getDeclaredField(name):根據字段名獲取當前類的某個field(不包括父類)
  • Field[] getFields():獲取全部public的field(包括父類)
  • Field[] getDeclaredFields():獲取當前類的全部field(不包括父類)
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 獲取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 獲取繼承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 獲取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

class Student extends Person {
    public int score;
    private int grade;
}

class Person {
    public String name;
}

上述代碼首先獲取StudentClass實例,而後,分別獲取public字段、繼承的public字段以及private字段,打印出的Field相似:

public int Student.score
public java.lang.String Person.name
private int Student.grade

一個Field對象包含了一個字段的全部信息:

  • getName():返回字段名稱,例如,"name"
  • getType():返回字段類型,也是一個Class實例,例如,String.class
  • getModifiers():返回字段的修飾符,它是一個int,不一樣的bit表示不一樣的含義。

String類的value字段爲例,它的定義是:

public final class String {
    private final byte[] value;
}

咱們用反射獲取該字段的信息,代碼以下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]類型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
2.1.1 獲取字段值

利用反射拿到字段的一個Field實例只是第一步,咱們還能夠拿到一個實例對應的該字段的值。

例如,對於一個Person實例,咱們能夠先拿到name字段對應的Field,再獲取這個實例的name字段的值:

public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}

上述代碼先獲取Class實例,再獲取Field實例,而後,用Field.get(Object)獲取指定實例的指定字段的值。

運行代碼,若是不出意外,會獲得一個IllegalAccessException,這是由於name被定義爲一個private字段,正常狀況下,Main類沒法訪問Person類的private字段。要修復錯誤,能夠將private改成public,或者,在調用Object value = f.get(p);前,先寫一句:

f.setAccessible(true);

調用Field.setAccessible(true)的意思是,別管這個字段是否是public,一概容許訪問。

能夠試着加上上述語句,再運行代碼,就能夠打印出private字段的值。

有童鞋會問:若是使用反射能夠獲取private字段的值,那麼類的封裝還有什麼意義?

答案是正常狀況下,咱們老是經過p.name來訪問Personname字段,編譯器會根據publicprotectedprivate決定是否容許訪問字段,這樣就達到了數據封裝的目的。

而反射是一種很是規的用法,使用反射,首先代碼很是繁瑣,其次,它更多地是給工具或者底層框架來使用,目的是在不知道目標實例任何信息的狀況下,獲取特定字段的值。

此外,setAccessible(true)可能會失敗。若是JVM運行期存在SecurityManager,那麼它會根據規則進行檢查,有可能阻止setAccessible(true)。例如,某個SecurityManager可能不容許對javajavax開頭的package的類調用setAccessible(true),這樣能夠保證JVM核心庫的安全。

2.1.2 設置字段值

經過Field實例既然能夠獲取到指定實例的字段值,天然也能夠設置字段的值。

設置字段值是經過Field.set(Object, Object)實現的,其中第一個Object參數是指定的實例,第二個Object參數是待修改的值。示例代碼以下:

public class Main {

    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        f.set(p, "Xiao Hong");
        System.out.println(p.getName()); // "Xiao Hong"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
2.2 調用方法

咱們已經能經過Class實例獲取全部Field對象,一樣的,能夠經過Class實例獲取全部Method信息。Class類提供瞭如下幾個方法來獲取Method

  • Method getMethod(name, Class...):獲取某個publicMethod(包括父類)
  • Method getDeclaredMethod(name, Class...):獲取當前類的某個Method(不包括父類)
  • Method[] getMethods():獲取全部publicMethod(包括父類)
  • Method[] getDeclaredMethods():獲取當前類的全部Method(不包括父類)
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 獲取public方法getScore,參數爲String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 獲取繼承的public方法getName,無參數:
        System.out.println(stdClass.getMethod("getName"));
        // 獲取private方法getGrade,參數爲int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}

class Person {
    public String getName() {
        return "Person";
    }
}

上述代碼首先獲取StudentClass實例,而後,分別獲取public方法、繼承的public方法以及private方法,打印出的Method相似:

public int Student.getScore(java.lang.String)
public java.lang.String Person.getName()
private int Student.getGrade(int)

一個Method對象包含一個方法的全部信息:

  • getName():返回方法名稱,例如:"getScore"
  • getReturnType():返回方法返回值類型,也是一個Class實例,例如:String.class
  • getParameterTypes():返回方法的參數類型,是一個Class數組,例如:{String.class, int.class}
  • getModifiers():返回方法的修飾符,它是一個int,不一樣的bit表示不一樣的含義。
2.2.1 調用方法

當咱們獲取到一個Method對象時,就能夠對它進行調用。咱們如下面的代碼爲例:

String s = "Hello world";
String r = s.substring(6); // "world"

若是用反射來調用substring方法,須要如下代碼:

public class Main {
    public static void main(String[] args) throws Exception {
        // String對象:
        String s = "Hello world";
        // 獲取String substring(int)方法,參數爲int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s對象上調用該方法並獲取結果:
        String r = (String) m.invoke(s, 6);
        // 打印調用結果:
        System.out.println(r);
    }
}

注意到substring()有兩個重載方法,咱們獲取的是String substring(int)這個方法。思考一下如何獲取String substring(int, int)方法。

Method實例調用invoke就至關於調用該方法,invoke的第一個參數是對象實例,即在哪一個實例上調用該方法,後面的可變參數要與方法參數一致,不然將報錯

2.2.2 調用靜態方法

若是獲取到的Method表示一個靜態方法,調用靜態方法時,因爲無需指定實例對象,因此invoke方法傳入的第一個參數永遠爲null。咱們以Integer.parseInt(String)爲例:

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取Integer.parseInt(String)方法,參數爲String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 調用該靜態方法並獲取結果:
        Integer n = (Integer) m.invoke(null, "12345");
        // 打印調用結果:
        System.out.println(n);
    }
}
2.2.3 調用非public方法

和Field相似,對於非public方法,咱們雖然能夠經過Class.getDeclaredMethod()獲取該方法實例,但直接對其調用將獲得一個IllegalAccessException。爲了調用非public方法,咱們經過Method.setAccessible(true)容許其調用:

此外,setAccessible(true)可能會失敗。若是JVM運行期存在SecurityManager,那麼它會根據規則進行檢查,有可能阻止setAccessible(true)。例如,某個SecurityManager可能不容許對javajavax開頭的package的類調用setAccessible(true),這樣能夠保證JVM核心庫的安全。

2.2.4 多態方法

一個Person類定義了hello()方法,而且它的子類Student也覆寫了hello()方法,那麼,從Person.class獲取的Method,做用於Student實例時,調用的方法究竟是哪一個?

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取Person的hello方法:
        Method h = Person.class.getMethod("hello");
        // 對Student實例調用hello方法:
        h.invoke(new Student());
    }
}

class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}

class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}

運行上述代碼,發現打印出的是Student:hello,所以,使用反射調用方法時,仍然遵循多態原則:即老是調用實際類型的覆寫方法(若是存在)。上述的反射代碼:

Method m = Person.class.getMethod("hello");
m.invoke(new Student());

實際上至關於:

Person p = new Student();
p.hello();
2.3 調用構造方法

咱們一般使用new操做符建立新的實例:

Person p = new Person();

若是經過反射來建立新的實例,能夠調用Class提供的newInstance()方法:

Person p = Person.class.newInstance();

調用Class.newInstance()的侷限是,它只能調用該類的public無參數構造方法。若是構造方法帶有參數,或者不是public,就沒法直接經過Class.newInstance()來調用。

爲了調用任意的構造方法,Java的反射API提供了Constructor對象,它包含一個構造方法的全部信息,能夠建立一個實例。Constructor對象和Method很是相似,不一樣之處僅在於它是一個構造方法,而且,調用結果老是返回實例:

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取構造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 調用構造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 獲取構造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

經過Class實例獲取Constructor的方法以下:

  • getConstructor(Class...):獲取某個publicConstructor
  • getDeclaredConstructor(Class...):獲取某個Constructor
  • getConstructors():獲取全部publicConstructor
  • getDeclaredConstructors():獲取全部Constructor

注意Constructor老是當前類定義的構造方法,和父類無關,所以不存在多態的問題。

調用非publicConstructor時,必須首先經過setAccessible(true)設置容許訪問。setAccessible(true)可能會失敗。

2.4 獲取繼承關係
2.4.1 獲取interface

因爲一個類可能實現一個或多個接口,經過Class咱們就能夠查詢到實現的接口類型。例如,查詢Integer實現的接口:

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

運行上述代碼可知,Integer實現的接口有:

  • java.lang.Comparable
  • java.lang.constant.Constable
  • java.lang.constant.ConstantDesc

要特別注意:getInterfaces()只返回當前類直接實現的接口類型,並不包括其父類實現的接口類型:

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class.getSuperclass();
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

Integer的父類是NumberNumber實現的接口是java.io.Serializable

此外,對全部interfaceClass調用getSuperclass()返回的是null,獲取接口的父接口要用getInterfaces()

System.out.println(java.io.DataInputStream.class.getSuperclass()); // java.io.FilterInputStream,由於DataInputStream繼承自FilterInputStream
System.out.println(java.io.Closeable.class.getSuperclass()); // null,對接口調用getSuperclass()老是返回null,獲取接口的父接口要用getInterfaces()
2.4.2 繼承關係

當咱們判斷一個實例是不是某個類型時,正常狀況下,使用instanceof操做符:

Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

若是是兩個Class實例,要判斷一個向上轉型是否成立,能夠調用isAssignableFrom()

// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,由於Integer能夠賦值給Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,由於Integer能夠賦值給Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,由於Integer能夠賦值給Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,由於Number不能賦值給Integer

3、反射優缺點

3.1 優勢
  1. 反射機制極大的提升了程序的靈活性和擴展性,下降模塊的耦合性,提升自身的適應能力。
  2. 經過反射機制可讓程序建立和控制任何類的對象,無需提早硬編碼目標類。
  3. 使用反射機制可以在運行時構造一個類的對象、判斷一個類所具備的成員變量和方法、調用一個對象的方法。

反射機制是構建框架技術的基礎所在,使用反射能夠避免將代碼寫死在框架中。正是反射有以上的特徵,因此它能動態編譯和建立對象,極大的激發了編程語言的靈活性,強化了多態的特性,進一步提高了面向對象編程的抽象能力,於是受到編程界的青睞。

3.2 缺點

儘管反射機制帶來了極大的靈活性及方便性,但反射也有缺點。反射機制的功能很是強大,但不能濫用。在能不使用反射完成時,儘可能不要使用,緣由有如下幾點:

  1. 性能問題。

    Java反射機制中包含了一些動態類型,因此Java虛擬機不可以對這些動態代碼進行優化。所以,反射操做的效率要比正常操做效率低不少。咱們應該避免在對性能要求很高的程序或常常被執行的代碼中使用反射。並且,如何使用反射決定了性能的高低。若是它做爲程序中較少運行的部分,性能將不會成爲一個問題。

  2. 安全限制

    使用反射一般須要程序的運行沒有安全方面的限制。若是一個程序對安全性提出要求,則最好不要使用反射。

  3. 程序健壯性

    反射容許代碼執行一些一般不被容許的操做,因此使用反射有可能會致使意想不到的後果。反射代碼破壞了Java程序結構的抽象性,因此當程序運行的平臺發生變化的時候,因爲抽象的邏輯結構不能被識別,代碼產生的效果與以前會產生差別。

4、反射實際應用場景

4.1 Spring IOC

IOC:即「控制反轉」,不是什麼技術,而是一種思想。使用IOC意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。在Spring的配置文件中,常常看到以下配置:

<bean id="userService" class="com.tim.wang.sourcecode.reflection.springioc.UserServiceImpl"></bean>

那麼經過這樣配置,Spring是怎麼幫咱們實例化對象,而且放到容器中去了了,就是經過反射!!!

//解析<bean .../>元素的id屬性獲得該字符串值爲「courseDao」
String idStr = "courseDao";
//解析<bean .../>元素的class屬性獲得該字符串值爲「com.qcjy.learning.Dao.impl.CourseDaoImpl」
String classStr = "com.qcjy.learning.Dao.impl.CourseDaoImpl";
//利用反射知識,經過classStr獲取Class類對象
Class<?> cls = Class.forName(classStr);
//實例化對象
Object obj = cls.newInstance();
//container表示Spring容器
container.put(idStr, obj);

當一個類裏面須要應用另外一類的對象時,Spring的配置以下所示:

<bean id="courseService" class="com.qcjy.learning.service.impl.CourseServiceImpl">
     <!-- 控制調用setCourseDao()方法,將容器中的courseDao bean做爲傳入參數 -->
     <property name="courseDao" ref="courseDao"></property>
</bean>

繼續用僞代碼的形式來模擬實現一下Spring底層處理原理:

//解析<property .../>元素的name屬性獲得該字符串值爲「courseDao」
String nameStr = "courseDao";
//解析<property .../>元素的ref屬性獲得該字符串值爲「courseDao」
String refStr = "courseDao";
//生成將要調用setter方法名
String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1);
//獲取spring容器中名爲refStr的Bean,該Bean將會做爲傳入參數
Object paramBean = container.get(refStr);
//獲取setter方法的Method類,此處的cls是剛纔反射代碼獲得的Class對象
Method setter = cls.getMethod(setterName, paramBean.getClass());
//調用invoke()方法,此處的obj是剛纔反射代碼獲得的Object對象
setter.invoke(obj, paramBean);
相關文章
相關標籤/搜索