Java高級程序員必備:反射、動態代理

1. Java類型系統

獲取Java類型系統,主要有兩個方式:一種是傳統的RTTI(Run-Time Type Identification),它假定咱們在編譯時已經知道了全部的類型信息;另外一種是反射(Reflect),它容許咱們在程序運行時獲取並使用類型信息。

假若有一個簡單的繼承體系,讓咱們看下在RTTI和Reflect不一樣狀況下如何獲取類型信息。java

Animal爲接口,定義getType以返回不一樣動物的類型,Cat、Dog、Elephant等爲具體實現類,均實現getType接口。通常狀況下,咱們會建立一個具體的對象(Cat,Dog,Elephant等),把它向上轉型爲Animal,並在程序後面直接使用Animal引用。設計模式

具體樣例代碼以下:數組

/**
 * 動物
 */
public interface Animal {
    /**
     * 獲取動物類型
     * @return
     */
    String getType();
}

/**
 * 動物具體子類 貓
 */
public class Cat implements Animal{

    @Override
    public String getType() {
        return "貓";
    }
}

/**
 * 動物具體子類 狗
 */
public class Dog implements Animal{
    @Override
    public String getType() {
        return "狗";
    }
}

/**
 * 動物具體實現 大象
 */
public class Elephant implements Animal{
    @Override
    public String getType() {
        return "大象";
    }
}

讓咱們看下相同的功能經過硬編碼反射兩個機制如何實現。安全

1.1. 硬編碼

RTTI假定在編譯期,已經知道了全部的類型信息。在編碼時,能夠直接使用具體的類型信息,這是咱們最多見的類型用法。

在編譯期,編譯器經過容器、泛型保障類型系統的完整性;在運行時,由類型轉換操做來確保這一點。app

硬編碼樣例以下:框架

public static void main(String... args){
    List<Animal> animals = createAnimals();
    for (Animal animal : animals){
        System.out.println(animal.getType());
    }

}

/**
 * RTTI假定咱們在編譯時已經知道了全部的類型
 * @return
 */
private static List<Animal> createAnimals() {
    List<Animal> animals = new ArrayList<>();
    animals.add(new Cat()); // 已知類型Cat
    animals.add(new Elephant()); // 已知類型Elephant
    animals.add(new Dog()); // 已知類型 Dog
    return animals;
}

在這個例子中,咱們把Cat、Elephant、Dog等向上轉型爲Animal並存放於List<Animal>中,在轉型過程當中丟失了具體的類型信息(只保留了接口信息Animal);當咱們從List<Animal>中取出元素時,這時容器(容器內部全部的元素都被當作Object)會自動將結果轉型成Animal。這是RTTI最基本的用法,在Java中全部的類型轉換都是在運行時進行有效性檢查。這也是RTTI的含義,在運行時,識別一個對象的類型。ide

1.2. Reflect

Reflect容許咱們在運行時獲取並使用類型信息,它主要用於在編譯階段沒法得到全部的類型信息的場景,如各種框架。

反射樣例以下:函數

private static final String[] ANIMAL_TYPES = new String[]{
        "com.example.reflectdemo.base.Cat",
        "com.example.reflectdemo.base.Elephant",
        "com.example.reflectdemo.base.Dog"
};

public static void main(String... args){
    List<Object> animals = createAnimals();
    for (Object animal : animals){
        System.out.println(invokeGetType(animal));
    }

}

/**
 * 利用反射API執行getType方法(等同於animal.getType)
 * @param animal
 * @return
 */
private static String invokeGetType(Object animal){
    try {
        Method getTypeMethod = Animal.class.getMethod("getType");
        return (String) getTypeMethod.invoke(animal);
    }catch (Exception e){
        return null;
    }

}

/**
 * 反射容許咱們在運行時獲取類型信息
 * @return
 */
private static List<Object> createAnimals() {
    List<Object> animals = new ArrayList<>();
    for (String cls : ANIMAL_TYPES){
        animals.add(instanceByReflect(cls));
    }
    return animals;
}

/**
 * 使用反射機制,在運行時動態的實例化對象(等同於new關鍵字)
 * @param clsStr
 * @return
 */
private static Object instanceByReflect(String clsStr) {
    try {
        // 經過反射獲取類型信息
        Class cls = Class.forName(clsStr);
        // 經過Class實例化對象
        Object object = cls.newInstance();
        return object;
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }

}

反射,能夠經過一組特殊的API,在運行時,動態執行全部Java硬編碼完成的功能(如對象建立、方法調用等)。性能

相比硬編碼,Java反射API要複雜的多,但其給咱們帶來了更大的靈活性。this

2. Class對象

要理解RTTI在Java中的工做原理,首先須要知道類型信息在Java中是如何表示的。這個工做是由稱爲Class對象的特殊對象完成的,它包含了與類相關的全部信息。Java使用Class對象來執行RTTI。

類是程序的一部分,每一個類都會有一個Class對象。每當編寫並編譯一個新類(動態代理、CGLIB、運行時編譯都能建立新類),就會產生一個Class對象,爲了生成這個類的對象,運行這個程序的JVM將使用稱爲「類加載器」的子系統。

2.1. Class Loader

類加載器子系統,是JVM體系重要的一環,主要完成將class二進制文件加載到JVM中,並將其轉換爲Class對象的過程。

類加載器子系統其實是一條類加載器鏈,可是隻有一個原生類加載器,它是JVM實現的一部分。原生類加載器加載的是可信類,包括Java API類,他們一般是從本地加載。在這條鏈中,一般不須要添加額外的類加載器,可是若是有特殊需求,能夠掛載新的類加載器(好比Web容器)。

全部的類都是在第一次使用時,動態加載到JVM中的,當程序建立第一次對類的靜態成員引用時,就會加載這個類。實際上構造函數也是類的靜態方法,所以使用new關鍵字建立類的新對象也會被當作對類的靜態引用,從而觸發類加載器對類的加載。

Java程序在它開始運行以前並不是被所有加載,各個部分是在須要時按需加載的。類加載器在加載類以前,首先檢查這個類的Class是否已經加載,若是還沒有加載,加載器會按照類名查找class文件,並對字節碼進行有效性校驗,一旦Class對象被載入內存,它就用來建立這個類的全部對象。

static初始化塊在類加載時調用,所以能夠用於觀察類在何時進行加載,樣例以下:

static class C1{
    static {
        System.out.println("C1");
    }
}

static class C2{
    static {
        System.out.println("C2");
    }
}

static class C3{
    static {
        System.out.println("C3");
    }
}

public static void main(String... args) throws Exception{
    System.out.println("new start");
    // 構造函數爲類的靜態引用,觸發類型加載
    new C1();
    new C1();
    System.out.println("new end");

    System.out.println();

    System.out.println("Class.forName start");
    // Class.forName爲Class上的靜態函數,用於強制加載Class
    Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
    Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
    System.out.println("Class.forName end");

    System.out.println();

    System.out.println("C3.class start");
    // Class引用,會觸發Class加載,可是不會觸發初始化
    Class c1 = C3.class;
    Class c2 = C3.class;
    System.out.println("C3.class end");

    System.out.println();

    System.out.println("c1.newInstance start");
    // 調用class上的方法,觸發初始化邏輯
    c1.newInstance();
    System.out.println("c1.newInstance end");

}

輸出結果爲:

new start
C1
new end

Class.forName start
C2
Class.forName end

C3.class start
C3.class end

c1.newInstance start
C3
c1.newInstance end

看結果,C3.class的調用不會自動的初始化該Class對象(調用static塊)。爲了使用Class而作的準備工做主要包括三個步驟:

  1. 加載,這個是由類加載器執行。該步驟將查找字節碼文件,並根據字節碼建立一個Class對象。
  2. 連接,在連接階段將驗證類中的字節碼,爲靜態域分配存儲空間,若是必要的話,將解析這個類建立的對其餘類的引用。
  3. 初始化,若是該類有超類,則對其進行初始化,執行靜態初始化器和靜態初始化塊。初始化被延時到對靜態方法或很是數靜態域進行首次訪問時才執行。

2.2. Class 實例獲取

Class對象做爲Java類型體系的入口,如何獲取實例成爲第一個要解決的問題。

Class對象的獲取主要有如下幾種途徑:

  1. ClassName.class,獲取Class對象最簡單最安全的方法,其在編譯時會受到編譯檢測,但上例中已經證明,該方法不會觸發初始化邏輯。
  2. Class.forName,這是反射機制最經常使用的方法之一,能夠在不知具體類型時,經過一個字符串加載所對應的Class對象。
  3. object.getClass,這也是比較經常使用的方式之一,經過一個對象獲取生成該對象的Class實例。

對於基本數據類型對於的包裝器類,還提供了一個TYPE字段,指向對應的基本類型的Class對象。

基本類型 TYPE類型
boolean.class Boolean.TYPE
char.class Char.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

2.3. Class 類型信息

Class對象存儲了一個class的全部類型信息,當獲取到Class對象後,便能經過API獲取到全部信息。

在進入Class類型信息以前,須要簡單的瞭解下幾個反射的基類,以便更好的理解反射實現體系。

2.3.1 ClassAPI 基礎

Class API基礎主要是爲反射API提供通用特性的接口或基類。因爲其通用性,現統一介紹,在具體的API中將對其進行忽略。

2.3.1.1 AnnotatedElement

AnnotatedElement爲Java1.5新增接口,該接口表明程序中能夠接受註解的程序元素,並提供統一的Annotation訪問方式,賦予API經過反射獲取Annotation的能力,當一個Annotation類型被定義爲運行時後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。

AnnotatedElement接口是全部註解元素(Class、Method、Field、Package和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的下列方法來訪問Annotation信息:

方法 含義
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 返回程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null
Annotation[] getAnnotations() 返回該程序元素上存在的全部註解
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false
Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。

AnnotatedElement子類涵蓋全部能夠出現Annotation的地方,其中包括:

  1. Constructor 構造函數
  2. Method 方法
  3. Class 類型
  4. Field 字段
  5. Package 包
  6. Parameter 參數
  7. AnnotatedParameterizedType 泛型
  8. AnnotatedTypeVariable 變量
  9. AnnotatedArrayType 數組類型
  10. AnnotatedWildcardType

樣例以下:

public class AnnotatedElementTest {

    public static void main(String... args){
        System.out.println("getAnnotations:");
        for (Annotation annotation :  A.class.getAnnotations()){
            System.out.println(annotation);
        }

        System.out.println();
        System.out.println("getAnnotation:" + A.class.getAnnotation(TestAnn1.class));

        System.out.println();
        System.out.println("isAnnotationPresent:" + A.class.isAnnotationPresent(TestAnn1.class));

    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnn1{

    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnn2{

    }

    @TestAnn1
    @TestAnn2
    public class A{

    }
}

輸出結果以下:

getAnnotations:
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn2()

getAnnotation:@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()

isAnnotationPresent:true

2.3.1.2. Member

Member用於標記反射中簡單元素。

所涉及方法以下:

方法 含義
getDeclaringClass 元素所在類
getName 元素名稱
getModifiers 元素修飾
isSynthetic 是否爲Synthetic,synthetic是由編譯器引入的字段、方法、類或其餘結構,主要用於JVM內部使用。

其子類主要包括:

  1. Class 類型
  2. Field 字段
  3. Method 方法
  4. Constructor 構造函數

2.3.1.3. AccessibleObject

AccessibleObject可訪問對象,其對元素的可見性進行統一封裝。同時實現AnnotatedElement接口,提供對Annotation元素的訪問。

所涉及方法以下:

方法 含義
isAccessible 是否可訪問
setAccessible 從新訪問性

其中AccessibleObject所涉及的子類主要包括:

  1. Field 字段
  2. Constructor 構造函數
  3. Method 方法

AccessibleObject 對可見性提供了強大的支持,使咱們可以經過反射擴展訪問限制,甚至能夠對private成員進行訪問。

樣例代碼以下:

public class TestBean {
    private String id;

    public String getId() {
        return id;
    }

    private void setId(String id) {
        this.id = id;
    }

}
public class AccessibleObjectBase {

    public static void main(String... args) throws Exception{
        TestBean testBean = new TestBean();
        // private方法, 不能直接調用
        Method setId = TestBean.class.getDeclaredMethod("setId", String.class);
        System.out.println("setId:" + setId.isAccessible());
        try {
            setId.invoke(testBean, "111");
        }catch (Exception e){
            System.out.println("private不能直接調用");
        }
        setId.setAccessible(true);
        System.out.println("設置可訪問:" + setId.isAccessible());

        setId.invoke(testBean, "111");
        System.out.println("設置可訪問後,能夠繞過private限制,進行調用,結果爲:" + testBean.getId());

    }
}

輸出結果以下:

setId:false
private不能直接調用
設置可訪問:true
設置可訪問後,能夠繞過private限制,進行調用,結果爲:111

2.3.1.4. Executable

Executable表示可執行元素的一種封裝,能夠獲取方法簽名相關信息。

所涉及方法以下:

方法 含義
getName 獲取名稱
getModifiers 獲取修飾符
getTypeParameters 獲取類型參數(泛型)
getParameterTypes 獲取參數列表
getParameterCount 獲取參數數量
getGenericParameterTypes 獲取參數類型
getExceptionTypes 獲取異常列表
getGenericExceptionTypes 獲取異常列表

鎖涉及的子類主要有:

  1. Constructor 構造函數
  2. Method 方法

樣例代碼以下:

public class TestBean {
    private String id;

    public <T, R>TestBean(String id) throws IllegalArgumentException, NotImplementedException {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    private void setId(String id) {
        this.id = id;
    }

}

public class ExecutableTest {
    public static void main(String... args) throws Exception{
        for (Constructor constructor : TestBean.class.getConstructors()){
            System.out.println("getName: " + constructor.getName());

            System.out.println();

            System.out.println("getModifiers: " + Modifier.toString(constructor.getModifiers()));

            System.out.println();

            System.out.println("getTypeParameters:");
            for (TypeVariable<Constructor> t : constructor.getTypeParameters()){
                System.out.println("type var:" + t.getName());
            }

            System.out.println();
            System.out.println("getParameterCount:" + constructor.getParameterCount());

            System.out.println();
            System.out.println("getParameterTypes:");
            for (Class cls : constructor.getParameterTypes()){
                System.out.println(cls.getName());
            }

            System.out.println();
            System.out.println("getExceptionTypes:");
            for (Class cls : constructor.getExceptionTypes()){
                System.out.println(cls.getName());
            }
        }
    }
}

輸出結果爲:

getName: com.example.reflectdemo.reflectbase.TestBean

getModifiers: public

getTypeParameters:
type var:T
type var:R

getParameterCount:1

getParameterTypes:
java.lang.String

getExceptionTypes:
java.lang.IllegalArgumentException
sun.reflect.generics.reflectiveObjects.NotImplementedException

2.3.1.5. 方法命名規則

整個反射機制存在着通用的命名規則,瞭解這些規則,能夠大大減小理解方法的阻力。

getXXXgetDeclaredXXX, 二者主要區別在於獲取元素的可見性不一樣,通常狀況下getXXX返回public類型的元素,而getDeclaredXXX獲取全部的元素,其中包括private、protected、public和package。

2.3.2. 類型信息

Class自身信息包括類名、包名、父類以及實現的接口等。

Class類實現AnnotatedElement接口,以提供對註解的支持。除此之外,涉及方法以下:

方法 含義
getName 獲取類名
getCanonicalName 獲得目標類的全名(包名+類名)
getSimpleName 等同於getCanonicalName
getTypeParameters 獲取類型參數(泛型)
getSuperclass 獲取父類
getPackage 獲取包信息
getInterfaces 獲取實現接口
getModifiers 獲取修飾符
isAnonymousClass 是否匿名類
isLocalClass 是否局部類
isMemberClass 是否成員類
isEnum 是否枚舉
isInterface 是不是接口
isArray 是不是數組
getComponentType 獲取數組元素類型
isPrimitive 是不是基本類型
isAnnotation 是不是註解
getEnumConstants 獲取枚舉全部類型
getClasses 獲取定義在該類中的public類型
getDeclaredClasses 獲取定義在該類中的類型

實例以下:

class Base<T> implements Callable<T> {

    @Override
    public T call() throws Exception {
        return null;
    }
}
public final class BaseClassInfo<T, R extends Runnable> extends Base<T> implements Runnable, Serializable {

    @Override
    public void run() {

    }


    public static void main(String... args){
        Class<BaseClassInfo> cls = BaseClassInfo.class;

        System.out.println("getName:" + cls.getName());
        System.out.println();
        System.out.println("getCanonicalName:"  + cls.getCanonicalName());
        System.out.println();
        System.out.println("getSimpleName:" + cls.getSimpleName());
        System.out.println();
        System.out.println("getSuperclass:" + cls.getSuperclass());
        System.out.println();
        System.out.println("getPackage:" + cls.getPackage());

        System.out.println();
        for (Class c : cls.getInterfaces()){
            System.out.println("interface : " + c.getSimpleName());
        }

        System.out.println();
        for (TypeVariable<Class<BaseClassInfo>> typeVariable : cls.getTypeParameters()){
            System.out.println("type var : " + typeVariable.getTypeName());
        }

        System.out.println();
        System.out.println("getModifiers:" + Modifier.toString(cls.getModifiers()));
    }
}

輸出結果爲:

getName:com.example.reflectdemo.classdetail.BaseClassInfo

getCanonicalName:com.example.reflectdemo.classdetail.BaseClassInfo

getSimpleName:BaseClassInfo

getSuperclass:class com.example.reflectdemo.classdetail.Base

getPackage:package com.example.reflectdemo.classdetail

interface : Runnable
interface : Serializable

type var : T
type var : R

getModifiers:public final

Class類型判斷,實例以下:

public class ClassTypeTest {

    public static void main(String... args){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                printClassType(getClass());
            }
        };
        System.out.println("匿名內部類");
        runnable.run();

        class M implements Runnable{

            @Override
            public void run() {
                printClassType(getClass());
            }
        }

        System.out.println("方法內部類");
        new M().run();

        System.out.println("內部類");
        new ClassTypeTest().new T().run();

        System.out.println("靜態內部類");
        new S().run();

        System.out.println("枚舉");
        printClassType(EnumTest.class);


        System.out.println("接口");
        printClassType(Runnable.class);

        System.out.println("數組");
        printClassType(int[].class);

        System.out.println("int");
        printClassType(int.class);

        System.out.println("註解");
        printClassType(AnnTest.class);

    }


    class T implements Runnable{

        @Override
        public void run() {
            printClassType(getClass());
        }
    }

    static class S implements Runnable{

        @Override
        public void run() {
            printClassType(getClass());
        }
    }

    enum EnumTest{
        A, B, C
    }

    @interface AnnTest{

    }


    private static void printClassType(Class cls){
        System.out.println("Class:" + cls.getName());
        System.out.println("isAnonymousClass:" + cls.isAnonymousClass());
        System.out.println("isLocalClass:" + cls.isLocalClass());
        System.out.println("isMemberClass:" + cls.isMemberClass());
        System.out.println("isEnum:" + cls.isEnum());
        System.out.println("isInterface:" + cls.isInterface());
        System.out.println("isArray:" + cls.isArray());
        System.out.println("isPrimitive:" + cls.isPrimitive());
        System.out.println("isAnnotation:" + cls.isAnnotation());

        if (cls.isEnum()){
            System.out.println("getEnumConstants:");
            for (Object o : cls.getEnumConstants()){
                System.out.println(o);
            }
        }

        if (cls.isArray()){
            System.out.println("getComponentType:" + cls.getComponentType());
        }
        System.out.println();
    }
}

輸出結果以下:

匿名內部類
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1
isAnonymousClass:true
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false

方法內部類
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M
isAnonymousClass:false
isLocalClass:true
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false

內部類
Class:com.example.reflectdemo.classdetail.ClassTypeTest$T
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false

靜態內部類
Class:com.example.reflectdemo.classdetail.ClassTypeTest$S
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false

枚舉
Class:com.example.reflectdemo.classdetail.ClassTypeTest$EnumTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:true
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
getEnumConstants:
A
B
C

接口
Class:java.lang.Runnable
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:false

數組
Class:[I
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:true
isPrimitive:false
isAnnotation:false
getComponentType:int

int
Class:int
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:true
isAnnotation:false

註解
Class:com.example.reflectdemo.classdetail.ClassTypeTest$AnnTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:true

內部類型樣例以下:

public class InnerClassTest {

    public static void main(String... args){
        System.out.println("getClasses");
        for (Class cls : InnerClassTest.class.getClasses()){
            System.out.println(cls.getName());
        }
    }

    public interface I{

    }

    public class A implements I{

    }

    public class B implements I{

    }
}

輸出結果以下:

getClasses
com.example.reflectdemo.classdetail.InnerClassTest$B
com.example.reflectdemo.classdetail.InnerClassTest$A
com.example.reflectdemo.classdetail.InnerClassTest$I

2.3.3. 對象實例化

對象實例化,主要經過Constructor實例完成,首先經過相關方法獲取Constructor對象,而後進行實例化操做。

所涉及的方法以下:

方法 含義
newInstance 使用默認構造函數實例化對象
getConstructors 獲取public構造函數
getConstructor(Class<?>... parameterTypes) 獲取特定public構造函數
getDeclaredConstructors 獲取全部的構造函數
getDeclaredConstructor 獲取特定構造函數

實例化涉及的核心類爲Constructor,Constructor繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關功能,其核心方法以下:

方法 含義
newInstance 調用構造函數,實例化對象

樣例以下:

public class TestBean {
    private final Integer id;
    private final String name;

    public <T, R>TestBean(Integer id, String name) throws IllegalArgumentException, NotImplementedException {
        this.id = id;
        this.name = name;
    }

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

public class ConstructorTest {
    public static void main(String... args) throws Exception{
        for (Constructor constructor : TestBean.class.getConstructors()){
            TestBean bean = (TestBean) constructor.newInstance(1, "Test");
            System.out.println("newInstance:" + bean);
        }
    }
}

輸出結果爲:

newInstance:TestBean{id=1, name='Test'}

2.3.4. 屬性信息

對象屬性是類型中最主要的信息之一,主要經過Field表示,首先經過相關方法獲取Field實例,而後進行屬性值操做。

所涉及的方法以下:

方法 含義
getFields 獲取public字段
getField(String name) 獲取特定public字段
getDeclaredFields 獲取全部的的屬性
getDeclaredField 獲取特定字段

Field繼承自AccessibleObject實現Member接口,擁有AccessibleObject、AnnotatedElement、Member相關功能,其核心方法以下:

方法 含義
isEnumConstant 是否枚舉常量
getType 獲取類型
get 獲取屬性值
getBoolean 獲取boolean值
getByte 獲取byte值
getChar 獲取chat值
getShort 獲取short值
getInt 獲取int值
getLong 獲取long值
getFloat 獲取float值
getDouble 獲取double值
set 設置屬性值
setBoolean 設置boolean值
setByte 設置byte值
setChar 設置char值
setShort 設置short值
setInt 設置int值
setLong 設置long值
setFloat 設置float值
setDouble 設置double值

實例以下:

public enum EnumTest {
    A
}

public class FieldBean {
    private EnumTest aEnum;
    private String aString;
    private boolean aBoolean;
    private byte aByte;
    private char aChar;
    private short aShort;
    private int anInt;
    private long aLong;
    private float aFloat;
    private double aDouble;

}


public class FieldTest {
    public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
        FieldBean fieldBean = new FieldBean();
        Field aEnum = getByName("aEnum");
        Field aString = getByName("aString");
        Field aBoolean = getByName("aBoolean");
        Field aByte = getByName("aByte");
        Field aChar = getByName("aChar");
        Field aShort = getByName("aShort");
        Field anInt = getByName("anInt");
        Field aLong = getByName("aLong");
        Field aFloat = getByName("aFloat");
        Field aDouble = getByName("aDouble");

        aEnum.set(fieldBean, EnumTest.A);
        System.out.println("isEnumConstant: " + aEnum.isEnumConstant());
        System.out.println("set and get enum : " + aEnum.get(fieldBean));

        aString.set(fieldBean, "Test");
        System.out.println("set and get String : " + aString.get(fieldBean));

        aBoolean.setBoolean(fieldBean, true);
        System.out.println("set and get Boolean : " + aBoolean.getBoolean(fieldBean));

        aByte.setByte(fieldBean, (byte) 1);
        System.out.println("set and get Byte : " + aByte.getByte(fieldBean));

        aChar.setChar(fieldBean, 'a');
        System.out.println("set and get Char : " + aChar.getChar(fieldBean));

        aShort.setShort(fieldBean, (short) 1);
        System.out.println("set and get Short : " + aShort.getShort(fieldBean));

        anInt.setInt(fieldBean, 1);
        System.out.println("set and get Int : " + anInt.getInt(fieldBean));

        aLong.setLong(fieldBean, 1L);
        System.out.println("set and get Long : " + aLong.getLong(fieldBean));

        aFloat.setFloat(fieldBean, 1f);
        System.out.println("set and get Float : " + aLong.getFloat(fieldBean));

        aDouble.setDouble(fieldBean, 1.1);
        System.out.println("set and get Double : " + aLong.getDouble(fieldBean));

    }

    private static Field getByName(String name) throws NoSuchFieldException {
        Field field = FieldBean.class.getDeclaredField(name);
        field.setAccessible(true);
        return field;
    }
}

2.3.5. 方法信息

類型中的方法經過Method表示,首先經過相關方法獲取Method實現,而後經過反射執行方法。

所涉及的方法以下:

方法 含義
getMethods 獲取public方法
getMethod(String name, Class<?>... parameterTypes) 獲取特定public方法
getDeclaredMethods 獲取全部方法
getDeclaredMethod 獲取特定方法

Method繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關功能,其核心方法以下:

方法 含義
getReturnType 獲取方法返回類型
invoke 調用方法
isBridge 是否爲橋接方法。橋接方法是 JDK 1.5 引入泛型後,爲了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法。咱們能夠經過Method.isBridge()方法來判斷一個方法是不是橋接方法。
isDefault 是否爲默認方法

實例以下:

public interface SayHi {
    String get();

    default void hi(){
        System.out.println("Hi " + get());
    }
}
public class MethodBean implements Function<String, String>, SayHi {
    private final String name;

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

    @Override
    public String get() {
        return "Hi " + name;
    }

    @Override
    public String apply(String s) {
        return s + name;
    }
}
public class MethodTest {
    public static void main(String... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method strMethod = MethodBean.class.getDeclaredMethod("apply", String.class);
        Method objMethod = MethodBean.class.getDeclaredMethod("apply", Object.class);
        Method hiMethod = SayHi.class.getDeclaredMethod("hi");

        MethodBean methodBean = new MethodBean("張三");

        System.out.println("Return Type:");
        System.out.println("getMethod(String):" + strMethod.getReturnType());
        System.out.println("getMethod(Object):" + objMethod.getReturnType());
        System.out.println("hi():" + hiMethod.getReturnType());

        System.out.println();
        System.out.println("isBridge:");
        System.out.println("getMethod(String):" + strMethod.isBridge());
        System.out.println("getMethod(Object):" + objMethod.isBridge());
        System.out.println("hi():" + hiMethod.isBridge());


        System.out.println();
        System.out.println("isDefault:");
        System.out.println("getMethod(String):" + strMethod.isDefault());
        System.out.println("getMethod(Object):" + objMethod.isDefault());
        System.out.println("hi():" + hiMethod.isDefault());


        System.out.println();
        System.out.println("invoke:");
        System.out.println("invoke(String):" + strMethod.invoke(methodBean, "Test"));
        System.out.println("invoke(Object):" + objMethod.invoke(methodBean, "Test"));
        System.out.println("hi():" + hiMethod.invoke(methodBean));


    }
}

輸出結果:

Return Type:
getMethod(String):class java.lang.String
getMethod(Object):class java.lang.Object
hi():void

isBridge:
getMethod(String):false
getMethod(Object):true
hi():false

isDefault:
getMethod(String):false
getMethod(Object):false
hi():true

invoke:
invoke(String):Test張三
invoke(Object):Test張三
Hi Hi 張三
hi():null

2.3.6. 其餘

除上述核心方法外,Class對象提供了一些使用方法。

所涉及方法以下:

方法 含義
isInstance 判斷某對象是不是該類的實例
isAssignableFrom 斷定此 Class 對象所表示的類或接口與指定的 Class 參數所表示的類或接口是否相同,或是不是其超類或超接口。若是是則返回 true;不然返回 false。
getClassLoader 獲取加載當前類的ClassLoader
getResourceAsStream 根據該ClassLoader加載資源
getResource 根據該ClassLoader加載資源
public class Task implements Runnable{
    @Override
    public void run() {

    }
}
public class OtherTest {
    public static void main(String...args){
        Task task = new Task();
        System.out.println("Runnable isInstance Task:" + Runnable.class.isInstance(task));
        System.out.println("Task isInstance Task:" + Task.class.isInstance(task));

        System.out.println("Task isAssignableFrom Task:" + Task.class.isAssignableFrom(Task.class));

        System.out.println("Runnable isAssignableFrom Task :" + Runnable.class.isAssignableFrom(Task.class));
    }
}

輸出結果:

Runnable isInstance Task:true
Task isInstance Task:true
Task isAssignableFrom Task:true
Runnable isAssignableFrom Task :true

3. 動態代理

代理是基本的設計模式之一,它是咱們爲了提供額外的或不一樣的操做,而插入的用來代替「實際」對象的對象。這些操做一般與「實際」對象通訊,所以代理一般充當中間人的角色。

例如,咱們已有一個Handler接口,和一個實現類HandlerImpl,現須要對其進行性能統計,使用代理模式,代碼以下:

/**
 * handler接口
 */
public interface Handler {
    /**
     * 數據處理
     * @param data
     */
    void handle(String data);
}

/**
 * Handler 實現
 */
public class HandlerImpl implements Handler{
    @Override
    public void handle(String data) {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * Handler代理<br />
 * 實現Handler接口,記錄耗時狀況,並將請求發送給目標對象
 */
public class HandlerProxy implements Handler{
    private final Handler handler;

    public HandlerProxy(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void handle(String data) {
        long start = System.currentTimeMillis();
        this.handler.handle(data);
        long end = System.currentTimeMillis();
        System.out.println("cost " + (end - start) + " ms");
    }
}

public static void main(String... args){
    Handler handler = new HandlerImpl();
    Handler proxy = new HandlerProxy(handler);
    proxy.handle("Test");
}

採用代理模式,比較優雅的解決了該問題,但若是Handler接口存在多個方法,而且須要對全部方法進行性能監控,那HandlerProxy的複雜性將會提升。
Java動態代理比代理更進一步,由於它能夠動態的建立代理並動態的處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上。

3.1. InvocationHandler

InvocationHandler 是由動態代理處理器實現的接口,對代理對象的方法調用,會路由到該處理器上進行統一處理。

其只有一個核心方法:

/**
* proxy : 代理對象
* method : 調用方法
* args : 調用方法參數
**/
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

3.2. Proxy

Proxy 用於生成代理對象。

其核心方法爲:

/**
* 獲取代理類<br />
* loader : 類加載器
* interfaces: 類實現的接口
*
*/
Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces);
/*
* 生成代理對象<br />
* loader : 類加載器
* interfaces : 類實現的接口
* h : 動態代理回調
*/
Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h);

/*
* 判斷是否爲代理類<br />
* 
* cl : 待判斷類
*/
public static boolean isProxyClass(Class<?> cl);

/*
* 獲取代理對象的InvocationHandler <br />
*
* proxy : 代理對象
*/
InvocationHandler getInvocationHandler(Object proxy);

3.3. demo

對於以前的性能監控,使用Java動態代理怎麼實現?
/**
 * 定義代理方法回調處理器
 */
public class CostInvocationHandler implements InvocationHandler {
    // 目標對象
    private final Object target;

    public CostInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("call method " + method + " ,args " + args);
        long start = System.currentTimeMillis();
        try {
            // 將請求轉發給目標對象
            return method.invoke(this.target, args);
        }finally {
            long end = System.currentTimeMillis();
            System.out.println("cost " + (end - start) + "ms");
        }


    }
}
public static void main(String... args){
    Handler handler = new HandlerImpl();

    CostInvocationHandler invocationHandler = new CostInvocationHandler(handler);

    Class cls = Proxy.getProxyClass(DHandlerMain.class.getClassLoader(), Handler.class);

    Handler proxy = (Handler) Proxy.newProxyInstance(DHandlerMain.class.getClassLoader(),
            new Class[]{Handler.class},
            invocationHandler);

    System.out.println("invoke method");
    proxy.handle("Test");
    System.out.println("isProxyClass: " + Proxy.isProxyClass(cls));
    System.out.println("getInvocationHandler: " + (invocationHandler == Proxy.getInvocationHandler(proxy)));
}

4. 基於SPI的Plugin

SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。 目前有很多框架用它來作服務的擴展發現,它是一種動態替換髮現的機制。

具體用法是在JAR包的"META-INF/services/"目錄下創建一個文件,文件名是接口的全限定名,文件的內容能夠有多行,每行都是該接口對應的具體實現類的全限定名。而後使用 ServiceLoader.load(Interface.class) 對插件進行加載。

假定,現有個場景,須要對消息進行處理,但消息處理器的實現須要放開,及能夠動態的對處理器進行加載,當有新消息到達時,依次調用處理器對消息進行處理,讓咱們結合SPI和反射構造一個簡單的Plugin系統。

首先咱們須要一個插件接口和若干個實現類:

/**
 * 插件接口
 */
public interface Handler {
    void handle(String msg);
}
/**
 * 實現1
 */
public class Handler1 implements Handler{
    @Override
    public void handle(String msg) {
        System.out.println("Handler1:" + msg);
    }
}
/**
 * 實現2
 */
public class Handler2 implements Handler{
    @Override
    public void handle(String msg) {
        System.out.println("Handler2:" + msg);
    }
}

而後,咱們添加SPI配置,及在META-INF/services/com.example.reflectdemo.plugin.Handler添加配置信息:

com.example.reflectdemo.plugin.Handler1
com.example.reflectdemo.plugin.Handler2

其次,咱們實現DispatcherInvocationHandler類繼承自InvocationHandler接口,將方法調用分發給目標對象。

/**
 * 分發處理器<br />
 * 將請求挨個轉發給目標對象
 */
public class DispatcherInvocationHandler implements InvocationHandler {
    // 目標對象集合
    private final List<Object> targets;

    public DispatcherInvocationHandler(List<Object> targets) {
        this.targets = targets;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        for (Object target : targets){
            // 將請求轉發給目標對象
            method.invoke(target, args);
        }
        return null;
    }
}

實現主流程,經過SPI加裝插件,將插件做爲轉發對象實例化DispatcherInvocationHandler,在經過Proxy構建動態代理對象,最後調用handle方法進行業務處理。

public static void main(String... args){
        // 使用SPI加載插件
        ServiceLoader<Handler> serviceLoader = ServiceLoader.load(Handler.class);
        List<Object> handlers = new ArrayList<>();
        Iterator<Handler> handlerIterator = serviceLoader.iterator();
        while (handlerIterator.hasNext()){
            Handler handler = handlerIterator.next();
            handlers.add(handler);
        }
        // 將加載的插件組裝成InvocationHandler,以進行分發處理
        DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers);
        // 生成代理對象
        Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler);
        // 調用handle方法
        proxy.handle("Test");
    }

運行結果以下:

Handler1:Test
Handler2:Test

5. 總結

Java類型系統、反射、動態代理,做爲Java的高級應用,大量用於各大框架中。對其的掌握有助於加深對框架的理解。
相關文章
相關標籤/搜索