国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

church / 3386人閱讀

摘要:相比硬編碼,反射要復雜的多,但其給我們帶來了更大的靈活性。實際上構造函數也是類的靜態方法,因此使用關鍵字創建類的新對象也會被當做對類的靜態引用,從而觸發類加載器對類的加載。基礎基礎主要是為反射提供通用特性的接口或基類。

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

假如有一個簡單的繼承體系,讓我們看下在RTTI和Reflect不同情況下如何獲取類型信息。

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假定在編譯期,已經知道了所有的類型信息。在編碼時,可以直接使用具體的類型信息,這是我們最常見的類型用法。

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

硬編碼樣例如下:

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

}

/**
 * RTTI假定我們在編譯時已經知道了所有的類型
 * @return
 */
private static List createAnimals() {
    List 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);當我們從List中取出元素時,這時容器(容器內部所有的元素都被當做Object)會自動將結果轉型成Animal。這是RTTI最基本的用法,在Java中所有的類型轉換都是在運行時進行有效性檢查。這也是RTTI的含義,在運行時,識別一個對象的類型。

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 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 createAnimals() {
    List 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要復雜的多,但其給我們帶來了更大的靈活性。

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而做的準備工作主要包括三個步驟:

加載,這個是由類加載器執行。該步驟將查找字節碼文件,并根據字節碼創建一個Class對象。

鏈接,在鏈接階段將驗證類中的字節碼,為靜態域分配存儲空間,如果必要的話,將解析這個類創建的對其他類的引用。

初始化,如果該類有超類,則對其進行初始化,執行靜態初始化器和靜態初始化塊。初始化被延時到對靜態方法或非常數靜態域進行首次訪問時才執行。

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

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

ClassName.class,獲取Class對象最簡單最安全的方法,其在編譯時會受到編譯檢測,但上例中已經證實,該方法不會觸發初始化邏輯。

Class.forName,這是反射機制最常用的方法之一,可以在不知具體類型時,通過一個字符串加載所對應的Class對象。

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 getAnnotation(Class annotationClass) 返回程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null
Annotation[] getAnnotations() 返回該程序元素上存在的所有注解
boolean is AnnotationPresent(Class annotationClass) 判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

AnnotatedElement子類涵蓋所有可以出現Annotation的地方,其中包括:

Constructor 構造函數

Method 方法

Class 類型

Field 字段

Package 包

Parameter 參數

AnnotatedParameterizedType 泛型

AnnotatedTypeVariable 變量

AnnotatedArrayType 數組類型

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內部使用。

其子類主要包括:

Class 類型

Field 字段

Method 方法

Constructor 構造函數

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

所涉及方法如下:

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

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

Field 字段

Constructor 構造函數

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 獲取異常列表

鎖涉及的子類主要有:

Constructor 構造函數

Method 方法

樣例代碼如下:

public class TestBean {
    private String id;

    public 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 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 implements Callable {

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

    @Override
    public void run() {

    }


    public static void main(String... args){
        Class 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> 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 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, 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代理
* 實現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 用于生成代理對象。

其核心方法為:

/**
* 獲取代理類
* loader : 類加載器 * interfaces: 類實現的接口 * */ Class getProxyClass(ClassLoader loader, Class... interfaces); /* * 生成代理對象
* loader : 類加載器 * interfaces : 類實現的接口 * h : 動態代理回調 */ Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h); /* * 判斷是否為代理類
* * cl : 待判斷類 */ public static boolean isProxyClass(Class cl); /* * 獲取代理對象的InvocationHandler
* * 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接口,將方法調用分發給目標對象。

/**
 * 分發處理器
* 將請求挨個轉發給目標對象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目標對象集合 private final List targets; public DispatcherInvocationHandler(List 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 serviceLoader = ServiceLoader.load(Handler.class);
        List handlers = new ArrayList<>();
        Iterator 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的高級應用,大量用于各大框架中。對其的掌握有助于加深對框架的理解。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76818.html

相關文章

  • MyBatis初步

    摘要:本章主要介紹的是的基礎應用和源碼涉及的相關等,主要包含的內容有的簡介反射動態代理包含代理和代理使用和代碼生成器等。組件生命周期,如圖測試代碼生成器代碼生成器,又稱逆向工程。 本章主要介紹的是MyBatis的基礎應用和源碼涉及的相關等,主要包含的內容有MyBatis的簡介、反射、動態代理(包含JDK代理和cglib代理)、MyBatis使用和代碼生成器等。 1.1 MyBatis簡介 M...

    MASAILA 評論0 收藏0
  • Java 高級面試知識點匯總!

    摘要:適配器模式將一個類的接口轉換成客戶希望的另外一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。 1、常用設計模式 單例模式:懶漢式、餓漢式、雙重校驗鎖、靜態加載,內部類加載、枚舉類加載。保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 代理模式:動態代理和靜態代理,什么時候使用...

    since1986 評論0 收藏0
  • Java動態編程初探

    摘要:動態編程使用場景通過配置生成代碼,減少重復編碼,降低維護成本。動態生成字節碼操作字節碼的工具有,其中有兩個比較流行的,一個是,一個是。 作者簡介 傳恒,一個喜歡攝影和旅游的軟件工程師,先后從事餓了么物流蜂鳥自配送和蜂鳥眾包的開發,現在轉戰 Java,目前負責物流策略組分流相關業務的開發。 什么是動態編程 動態編程是相對于靜態編程而言的,平時我們討論比較多的靜態編程語言例如Java, 與動態...

    趙連江 評論0 收藏0

發表評論

0條評論

church

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<