1. 程式人生 > 其它 >框架的基礎——反射和註解(reflection and annotation)

框架的基礎——反射和註解(reflection and annotation)

一.註解

1.什麼是註解?

  程式的註釋,具有一定的功能,可以被解析。是從jdk5開始引入的技術。

2.註解的作用

  - 不是程式本身,可以對程式做出解釋(這一點和comment沒有什麼區別)
- 可以被其他程式(如編譯器等)讀取

3.註解的格式

@註解名,還可以新增一些引數值。如:@SuppressWarnings(value = "unchecked")

4.註解使用在哪裡?

可以附加在package,class,method,field等上面,相當於給他們添加了輔助資訊。我們通過反射機制實現對這些元資料的訪問。

5.內建註解

- @Override 定義在java.lang.Override中,此註解只適用於修辭一個方法,表示一個方法打算重寫超類中的方法
- @SuppressWarnings 定義同上,用來抑制編譯時的警告資訊。與前兩個註解有所不同,需要新增一個引數才能使用,這些引數都是已經定義好的,我們選擇使用就好。
@SuppressWarnings(value = "all") "unchecked" "checked" {"unchecked", "deprecation"}
- @Deprecated 定義同上,此註解用於修飾方法,屬性,類。表示不鼓勵程式設計師使用這樣的元素,通常是因為它很危險或者有更好的替代。

6.元註解

1.元註解作用:負責註解其他註解的註解
2.四個元註解(meta-annotation),用來對其他註解型別做說明
- @Target 用於描述註解的使用範圍(即描述註解可以用在什麼地方)
- @Retention 保持的意思,表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(Source原始碼 < Class位元組碼 < Runtime執行時),表示該註解在什麼時候是有效的,一般設定為Runtime
- @Documented 表明該註解將被包含在javadoc中
- @Inherited 說明子類可以繼承父類中的該註解

package annotation;

import java.lang.annotation.*;

// 測試元註解
@MyAnnotation
public class Test02 {
    public static void main(String[] args) {

    }

    @MyAnnotation
    public void test() {

    }
}

// @Target表示註解的作用在哪些地方
@Target(value = {ElementType.METHOD, ElementType.TYPE})
// @Retention表示註解的有效範圍:source < class < runtuime 會向下覆蓋生效
@Retention(value = RetentionPolicy.RUNTIME)
// @Documented表示是否將我們的註解生成在javadoc中
@Documented
// @Inherited表示子類可以繼承該註解
@Inherited
@interface MyAnnotation {



}

7.自定義註解格式

使用@interface定義,使用了它就自動繼承了lang.annotation.Annotation介面

// @Target表示註解的作用在哪些地方
@Target(value = {ElementType.METHOD, ElementType.TYPE})
// @Retention表示註解的有效範圍:source < class < runtuime 會向下覆蓋生效
@Retention(value = RetentionPolicy.RUNTIME)
// @Documented表示是否將我們的註解生成在javadoc中
@Documented
// @Inherited表示子類可以繼承該註解
@Inherited
@interface MyAnnotation {



}
  • 用@interface宣告一個註解。格式:public @interface name {定義內容}
  • 其中宣告引數的方式為:引數型別 + 引數名 + ();
  • 引數型別只能是8種基本資料型別和String,Enum,Class,annotations資料型別,以及這些型別的陣列
  • 可以在()後新增defalt "",設定預設引數值
  • 如果只有一個引數,一般引數名為value,這樣可以在書寫時,不寫value,直接寫引數
  • 註解元素必須要有值 我們通常使用空字串”“和0作為預設值

二、反射

1.動態語言

動態語言是在執行時,可以改變其結構的語言:如新的函式,物件,甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。
如:js,C#,PHP,Python,Object-C

2.靜態語言

與動態語言相對,執行時不可以改變結構。Java,C,C++。
Java是靜態語言,但是可以通過反射機制,讓Java具有一定的動態性,Java的動態性讓程式設計更加靈活。但是也損失了效率,也帶來了安全問題。

3.Java反射 Reflection

反射是Java被稱作動態語言的關鍵,反射機制允許程式在執行期間藉助Reflection API獲取類的所有內部資訊,並能直接操作任意物件的內部屬性和方法。
原理:一個類載入完後,就會在堆記憶體的方法區中,產生一個Class型別物件(一個類只有一個),這個物件包含了完整的類的結構資訊,我們可以通過這個物件看到類的內部結構。這個物件就像一面鏡子,通過這個鏡子看到類的結構,所以形象的稱之為:反射

4.Java反射的優缺點

優點:可以實現動態建立物件,有很強的靈活性
缺點:對效能有影響。因為我們需要告訴JVM去做什麼,這列操作總使比直接執行要慢。

5.反射相關的主要API

java.lang.Class: 代表一個類
java.lang.reflect.Method:代表一個方法
java.lang.reflect.Filed:代表類的成員變數
java.lang.reflect.Constructor:代表類的構造器

6.Class類

在Object類中定義了public final native Class getClass();方法可以返回物件的Class物件,也就是物件的類,類模板,也就是包括類的所有資訊。
Class類是Java反射的源頭。反射就是從物件反射得到類物件。
關於Class:

  • Class本身也是一個類
  • Class物件只能由系統建立
  • 一個載入的類在JVM中只會有一個Class例項
  • 每個Class物件對應的是載入到JVM的一個class檔案
  • 通過Class可以完整的得到一個類中的所有被載入的結構
  • Class類是Reflection的根源,針對任何你想動態載入,執行的類,先獲取它的Class才可行。

7.獲取Class物件的方法

  • 通過類的.class獲取Class物件 效能最好,一個類載入到JVM中會建立一個Class物件和它的模板對應,class屬性就是這個Class物件的控制代碼,獲取該物件的模板物件Class。
  • 通過物件的getClass()方法獲取
  • 通過Class.forName()方法,傳入全限定路徑名,獲取對應的類模板
  • 內建基資料型別包裝類,類名.TYPE
  • 還可以使用ClassLoader獲取
  • 獲取父類的Class,使用c.getSuperClass();

注意:獲取的Class物件對應的類模板物件就是具體new 的哪個物件的class檔案的物件。而不是父類的,只是由於它繼承了父類,那麼它的class檔案中就包含了父類的class資訊。每一個類在JVM中都會由對應的結構資訊,這個是唯一的。一個類一個。

8. 可以有Class物件的型別

幾乎所有型別都有自己的Class

  • 介面
  • 註解
  • 列舉
  • 基本資料型別
  • Class(本身就是一個類)
  • 陣列 只要型別一樣,維度一樣就是同一個Class物件。
  • void空型別

9.類的載入與ClassLoader的理解

載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換為方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件。
連結:校驗,準備,連結。檢查錯誤規範; 給靜態變數分配記憶體(將在方法區分配),賦預設值; 將符號引用轉換為地址引用。
初始化:執行類構造器方法,類構造器方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的(類構造器是構造類資訊的,不是構造該類物件的的構造器)。按照書寫順序執行進行定義初始化。如果前面的靜態程式碼塊,使用了下面的靜態變數,會提示非法前向引用:也就是嘗試在沒有定義之前使用它。
合併類變數的賦值動作和靜態程式碼塊中的語句如下:當然類的定義仍然按照書寫順序,也就是靜態變數的賦值合併在了它的實際定義位置。

/**
     * 總結:
     *  1.載入類到記憶體,生成類對應的Class物件
     *  2. 連結 靜態變數預設值
     *  3. 初始化:執行<clinit>方法,clinit方法會把靜態變數,靜態程式碼塊合併,按照書寫順序進行執行,為靜態變數賦初始值。此時 m = 0;
     *      <clinit>(){
     *          System.out.println("A類靜態程式碼塊執行");
     *          System.out.println(m);
     *          m = 300;
     *          m = 100;
     *      }
     */

10.什麼情況下會發生類初始化?

  • 主動引用會發生類初始化

1.JVM啟動時,初始化main方法所在類

2.new一個物件

3.反射 (獲取類模板,就要載入類,載入類,就要初始化類)

4.呼叫類的變數,類的方法。除了final修飾的常量。

5.初始化會保證其父類被初始化完畢

  • 被動引用不會發生類初始化

1.通過子類呼叫父類的靜態成員,不會使子類初始化,但是會初始化父類。

2.建立類的陣列不會引起類初始化,僅僅是建立了一塊空間,並且給空間命名,並不會載入類。

3.呼叫類的常量不會引起來初始化(常量在連結階段就被調入了類的常量池)

總結:只有JVM啟動,new,反射,呼叫類的變數,方法,會進行初始化類

11.類載入器的作用

1.類載入的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後再堆中生成一個代表這個類的Class物件,作為方法區中類資料的訪問入口。

2.類快取:類載入後,Class物件會快取再記憶體中。GC回收。

12.獲取執行時類的完整結構

package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class clazz = Class.forName("reflection.User");
        System.out.println(clazz.getName());
        System.out.println(clazz.getSimpleName());

        // 獲取欄位(屬性)
        Field[] fields = clazz.getFields(); // 只能獲取public屬性 本類和父類的
//        fields = clazz.getDeclaredFields(); // 獲取所有宣告的屬性 本類的
        for (Field field:
             fields) {
            System.out.println(field + " =======");
        }

//        Field name = clazz.getField("name"); // 獲得指定名稱的屬性 public,包含父類的
        Field name = clazz.getDeclaredField("name"); // 獲得指定名稱的屬性 public
        System.out.println(name);

        // 獲得類的方法
        Method[] methods = clazz.getMethods(); // 獲取本類,父類的public方法。
        for (Method method : methods) {
            System.out.println("正常的:" + method );
        }

        Method[] declaredMethods = clazz.getDeclaredMethods(); // 獲取本類所有方法 包括private
        for (Method declaredMethod : declaredMethods) {
            System.out.println("getDeclaredMethods:" + declaredMethod);
        }

        System.out.println("=================================");

        // 獲取指定方法
        Method getName = clazz.getMethod("getName", null); //  (方法名,引數列表的型別的Class)
        System.out.println(getName);

        // 獲取有參方法
        Method setName = clazz.getMethod("setName", String.class); // 過載,需要指明引數,定位具體方法
        System.out.println(setName);

        System.out.println("======================");
        // 獲取指定的構造器
        Constructor[] constructors = clazz.getConstructors(); // 獲取本類的public構造方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); // 獲取所有構造方法
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        Constructor constructor = clazz.getConstructor(String.class, int.class, int.class); // public的
        System.out.println(constructor);

        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class, int.class); // 獲取指定的宣告的構造方法
        System.out.println(declaredConstructor);


    }
}

13.Class的使用

通過反射建立物件,設定屬性值,呼叫方法。
私有也可以通過獲取到的屬性,方法的setAccessible(true)就可以關閉訪問安全檢測,私有可訪問。
setAccessible(true)可以提高反射的效率,如果需要頻繁對某一個方法或屬性操作,那麼請設定true。

package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 動態的建立物件 通過反射
public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        // 獲得Class物件
        Class c1 = Class.forName("reflection.User");
        // 構造一個物件
//        User user = (User) c1.newInstance(); // 本質呼叫無參構造器,並且要求類的構造器訪問許可權足夠
//        System.out.println(user);

        /*
           通過構造器建立物件
         */
//        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
//        User user2 = (User) declaredConstructor.newInstance("sqzr", 12, 12);
//        System.out.println(user2);

        /*
        通過反射呼叫方法
         */
        User user3  = (User) c1.newInstance();
        Method setName = c1.getDeclaredMethod("setName", String.class); // 方法有了,但是不知道方法 呼叫哪個物件的這個方法
        setName.invoke(user3, "sqzr"); // user3 指的是呼叫的哪個物件的方法, 因為這個物件可能已經有過其他引數了,如果獲取了方法,不指定哪個物件的,就不知道呼叫哪個
        System.out.println(user3);

        /*
        通過反射操作屬性
         */
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        name.setAccessible(true); // 設定可訪問是否開啟,true會關閉安全檢測,使得私有可訪問,false會進行安全檢測。
        name.set(user4, "sqzr2");
        System.out.println(user4);

    }
}

14.setAccessible()效能問題分析

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 分析效能問題
public class Test10 {
    public static void main(String[] args) {
        test();
        try {
            test02();
            test03();;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    // 普通方法呼叫
    public static void test() {
        User user = new User();
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("普通方式執行10億次:" + (endTime - startTime));
    }

    // 反射方法呼叫
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName");

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("反射方式執行10億次:" + (endTime - startTime));
    }

    // 反射方法呼叫
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName");
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("關閉檢測許可權的反射方式執行10億次:" + (endTime - startTime));
    }
}

執行結果:

普通方式執行10億次:4
反射方式執行10億次:3875
關閉檢測許可權的反射方式執行10億次:1812

反射的方式要比普通的方式慢1000倍左右,而關閉了訪問許可權檢測後,速度可以提高3倍左右。

15.反射操作泛型

1.泛型

泛型是一種約束機制,確保程式碼的安全性,免去一些強制型別轉換。
通過反射操作泛型,要記住:泛型(generic)

package reflection;


import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

// 通過反射獲取泛型
public class Test11 {
    public static void main(String[] args) throws NoSuchMethodException {
        Class c1 = Test11.class;
        // 獲取方法
        Method method = c1.getDeclaredMethod("test01", Map.class, List.class);
        // generic(泛型)通過方法獲取引數泛型型別 一個是Map,一個List
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("#" + genericParameterType);
            // 輸出泛型型別中的引數型別。 首先判斷是否是 當前型別是否是引數化型別,然後強轉,得到實際使用型別陣列。列印輸出。
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method method2 = c1.getDeclaredMethod("test02", null);
        // 獲取方法的返回值泛型型別 其他和上面相同。
        Type genericReturnType = method2.getGenericReturnType();
        System.out.println(genericReturnType);
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }


    }

    public static void test01(Map<String, User> map, List<User> list) {

    }

    public static Map<String, User> test02() {
        System.out.println("測試");
        return null;
    }

}

16.反射操作註解

通過Class物件的getAnnotation()方法獲取註解,或者通過Class物件獲取到方法,屬性後通過方法,屬性的getAnnotation()方法獲取註解。
可以直接獲取註解裡定義的引數,然後根據這些引數,生成相應的程式碼,執行程式。這就是框架的基礎。
總結:框架的基礎:通過反射,獲取註解引數資訊,生成相應程式碼;通過反射在程式執行時,建立物件,進行相應的操作。

package reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

// 練習反射操作註解
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("reflection.Student2");
        // 通過反射獲取註解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 獲得指定註解的value的值
        TableName tableName = (TableName) c1.getAnnotation(TableName.class);
        String value = tableName.value();
        System.out.println(value);

        // 獲得類指定註解
        Field name = c1.getDeclaredField("name");
        FieldName fieldName = name.getAnnotation(FieldName.class);
        System.out.println(fieldName.columnName());
        System.out.println(fieldName.type());
        System.out.println(fieldName.length());
    }
}

@TableName("db_student")
class Student2 {

    @FieldName(columnName = "db_id", type = "int", length = 10)
    private int id;
    @FieldName(columnName = "db_age", type = "int", length = 10)
    private int age;
    @FieldName(columnName = "db_name", type = "varchar", length = 10)
    private String name;

    public Student2() {
    }

    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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


// 類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableName {
    String value();
}

// 屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldName {
    String columnName();
    String type();
    int length();
}

17.註解和反射總結

1.註解

Annotation:不是程式本身,起到註釋作用,同時可以被其他程式(如編譯器讀取)。
註解格式:@interface Name{宣告引數}
作用範圍:package,class,method,field等上面,相當於給他們添加了輔助資訊,我們可以通過反射獲取這些元資料
內建註解:@Override,@Deprecated,@SuppressWarnings
元註解:4個meta-annotation:@Target,@Retention,@Documented,@Inherited。註解的註解,說明其他註解。

2.反射

1.靜態語言,動態語言
2.Java反射機制提供的功能,方法。
3.Class類對於每一個物件都只有一個。
4.類載入過程。載入(生成Class物件在堆記憶體中),連結(校驗,準備(分配記憶體,賦預設值),解析),初始化(執行clinit方法,為靜態變數賦值)。
5.類載入器,雙親委派機制
6.通過Class物件的各種方法,操作這個類模板。建立物件(可以通過newInstance呼叫無參構造方法,也可以通過獲取構造方法的newInstance()方法建立),操作各種方法,屬性,獲取註解。
7.setAccessible(),關閉訪問許可權檢查,可以提高效能。(大概是不關閉的3倍效能)。
8.反射操作泛型,泛型(generic),呼叫方法獲取方法的泛型,類的泛型,還可以獲取到其中的具體型別。
9.反射獲取註解(重要): 通過反射獲取註解的引數資料,可以用來動態生成指定功能的程式。(各種框架的基礎)。