Java 反射與註解

反射與註解

Java 從源碼到執行通常須要三個過程:html

  1. 編譯並生成字節碼文件,即 class 文件或者 jar 包
  2. JVM 加載字節碼文件並初始化運行環境,例如將字節碼翻譯成機器指令、初始化對象、加載依賴包等
  3. 執行 Java 程序

C/C++ 這類系統級編程語言相比,Java 多了生成字節碼文件與翻譯字節碼文件這些中間步驟,這是 Java 實現「一次編譯到處執行」的基礎,也是反射和註解的底層基礎。相同的字節碼在不一樣的平臺下會被 JVM 翻譯成不一樣的機器指令,從而實現跨平臺執行。java

Java 提供了一種機制,容許咱們在載入(建立)類對象時讀取與修改對象中的屬性,這種機制基於 JVM。程序員能夠經過 Java 內置的一些方法使用 JVM 的這部分特性。這是 Java 反射和註解的原理。程序員

反射與類中的 Class 對象

維基百科對計算機科學中的反射解釋以下:數據庫

In computer science, reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.編程

在計算機科學中,反射是運行時查看與修改自身結構和行爲的能力。編程語言

舉個例子,Java 中運行時能夠經過反射修改屬性和方法的訪問限制(例如從 private 修改成 public )。函數

stackoverflow 上點贊較多的回答以下:測試

The ability to inspect the code in the system and see object types is not reflection, but rather Type Introspection. Reflection is then the ability to make modifications at runtime by making use of introspection. The distinction is necessary here as some languages support introspection, but do not support reflection. One such example is C++hibernate

探視代碼和對象類型不是反射。在運行時經過類型檢查來作一些修改纔是反射。C++ 能夠查看對象的類型(例如使用 typeid)但不能在運行時對對象作修改,故C++不支持反射。(非直譯)翻譯

上面兩個解釋中都強調了反射運行時修改的特色。

Java 是面向對象的語言,除了內置的 POD(Plain Old Data)類型,其餘全部數據類型都是對象,並且這些對象中有着不少相同的方法,例如:equal、toString 等等。每個 Java 類中都有一個 Class 對象 class(相似於靜態成員變量),Class 對象保存了類自己的信息,例如類有多少屬性,這些屬性的類型是什麼;還有就是類有哪些方法,這些方法的參數又是什麼等等。Class 對象是 Java 反射的基礎,只要提供一個類的 Class 對象咱們就能夠不用 new 而是使用 Java 提供的方法構造一個對應的對象。假設咱們已經有了一個 Dog 類,那麼咱們就能夠使用下面的方式在運行時構造一個 Dog 對象:

Class pClass  = Class.forName(Dog.class); // 得到 Dog 類的 Class 對象
Constructor c = pClass.getConstructor();  // 經過 Class 對象得到 Dog 類的構造函數
Dog xiaohei   = (Dog) c.newInstance();    // 構造一個 Dog 對象小黑

註解與類中的 Class 對象

註解信息會保存在類的 Class 對象中,Java 提供了讀取這些信息的方法,例如 Class.getAnnotation(...)

綜合上面的介紹可知:

  1. Java 能夠經過 Class 對象得到一個類的詳細信息
  2. 註解信息也保留在了 Class 對象中
  3. Java 提供了在生成類對象時修改對象屬性方法的機制

舉個簡單的例子來講明反射和註解的一些功能。假設咱們有一個 Dog 類,Dog 類中有 name、gender、color 等屬性,這些屬性在 Dog 的 Class 對象中是有記錄的。如今咱們有了一個 DogInit 註解,這個註解中也有若干個屬性,例如 name、gender、color等。使用 Java 提供的註解語法將 DogInit 和 Dog 關聯起來:

@DogInit(name="xiaohei", gender="boy", color="black")
class Dog{
    public static Dog getDefaultDog()
    {
        DogInit dogInit = Dog.class.getAnnotation(DogInit.class); // 經過 Class 對象獲取註解信息
        Dog dog; // 經過反射而非構造函數的形式初始化了一個 Dog 對象
        dog.name   = dogInit.getName(); // 從註解中提取數據
        dog.gender = dogInit.getGender();
        dog.color  = dogInit.getColor();
        return dog; 
    }
    ...
    private name;
    private gender;
    private color;
}

上面的代碼中,咱們從註解中提取了數據並構造了 Dog 對象,按照傳統的方法咱們通常使用構造函數。以 Hibernate 爲例,在關聯對象和數據庫表的時候咱們須要使用註解 @Table(name = "table_name")來指明當前類關聯的表。類對象和數據庫表本不應有任何的耦合關係,因此不該該在構造函數中指定關聯數據庫表名,使用註解能夠實現對象和表的解耦。測試的時候能夠當作這些註解信息不存在,由於使用 new 建立對象的時候默認不解析註解信息。

Spring 中的依賴注入機制所依賴的就是 Java 的反射與註解。咱們常常會在 Spring 代碼中看到類被加上了 @Bean 這個註解,Spring 項目在啓動時,Spring 會掃描合法的字節碼文件並搜索全部類的 Class 對象,若是發現一個類的 Class 對象中包含 @Bean 註解信息,則會自動建立這個類的一個對象,其餘沒有 @Bean 相關注解的類不會在系統中建立對象,除非你手動 new 一個。

示例

下面的例子源自 how2j,我截取了部分,感謝原做者,侵刪。要想完整理解下面的例子,最好先了解一下 Hibernate,能夠參考 how2j 中介紹 Hibernate 的第一小節hello hibernate

定義註解

package hibernate_annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
// 定義實體註解,以標識使用當前註解的對象爲實體對象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyEntity {
 
}
// 下面註解用於指明須要映射的數據庫表
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
    String name();
}

添加註解

@MyEntity // 標識當前類爲實體
@MyTable(name="hero_") // 指明映射的數據庫表
public class Hero {
    private int id;
    private String name;
    private int damage;
    private int armor;
    ...
}

解析與使用註解

Class<Hero> clazz = Hero.class;
// 嘗試讀取實體註解以判斷當前對象是不是數據庫實體對象
MyEntity myEntity = (MyEntity) clazz.getAnnotation(MyEntity.class);
if (null == myEntity) {
    System.out.println("Hero類不是實體類");
} else {
    System.out.println("Hero類是實體類");
    // 從 MyTable 註解中提取須要關聯的數據庫表
    MyTable myTable= (MyTable) clazz.getAnnotation(MyTable.class);
    String tableName = myTable.name();
    System.out.println("其對應的表名是:" + tableName);
    ... // 關聯數據庫表和實體對象的代碼
}

在上面的例子中,咱們從註解中提取了信息並使用修改了原始對象屬性。

相關文章
相關標籤/搜索