1. 程式人生 > 其它 >Java基礎(017):反射初探

Java基礎(017):反射初探

  本文主要就反射的基礎概念進行說明,目錄結構如下:

0、前言:為什麼需要反射

  先說一說筆者整理後的一個大致的看法:我們知道,靜態型別語言一般都是在編譯時就確定型別,因此,對於某個介面、某個具體型別物件,在程式碼中可以呼叫哪些方法、哪些屬性都是確定的,這也使得所有呼叫在經過編譯器編譯之後基本可以認為是型別安全的。這樣,對於一個未知型別的 object 物件,我們是無法知道這個 object 物件到底有沒有像 doSomething() 這樣一個方法的,也肯定是無法直接呼叫的。靜態型別的設計不支援這種不安全的處理,除非我們知道這個物件是屬於某個已知的型別或者介面,然後進行轉型,我們才能使用這種型別確定性。而反射則是針對此種場景而開的一個後門,反射讓程式碼可以在執行時去獲取物件型別相關的所有元資訊,包括檢測是否存在指定的屬性、方法宣告、註解宣告等,或者獲取所有的這些相關宣告,同時還可以對此進行操作使用。也就是說,應用無需在編譯時就提前知道一切,存在哪些實現類,它可以通過反射在執行時再根據指定配置去獲取相關型別元資訊進行操作

(載入指定的類,按照指定的構造器例項化,呼叫指定的方法、處理指定的註解等),從而使得程式碼和框架應用等更具動態性、可擴充套件性,而基於此則衍生出很多框架及應用,包括註解、代理、JavaBean物件複製、AOP等,像通過properties檔案配置、xml檔案配置、註解配置等方式進行擴充套件等,這也是反射機制狠受歡迎的重要原因。當然,這種機制繞開了靜態語言中編譯器的型別安全檢查,可能會帶來一些潛在的安全問題。

  總結下,可以說這是由於靜態語言本身存在著限制,需要在編譯期完成型別確定(靜態載入),而反射則是突破這種限制,將編譯期該處理的事情,延遲到了執行期(動態載入),而且是毫無保留的,正是由於可以在執行時處理,才有了常見的各種基於配置、註解、動態代理的應用。

  本文旨在說明為什麼需要反射、反射是什麼、反射的基礎API基本操作、反射可以做什麼、有哪些應用等,好讓大家對反射有一個基本的認識。至於為什麼是這樣,底層是怎麼支援和體現的,這部分深入的知識,後續會再進一步分析。

1、反射是什麼

  反射是什麼??

  我們來看看官網對於反射的說明。根據官網[1]Trail: The Reflection API的描述:


Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.


  反射機制是 Java 語言提供的一種高階功能,它賦予程式在執行時自省(introspect)和修改執行時行為的能力。簡單來說就是通過反射,程式可以檢測或修改應用的執行時行為,對於任意一個類,都可以獲取這個類的所有屬性和方法、建構函式、實現的介面、父類等資訊;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。這種動態獲取資訊以及動態呼叫物件方法、修改執行時行為的功能稱為java語言的反射機制。

  作為一門比較成熟的面向物件設計語言,Java 把類內部的所有屬性、方法、建構函式等組成成分(元資訊)都抽象成相應的Java類(物件)來進行操作,包括常見的 Class、Method、Constructor、Field 等,利用反射,我們就可以獲取這些物件並進行操作,很多常見的框架和應用也都是基於此進行的擴充套件。

  而這一切其實都是來源於編譯後的 class 位元組碼在載入後所表述的資訊。類載入器將 .class 檔案(或者其他符合 JVM 位元組碼規範的二進位制流)載入到記憶體中,併為之生成對應的描述 Class 結構的元資訊物件,也就是常說的 Class 物件,而通過該元資訊物件可以獲知 Class 的結構資訊:如建構函式,屬性和方法等。反射機制允許程式碼通過這個Class物件來操作所有功能。我們可以毫不誇張地說: Class 是反射的基礎。

  由此我們知道,class 位元組碼在載入後既可以被我們直接利用來建立例項,實現直接操作處理(new),還可以被應用程式或者框架用於執行時載入和處理指定的配置類、方法等,甚至進行定製處理,做到靈活且可擴充套件性高。

  反射機制就像是為開發者開的一扇後門,提供了一種強大的機制來處理正常情況下不可能完成的事。反射機制把類的各種元資訊當成是物件來進行處理,包括屬性、方法、建構函式等,都會有對應的物件,通過這些,程式就可以在執行時動態地進行操作。在反射機制面前,類和物件是沒有祕密的,可以被任意操作(所以需要特別注意)。

  另外,維基百科wiki-Reflection中提到:

  1. Introspection is the ability of a program to examine the type or properties of an object at runtime.
  2. Reflection is the ability of a program to examine and modify the structure and behavior of an object at runtime.

  從定義來看, Introspection (自省) 是反射 Reflection 的一個子集,有些程式語言支援 Introspection 但不支援 Reflection 。

  從官網的描述可以看出,Java的反射機制包括了這兩層含義:

  • 獲取或檢測執行時的物件型別元資訊;
  • 修改或者操作相關元資訊。

2、反射的基礎API

  前面提到,反射所用到的各種元資訊物件,都來源於 Class ,接下來就先來看下反射的基礎API中的基礎 : java.lang.Class 。

2.1、反射的基礎類:java.lang.Class

  java.lang.Class類用於表示執行時型別資訊,是所有類的類,是反射的基礎,而 Class 物件則表示類對應的具體型別資訊,它和載入的 class 檔案所描述的資訊是一致的。像最常用的 java.lang.String ,JVM 會為 String 建立一個對應的 Class 物件 String.class ,儲存 String 類相關的型別資訊,該物件儲存在 jvm 堆中,作為訪問方法區中 String 型別資訊的介面,可以通過 String.class 訪問。

  Class 本身是無法直接例項化的(私有的建構函式),Class 物件都是由 JVM 載入和建立的。我們可以通過已知的類或者物件來獲取對應的Class物件。

  Java的基本型別 boolean、byte、char、short、int、long、float 和 double 和關鍵字 void 都有對應的 class 物件,例如 int.class(對應 Integer.TYPE 而不是 Integer.class ) 、void.class 等。

注:Void的API文件提到,The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the Java key word void.

  列舉是一種類([10]Enum Types)。

  註解是一種介面(繼承 java.lang.annotation.Annotation ,[11]9.6. Annotation Types:An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@).)

  即使是陣列都有與之關聯的Class類的物件,所有具有相同元素型別和維數的陣列都共享該 Class 物件,而且與具體的元素的Class物件是不一樣的,例如一維String陣列[Ljava.lang.String;和 java.lang.String 是不一樣的。

  在使用自定義類時,虛擬機器會先檢查這個類的Class物件是否已經載入,如果沒有載入,預設的類載入器就會先根據類的全限定名查詢對應的 .class 檔案,載入到記憶體然後生成對應的Class物件。

  結合上面的闡述,我們大概可以知道,Class物件載入總體來源有2種:靜態載入和動態載入。(或者其他類似說法,本質上是一樣的,不必拘泥,區別在於編譯時和執行時)

  靜態載入其實就是編譯器在編譯時都一一確定的(前面提到的靜態語言的編譯期型別確定性),例如,我們直接寫出來的 new 例項物件,對應的例項物件所代表的類肯定是已確定而且需要直接檢查的,我們直接用的是啥就得在編譯時檢查啥;而動態載入,只有在執行時才知道,主要表現在我們可能是從別的地方獲取到了一個引用配置(最常見的就是配置的方式,例如xml配置、properties配置等),然後動態的把這個未知型別所引用的物件的.class檔案載入進jvm虛擬機器裡,實際上準確來說是class二進位制位元組流,只要是符合JVM規範的位元組碼就行,具體的位元組碼格式建議參考JVM規範[9]Chapter 4. The class File Format或者《深入理解Java虛擬機器》第三版中的相關描述。class二進位制位元組流來源很多途徑,如:

  • 從ZIP包獲取,這是JAR、EAR、WAR等格式的基礎
  • 從網路中獲取,典型的應用是 Applet (基本不用了)
  • 執行時計算生成,這種場景使用最多的是動態代理技術,在 java.lang.reflect.Proxy 類中,就是用了 ProxyGenerator.generateProxyClass 來為特定介面生成形式為 *$Proxy 的代理類的二進位制位元組流
  • 由其它檔案生成,典型應用是JSP,即由JSP檔案生成對應的Class類
  • 從快取、資料庫中獲取等等

  動態載入體現的是執行時配的啥再來載入啥(需要獲取啥)。

注:記得在《Java程式設計思想》有提到 RTTI ,即 Run-Time Type Identification 執行時型別識別,暫未發現 Java 文件中有提到的,這裡就不多說了,可以自己搜尋或者簡要參考Java RTTI和反射的區別?

2.2、獲取Class物件的3種方式

  根據[2]Retrieving Class Objects提到的,可以通過下面3種方法獲取Class物件:

  • ① 通過 Object.getClass() 例項方法進行獲取
String str = "str";
Class<?> klass = str.getClass();
System.out.println(klass.getName());
  • ② 通過 .class 語法進行獲取
// Class<?> strClass = String.class;
Class<String> strClass = String.class;
System.out.println(strClass.getName());
Class<?> intClass = int.class;
Class<?> integerClass = Integer.class;
Class<?> integerTypeClass = Integer.TYPE;
// true
System.out.println(intClass == integerTypeClass);
// false
System.out.println(intClass == integerClass);
  • ③通過 Class.forName() 進行獲取
Class<?> cla = Class.forName("java.lang.String");
System.out.println(cla.getName());

2.3、Class物件核心介面方法

  Class物件是反射的基礎,提供了獲取類資訊的各種方法,以下是部分方法及其說明的相關翻譯,可以看到,這裡包含了修飾符、包資訊、父類、實現的介面、構造器、方法、屬性變數、註解等資訊,也都和我們寫的Java程式碼一一對應。具體參考API連結的說明[3]java.lang.Class<T>或者直接參考JDK相關原始碼及其註釋。

方法 說明
forName(String name) 返回指定類名name的Class物件
newInstance() 呼叫預設構造器,返回Class物件的一個例項
getName() 返回此Class物件所表示的實體(類、介面、陣列類或者void)的名稱
getPackage() 包資訊
getModifiers() 修飾符,如public, protected, private, final, static, abstract and interface; 等, 返回來的 int 值可以通過 java.lang.reflect.Modifier 提供的方法來判斷
getSuperClass() 返回當前Class物件的父類的Class物件
getInterfaces() 返回當前Class物件的介面
getClassLoader() 返回該類的類載入器
getConstructor(Class<?>... parameterTypes) 返回指定引數Class型別的public構造器的Constructor物件
getConstructors() 返回代表該類所有public構造器的Constructor陣列
getDeclaredConstructor(Class<?>... parameterTypes) 返回指定引數Class型別的構造器的Constructor物件
getDeclaredConstructors() 返回代表該類所有構造器的Constructor陣列
getMethod(String name, Class<?>... parameterTypes) 返回指定方法名稱和引數 Class 型別列表的public方法的 Method 物件, 包括從父類繼承過來的public方法
getMethods() 返回所有public方法的 Method 陣列,包括父類/介面的public方法
getDeclaredMethod(String name, Class... parameterTypes) 返回指定方法名稱和引數 Class 型別列表的方法,不包含父類的方法
getDeclaredMethods() 返回所有方法的 Method 陣列,包括 public、protected、預設(包私有)訪問 和 private 方法,但是不包括父類的方法
getField(String name) 返回指定名稱的public成員變數的 Field 物件
getFields() 返回所有可訪問的public成員變數的 Field 物件陣列
getDeclaredField(String name) 返回指定名稱的變數的 Field 物件
getDeclaredFields() 返回代表該類所有宣告的屬性變數的 Field 陣列
getAnnotation(Class<A> annotationClass) 返回指定型別的註解宣告
getAnnotations() 返回所有的註解宣告
getAnnotationsByType(Class<A> annotationClass) 返回指定型別的註解宣告,區別在於可否重複,有些註解型別可以重複出現
getDeclaredAnnotation(Class<A> annotationClass) 返回指定型別的註解宣告,只包含該類直接宣告的
getDeclaredAnnotations() 返回該類所直接宣告的註解,不包含繼承的
getDeclaredAnnotationsByType(Class<A> annotationClass) 返回指定型別的註解宣告,區別在於可否重複,有些註解型別可以重複出現, 但是隻包含該類直接宣告的
。。。。。。。。。。

  其中修飾符的判斷:

/// java.lang.reflect.Modifier
Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);

2.4、反射常用的核心類

  java.lang.reflect 包提供了支援 Java 反射機制的核心類:

  • Field :代表類的成員變數(成員變數也稱為類的屬性)
  • Method :代表類的方法
  • Constructor :代表類的構造方法
  • Annotation :註解資訊
  • Package :包資訊
  • Array 類:提供了動態建立陣列,以及訪問陣列的元素的靜態方法
  • Proxy 類:動態代理核心類
  • 。。。

3、Java反射提供的功能和API基本使用

  java反射框架主要提供以下內容:

  • 在執行時判斷任意一個物件所屬的類;
  • 在執行時構造任意一個類的物件;
  • 在執行時獲取任意一個類包含的成員變數、方法、繼承的父類、實現的介面等資訊;
  • 在執行時呼叫任意一個物件的任意方法和變數
  • 獲取泛型資訊
  • 獲取註解和處理
  • 生成動態代理
  • 。。。

  關於前面提到的API的具體使用,建議參考官方文件進行學習,例如[1]Trail: The Reflection API[3]Java Reflection API,也可以自行搜尋,網上有很多的案例。本文就簡單地列舉一些示例(具體在 3.6 或者參考筆者github)。

3.1、建立例項物件

  通過反射來建立例項物件主要有兩種方式:

  • 用 Class 物件的 newInstance 方法,使用預設無參構造器或者自定義的無參構造器
  • 用 Constructor 物件的 newInstance 方法

3.2、通過Method呼叫方法

  包括以下幾種:

  • 靜態方法
  • 公有方法
  • 私有方法

  私有方法需要通過 getDeclaredMethod 獲取,且需要setAccessible(true)

3.3、通過Field操作屬性

  私有屬性同樣需要setAccessible(true)

3.4、判斷是否為某個類的例項

  判斷是否為某個類的例項有兩種方式:

  • 用 instanceof 關鍵字
  • 用 Class 物件的 isInstance 方法(它是一個 Native 方法)

  例如:

String.class.isInstance("str");

boolean isString = "str" instanceof String;

3.5、獲取泛型資訊

  參考[12]Java Reflection - Generics。有部分泛型資訊是可以在執行時通過反射動態獲取的。我們知道,泛型主要包括兩部分:一部分是宣告,另一部分則是使用。而具體使用的時候,我們是可以知道這部分泛型資訊的。主要包括以下3部分:

  • 方法返回值的泛型
  • 方法引數的泛型
  • 成員變數的泛型

3.6、獲取註解和處理

  註解只是一種標記,叫啥名字不重要,重要的是處理這個註解標記的程式碼,例如jdk本身的 @Override 會在編譯時處理。

  下面看下相關的示例程式碼,可以參考筆者github

package cn.wpbxin.javabasis.reflection.basis;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    int order() default 0;
    String id() default "id";
}

/// 
package cn.wpbxin.javabasis.reflection.basis;

import java.util.HashMap;
import java.util.Map;

/**
 * 反射基礎API測試例項類
 * @author wpbxin
 *
 */
@Service(id = "reflectionService")
public class ReflectionService {
    
    private String msg = "default msg";
    private int type = 0;

    public ReflectionService(){}
    
    public ReflectionService(String msg,int type) {
        super();
        this.msg = msg;
        this.type = type;
    }
    
    @Override
    public String toString() {
        return "ReflectionService [msg=" + msg + ", type=" + type + "]";
    }
    
    private void privatePrintMsg(String msg) {
        System.out.println("private instance method:" + msg);
    }
    
    void defaultPrintMsg(String msg) {
        System.out.println("package-default instance method:" + msg);
    }
    
    protected void protectedPrintMsg(String msg) {
        System.out.println("protected instance method:" + msg);
    }
    
    public void publicPrintMsg(String msg) {
        System.out.println("public instance printMsg:" + msg);
    }
    
    public void throwException () {
        if (0 == type) {
            throw new NullPointerException();
        }
    }
    
    private void privatePrint(String msg) {
        System.out.println("private instance method:" + msg);
    }
    
    void defaultPrint(String msg) {
        System.out.println("package-default instance method:" + msg);
    }
    
    protected void protectedPrint(String msg) {
        System.out.println("protected instance method:" + msg);
    }
    
    public void publicPrint(String msg) {
        System.out.println("public instance printMsg:" + msg);
    }
    
    private static void privateStaticPrint(String msg) {
        System.out.println("privateStaticPrint:" + msg);
    }
    
    static void defaultStaticPrint(String msg) {
        System.out.println("defaultStaticPrint:" + msg);
    }
    
    protected static void protectedStaticPrint(String msg) {
        System.out.println("protectedStaticPrint:" + msg);
    }
    
    public static void publicStaticPrint(String msg) {
        System.out.println("publicStaticPrint:" + msg);
    }
    
    Map<String, Integer> map = new HashMap<>();
    
    public Map<String, Integer> getMap() {
        return this.map;
    }
    
    public void setMap(Map<String, Integer> map) {
        this.map = map;
    }
}

///
package cn.wpbxin.javabasis.reflection.basis;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * 反射基礎API測試類
 * 
 * @author wpbxin
 *
 */
public class ReflectionTest {
    public static void main(String[] args) throws Exception {

        // 獲取 ReflectionService 對應的Class物件
        Class<ReflectionService> klass = ReflectionService.class;

        /// 1、建立例項物件

        // 1.1、通過 Class.newInstance 構造物件,預設使用無參構造器
        ReflectionService rs1 = klass.newInstance();
        System.out.println(rs1.toString());
        System.out.println();

        // 1.2、通過 Constructor.newInstance 構造物件,指定引數Class型別
        // 獲取 ReflectionService 類中帶有 String、int 引數的構造器
        Constructor<ReflectionService> rsConstructor = klass.getConstructor(String.class, int.class);
        // 根據構造器建立例項
        ReflectionService rs2 = rsConstructor.newInstance("constructor newInstance", 1);
        rs2.publicPrintMsg("呼叫公有方法");
        System.out.println(rs2.toString());
        System.out.println();

        /// 2、操作方法 Method

        // 返回類或者介面中宣告的所有方法,包括私有的,但是不包含繼承的方法
        Method[] declaredMethods = klass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }
        System.out.println();

        // 2.1、靜態方法
        Method staticMethod = klass.getMethod("publicStaticPrint", String.class);
        staticMethod.invoke(null, "公有類方法呼叫");
        System.out.println();

        // 2.2、公有例項方法和私有例項方法
        // 返回類或者介面中宣告的所有public方法,包括從超類或者介面中繼承過來的
        Method[] methods = klass.getMethods();
        // 例項物件
        ReflectionService rfts = klass.newInstance();
        
        Method publicPrintMsg = klass.getMethod("publicPrintMsg", String.class);
        publicPrintMsg.invoke(rfts, "公有例項方法");
        System.out.println();
        
        // 私有方法必須使用 getDeclaredMethod
        Method privatePrintMsg = klass.getDeclaredMethod("privatePrintMsg", String.class);
        // 設定可見性
        privatePrintMsg.setAccessible(true);
        privatePrintMsg.invoke(rfts, "私有例項方法");
        System.out.println();

        /// 3、操作屬性 Field
        // 獲取所有屬性,包括私有的,但不包括繼承的
        Field[] declaredFields = klass.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        System.out.println();
        
        // 私有屬性
        Field privateField = klass.getDeclaredField("type");
        privateField.setAccessible(true);
        privateField.setInt(rfts, 20);
        System.out.println(rfts.toString());
        System.out.println();
        
        /// 4、判斷是否為某個類的例項物件
        System.out.println(String.class.isInstance("str"));
        System.out.println();

        /// 5、獲取泛型資訊,參考 http://tutorials.jenkov.com/java-reflection/generics.html
        // 5.1、方法返回值的泛型
        Method rMethod = klass.getDeclaredMethod("getMap");
        Type returnType = rMethod.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for (Type typeArgument : typeArguments) {
                Class<?> typeArgClass = (Class<?>) typeArgument;
                System.out.println("typeArgClass=" + typeArgClass);
            }
        }
        System.out.println();
        
        // 方法引數的泛型
        Method pMethod = klass.getDeclaredMethod("setMap", Map.class);
        Type[] genericParameterTypes = pMethod.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            if (genericParameterType instanceof ParameterizedType) {
                ParameterizedType aType = (ParameterizedType) genericParameterType;
                Type[] parameterArgTypes = aType.getActualTypeArguments();
                for (Type parameterArgType : parameterArgTypes) {
                    Class<?> parameterArgClass = (Class<?>) parameterArgType;
                    System.out.println("parameterArgClass=" + parameterArgClass);
                }
            }
        }
        System.out.println();
        
        // 成員變數的泛型
        Field mapField = klass.getDeclaredField("map");
        Type genericFieldType = mapField.getGenericType();
        if(genericFieldType instanceof ParameterizedType){
            ParameterizedType aType = (ParameterizedType) genericFieldType;
            Type[] fieldArgTypes = aType.getActualTypeArguments();
            for(Type fieldArgType : fieldArgTypes){
                Class<?> fieldArgClass = (Class<?>) fieldArgType;
                System.out.println("fieldArgClass = " + fieldArgClass);
            }
        }
        System.out.println();
    
        /// 6、獲取註解和處理
        // 簡單示例
        Annotation[] declaredAnnotations = klass.getDeclaredAnnotations();
        for (Annotation annotation : declaredAnnotations) {
            if (annotation instanceof Service) {
                System.out.println(annotation);
                // do something else
            }
        }
        
    }
}

3.7、動態代理

  動態代理是一種方便執行時動態構建代理、動態處理代理方法呼叫的機制,很多場景都是利用類似機制做到的,比如用來包裝 RPC 呼叫、面向切面程式設計(AOP)等。所謂的動態代理,首先它還是代理,不一樣的地方是體現在“動態”,也就是說它不是靜態的,不是原來就有的(編譯期已有),是需要在執行過程中生成的,存在著變化,這就體現出了動態性。

  Java動態代理的底層是通過反射機制來實現的,反射是實現Java動態代理的基礎,動態代理核心類 java.lang.reflect.Proxy 中有相關反射程式碼。

  Spring 動態代理的實現方式有兩種:cglib(基於ASM) 和 JDK 原生動態代理。代理是一個比較大的話題,具體分析後面會再詳細寫,本文不再深究。

4、反射的重要應用

  有人可能會有這樣的疑問:反射有什麼作用?這麼神嗎?我明明已經知道要使用的具體實現類了,還需要反射嗎?和直接 new 一個物件有什麼差別?反正最後都是呼叫實際的物件,非得要繞來繞去的。

  如果實現類已知且完全可控,直接 new 出來也可以,甚至說是最簡單高效的。但是有很多場景,實現類是未知的,有可能經常變化,只有在執行時才會根據指定的配置去確定實現類,甚至有的時候需要在執行中接收相應指令配置來載入的(例如查詢資料庫配置、配置中心推送等等);你也永遠無法預測誰寫的、寫了什麼實現類,也完全不可控,還有可能你完全接觸不到實現類(RPC呼叫),這個時候就 new 不出來了。這也算是隔離變化、封裝變化。

  有了反射,我們就無需提前知道有哪些需要用到的實現類,例如JDBC實現類,比較難去預測會有哪些新型別的資料庫,MySQL、Oracle、SQL server 等,而且版本不一樣,驅動包中的實現類還可能不一樣,每次都改程式碼太要命了,修改約定的配置檔案或者配置中心是最直接省事的;還有像雲應用介面,人臉識別等,可能有好多家廠商提供的介面。另外就是像RPC呼叫、AOP之類的,也不太可能去直接寫出每個代理類。

  反射是高擴充套件模組化、框架設計的靈魂,我們可以根據指定的配置來進行按需載入,設計出更加通用和靈活的架構。反射對於模組化的設計開發、通用基礎框架等有很大的作用,日常使用到的 Spring 、Mybatis 等框架都會用到反射機制。

  下面簡單列舉下反射在實際開發中的一些重要應用場景。

1、註解

  註解本身僅僅是起到標記作用,它需要利用反射機制,根據註解標記去呼叫註解處理器,執行相應的行為。正是有了反射,才可能獲取註解資訊並進行相關處理。現在有很多的框架都是優先建議使用註解配置了。

2、JUnit

  著名的單元測試框架 JUnit使用反射來檢測所有被 @Test 註解的方法並進行執行。

3、JSON與物件的轉換

  通過反射獲取屬性的 getter 和 setter

4、反射與javaBean

  JavaBean讀取屬性x的值的大致流程:變大寫、補字首、獲取方法。

"x"-->"X"-->"getX"-->"MethodGetX"

  簡單實現:使用 java.beans.PropertyDescriptor 類

  麻煩實現: 使用 java.beans.Introspector 類,遍歷 getBeanInfo 方法的返回值

  JavaBean必須有一個不帶引數的建構函式,不然無法使用預設的例項化。

  使用BeanUtils工具包進行復制(BeanUtils.copyProperties),當然,這些工具包還提供了它一些功能:

  • 字串和整數轉換(對比(PropertyUtils)
  • 屬性級聯操作
  • 操作map

5、Mock

  用於單元測試的動態模擬物件。mock 庫使用反射建立一個代理物件,完成類方法的 mocking

6、Servlet配置

  web應用中的 servlet 配置,當然,後面可能用註解進行替換了。

<servlet>
    <servlet-name>dipatcherServlet</servlet-name>
    <servlet-class>SpringDipatcherServlet</servlet-class>
<servlet>

  還有像 Spring WebMVC 的過濾器、攔截器、監聽器等等

7、IDE的程式碼補齊提示

  像 IntelliJ IDEA 、Eclipse 等開發工具,都會有程式碼補齊提示,在寫程式碼時會有程式碼(屬性或方法名)提示,就是因為使用了反射。

8、Spring框架的使用

  Java的反射機制在做基礎框架的時候非常有用。行內流傳著這樣一句話:反射機制是Java框架的基石。像大名鼎鼎的Spring,好些地方都用到了反射機制,最經典的就是基於xml的配置模式。只需要通過修改配置就可以載入不同的物件、呼叫不同的方法。

<bean id="person" class="com.spring.beans.Person" />

  Spring 就是依靠 class 屬性進行 Class 物件的載入的。

  Spring 通過 XML 配置模式裝載 Bean 的大致過程:

  • 將程式內所有 XML 或 Properties 配置檔案載入入記憶體中
  • Java類裡面解析xml或properties裡面的內容,得到對應實體類的全限定名字串以及相關的屬性資訊
  • 使用反射機制,根據這個字串獲得某個類的Class例項
  • 動態配置例項的屬性

  Spring這樣做的好處是:

  • 不用每一次都要在程式碼裡面去new或者做其他的事情
  • 以後要改的話直接改配置檔案,程式碼維護起來就很方便了
  • 有時為了適應某些需求,Java類裡面不一定能直接呼叫另外的方法,可以通過反射機制來實現

  像 SpringMVC中controller介面入參轉換、Java註解物件獲取、使用者鑑權、全域性異常處理、全域性日誌處理、事務處理等等,都與反射有關。

9、JDBC的資料庫連線

  在 JDBC 的操作中,如果要想進行資料庫的連線,則必須按照以下幾個步驟:

  • 通過Class.forName()載入資料庫的驅動程式 (這裡是通過反射進行載入,需要引入相關驅動Jar包)
  • 通過 DriverManager 類進行資料庫的連線,連線的時候要將資料庫的連線地址、使用者名稱、密碼等作為引數
  • 通過 Connection 介面接收連線

  其中第一步就是需要通過反射來獲取並載入對應的驅動類。

10、JDBC ResultSet和JavaBean

11、資料庫連線與事務管理

  Spring框架有一個事務代理,可以開啟、提交/回滾事務,可以參考Advanced Connection and Transaction Demarcation and Propagationhttp://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html中的詳細講解。

12、RPC遠端呼叫

  

13、反射與工廠模式

  配置檔案的形式,例如 config.properties、XML 檔案,還有很常見的約定在 META-INF 檔案下的配置,然後可以通過 Class.forName() 進行例項化,如果複用,可以通過 map 將進行快取。

  等等很多應用都與反射有關,而且細講起來都可以說是篇幅較長。鑑於筆者能力有限,這裡就不細講了。有興趣的可以自己進行相關搜尋。

5、反射的優點和缺點

  根據官網[1]Trail: The Reflection API中提到的,再加上部分總結:

優點:

  • 程式碼靈活,可擴充套件性強
  • IDE開發工具、測試與除錯工具利用發射提供了各種便利
  • 依賴少,JDK原生支援(當然也有可能後面哪個版本就禁止掉了)

缺點:

  • 效能問題:反射涉及到執行時型別資訊的動態解析獲取,JNI,JNA都涉及載入動態庫,需要大量資源,當然 jni,jna載入動態庫只加載一次就夠了,一般都會做快取。而且由於動態解析,某些JVM優化可能就無法執行。
  • 安全問題:反射的使用基本上沒有什麼許可權限制,使得程式執行在一個沒有安全限制的環境之下,可能會導致安全問題。
  • 破壞封裝和可移植性:反射可以修改訪問許可權,破壞了封裝和抽象,隨著迭代,反射程式碼可能會隨著平臺的升級而改變行為。

應用層的優化手段如下:

  • 獲取反射元資料的時候避免使用遍歷的方法(getMethods),儘量精準獲取(getMethod)。
  • 使用快取機制快取反射時候操作的相關元資料(整個反射過程中最耗時的就是獲取元資料階段,如Class.forName())。

6、參考