亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Java高級程序員必備:反射、動態(tài)代理

church / 3481人閱讀

摘要:相比硬編碼,反射要復(fù)雜的多,但其給我們帶來了更大的靈活性。實(shí)際上構(gòu)造函數(shù)也是類的靜態(tài)方法,因此使用關(guān)鍵字創(chuàng)建類的新對象也會被當(dāng)做對類的靜態(tài)引用,從而觸發(fā)類加載器對類的加載?;A(chǔ)基礎(chǔ)主要是為反射提供通用特性的接口或基類。

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

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

Animal為接口,定義getType以返回不同動物的類型,Cat、Dog、Elephant等為具體實(shí)現(xiàn)類,均實(shí)現(xiàn)getType接口。一般情況下,我們會創(chuàng)建一個具體的對象(Cat,Dog,Elephant等),把它向上轉(zhuǎn)型為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 "狗";
    }
}

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

讓我們看下相同的功能通過硬編碼反射兩個機(jī)制如何實(shí)現(xiàn)。

1.1. 硬編碼
RTTI假定在編譯期,已經(jīng)知道了所有的類型信息。在編碼時,可以直接使用具體的類型信息,這是我們最常見的類型用法。

在編譯期,編譯器通過容器、泛型保障類型系統(tǒng)的完整性;在運(yùn)行時,由類型轉(zhuǎn)換操作來確保這一點(diǎn)。

硬編碼樣例如下:

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

}

/**
 * RTTI假定我們在編譯時已經(jīng)知道了所有的類型
 * @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等向上轉(zhuǎn)型為Animal并存放于List中,在轉(zhuǎn)型過程中丟失了具體的類型信息(只保留了接口信息Animal);當(dāng)我們從List中取出元素時,這時容器(容器內(nèi)部所有的元素都被當(dāng)做Object)會自動將結(jié)果轉(zhuǎn)型成Animal。這是RTTI最基本的用法,在Java中所有的類型轉(zhuǎn)換都是在運(yùn)行時進(jìn)行有效性檢查。這也是RTTI的含義,在運(yùn)行時,識別一個對象的類型。

1.2. Reflect
Reflect允許我們在運(yùn)行時獲取并使用類型信息,它主要用于在編譯階段無法獲得所有的類型信息的場景,如各類框架。

反射樣例如下:

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執(zhí)行g(shù)etType方法(等同于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;
    }

}

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

/**
 * 使用反射機(jī)制,在運(yùn)行時動態(tài)的實(shí)例化對象(等同于new關(guān)鍵字)
 * @param clsStr
 * @return
 */
private static Object instanceByReflect(String clsStr) {
    try {
        // 通過反射獲取類型信息
        Class cls = Class.forName(clsStr);
        // 通過Class實(shí)例化對象
        Object object = cls.newInstance();
        return object;
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }

}

反射,可以通過一組特殊的API,在運(yùn)行時,動態(tài)執(zhí)行所有Java硬編碼完成的功能(如對象創(chuàng)建、方法調(diào)用等)。

相比硬編碼,Java反射API要復(fù)雜的多,但其給我們帶來了更大的靈活性。

2. Class對象
要理解RTTI在Java中的工作原理,首先需要知道類型信息在Java中是如何表示的。這個工作是由稱為Class對象的特殊對象完成的,它包含了與類相關(guān)的所有信息。Java使用Class對象來執(zhí)行RTTI。

類是程序的一部分,每個類都會有一個Class對象。每當(dāng)編寫并編譯一個新類(動態(tài)代理、CGLIB、運(yùn)行時編譯都能創(chuàng)建新類),就會產(chǎn)生一個Class對象,為了生成這個類的對象,運(yùn)行這個程序的JVM將使用稱為“類加載器”的子系統(tǒng)。

2.1. Class Loader
類加載器子系統(tǒng),是JVM體系重要的一環(huán),主要完成將class二進(jìn)制文件加載到JVM中,并將其轉(zhuǎn)換為Class對象的過程。

類加載器子系統(tǒng)實(shí)際上是一條類加載器鏈,但是只有一個原生類加載器,它是JVM實(shí)現(xiàn)的一部分。原生類加載器加載的是可信類,包括Java API類,他們通常是從本地加載。在這條鏈中,通常不需要添加額外的類加載器,但是如果有特殊需求,可以掛載新的類加載器(比如Web容器)。

所有的類都是在第一次使用時,動態(tài)加載到JVM中的,當(dāng)程序創(chuàng)建第一次對類的靜態(tài)成員引用時,就會加載這個類。實(shí)際上構(gòu)造函數(shù)也是類的靜態(tài)方法,因此使用new關(guān)鍵字創(chuàng)建類的新對象也會被當(dāng)做對類的靜態(tài)引用,從而觸發(fā)類加載器對類的加載。

Java程序在它開始運(yùn)行之前并非被全部加載,各個部分是在需要時按需加載的。類加載器在加載類之前,首先檢查這個類的Class是否已經(jīng)加載,如果尚未加載,加載器會按照類名查找class文件,并對字節(jié)碼進(jìn)行有效性校驗(yàn),一旦Class對象被載入內(nèi)存,它就用來創(chuàng)建這個類的所有對象。

static初始化塊在類加載時調(diào)用,因此可以用于觀察類在什么時候進(jìn)行加載,樣例如下:

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");
    // 構(gòu)造函數(shù)為類的靜態(tài)引用,觸發(fā)類型加載
    new C1();
    new C1();
    System.out.println("new end");

    System.out.println();

    System.out.println("Class.forName start");
    // Class.forName為Class上的靜態(tài)函數(shù),用于強(qiáng)制加載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引用,會觸發(fā)Class加載,但是不會觸發(fā)初始化
    Class c1 = C3.class;
    Class c2 = C3.class;
    System.out.println("C3.class end");

    System.out.println();

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

}

輸出結(jié)果為:

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

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

加載,這個是由類加載器執(zhí)行。該步驟將查找字節(jié)碼文件,并根據(jù)字節(jié)碼創(chuàng)建一個Class對象。

鏈接,在鏈接階段將驗(yàn)證類中的字節(jié)碼,為靜態(tài)域分配存儲空間,如果必要的話,將解析這個類創(chuàng)建的對其他類的引用。

初始化,如果該類有超類,則對其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。初始化被延時到對靜態(tài)方法或非常數(shù)靜態(tài)域進(jìn)行首次訪問時才執(zhí)行。

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

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

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

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

object.getClass,這也是比較常用的方式之一,通過一個對象獲取生成該對象的Class實(shí)例。

對于基本數(shù)據(jù)類型對于的包裝器類,還提供了一個TYPE字段,指向?qū)?yīng)的基本類型的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的所有類型信息,當(dāng)獲取到Class對象后,便能通過API獲取到所有信息。

在進(jìn)入Class類型信息之前,需要簡單的了解下幾個反射的基類,以便更好的理解反射實(shí)現(xiàn)體系。

2.3.1 ClassAPI 基礎(chǔ)
Class API基礎(chǔ)主要是為反射API提供通用特性的接口或基類。由于其通用性,現(xiàn)統(tǒng)一介紹,在具體的API中將對其進(jìn)行忽略。
2.3.1.1 AnnotatedElement
AnnotatedElement為Java1.5新增接口,該接口代表程序中可以接受注解的程序元素,并提供統(tǒng)一的Annotation訪問方式,賦予API通過反射獲取Annotation的能力,當(dāng)一個Annotation類型被定義為運(yùn)行時后,該注解才能是運(yùn)行時可見,當(dāng)class文件被裝載時被保存在class文件中的Annotation才會被虛擬機(jī)讀取。

AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的下列方法來訪問Annotation信息:

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

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

Constructor 構(gòu)造函數(shù)

Method 方法

Class 類型

Field 字段

Package 包

Parameter 參數(shù)

AnnotatedParameterizedType 泛型

AnnotatedTypeVariable 變量

AnnotatedArrayType 數(shù)組類型

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{

    }
}

輸出結(jié)果如下:

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用于標(biāo)記反射中簡單元素。

所涉及方法如下:

方法 含義
getDeclaringClass 元素所在類
getName 元素名稱
getModifiers 元素修飾
isSynthetic 是否為Synthetic,synthetic是由編譯器引入的字段、方法、類或其他結(jié)構(gòu),主要用于JVM內(nèi)部使用。

其子類主要包括:

Class 類型

Field 字段

Method 方法

Constructor 構(gòu)造函數(shù)

2.3.1.3. AccessibleObject
AccessibleObject可訪問對象,其對元素的可見性進(jìn)行統(tǒng)一封裝。同時實(shí)現(xiàn)AnnotatedElement接口,提供對Annotation元素的訪問。

所涉及方法如下:

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

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

Field 字段

Constructor 構(gòu)造函數(shù)

Method 方法

AccessibleObject 對可見性提供了強(qiáng)大的支持,使我們能夠通過反射擴(kuò)展訪問限制,甚至可以對private成員進(jìn)行訪問。

樣例代碼如下:

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方法, 不能直接調(diào)用
        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不能直接調(diào)用");
        }
        setId.setAccessible(true);
        System.out.println("設(shè)置可訪問:" + setId.isAccessible());

        setId.invoke(testBean, "111");
        System.out.println("設(shè)置可訪問后,可以繞過private限制,進(jìn)行調(diào)用,結(jié)果為:" + testBean.getId());

    }
}

輸出結(jié)果如下:

setId:false
private不能直接調(diào)用
設(shè)置可訪問:true
設(shè)置可訪問后,可以繞過private限制,進(jìn)行調(diào)用,結(jié)果為:111
2.3.1.4. Executable
Executable表示可執(zhí)行元素的一種封裝,可以獲取方法簽名相關(guān)信息。

所涉及方法如下:

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

鎖涉及的子類主要有:

Constructor 構(gòu)造函數(shù)

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());
            }
        }
    }
}

輸出結(jié)果為:

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. 方法命名規(guī)則
整個反射機(jī)制存在著通用的命名規(guī)則,了解這些規(guī)則,可以大大減少理解方法的阻力。

getXXXgetDeclaredXXX, 兩者主要區(qū)別在于獲取元素的可見性不同,一般情況下getXXX返回public類型的元素,而getDeclaredXXX獲取所有的元素,其中包括private、protected、public和package。

2.3.2. 類型信息
Class自身信息包括類名、包名、父類以及實(shí)現(xiàn)的接口等。

Class類實(shí)現(xiàn)AnnotatedElement接口,以提供對注解的支持。除此以外,涉及方法如下:

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

實(shí)例如下:

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()));
    }
}

輸出結(jié)果為:

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類型判斷,實(shí)例如下:

public class ClassTypeTest {

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

        class M implements Runnable{

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

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

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

        System.out.println("靜態(tài)內(nèi)部類");
        new S().run();

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


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

        System.out.println("數(shù)組");
        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();
    }
}

輸出結(jié)果如下:

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

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

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

靜態(tài)內(nèi)部類
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

數(shù)組
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

內(nèi)部類型樣例如下:

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{

    }
}

輸出結(jié)果如下:

getClasses
com.example.reflectdemo.classdetail.InnerClassTest$B
com.example.reflectdemo.classdetail.InnerClassTest$A
com.example.reflectdemo.classdetail.InnerClassTest$I
2.3.3. 對象實(shí)例化
對象實(shí)例化,主要通過Constructor實(shí)例完成,首先通過相關(guān)方法獲取Constructor對象,然后進(jìn)行實(shí)例化操作。

所涉及的方法如下:

方法 含義
newInstance 使用默認(rèn)構(gòu)造函數(shù)實(shí)例化對象
getConstructors 獲取public構(gòu)造函數(shù)
getConstructor(Class... parameterTypes) 獲取特定public構(gòu)造函數(shù)
getDeclaredConstructors 獲取所有的構(gòu)造函數(shù)
getDeclaredConstructor 獲取特定構(gòu)造函數(shù)

實(shí)例化涉及的核心類為Constructor,Constructor繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關(guān)功能,其核心方法如下:

方法 含義
newInstance 調(diào)用構(gòu)造函數(shù),實(shí)例化對象

樣例如下:

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);
        }
    }
}

輸出結(jié)果為:

newInstance:TestBean{id=1, name="Test"}
2.3.4. 屬性信息
對象屬性是類型中最主要的信息之一,主要通過Field表示,首先通過相關(guān)方法獲取Field實(shí)例,然后進(jìn)行屬性值操作。

所涉及的方法如下:

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

Field繼承自AccessibleObject實(shí)現(xiàn)Member接口,擁有AccessibleObject、AnnotatedElement、Member相關(guān)功能,其核心方法如下:

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

實(shí)例如下:

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表示,首先通過相關(guān)方法獲取Method實(shí)現(xiàn),然后通過反射執(zhí)行方法。

所涉及的方法如下:

方法 含義
getMethods 獲取public方法
getMethod(String name, Class... parameterTypes) 獲取特定public方法
getDeclaredMethods 獲取所有方法
getDeclaredMethod 獲取特定方法

Method繼承自Executable,擁有AnnotatedElement、AccessibleObject、Executable等相關(guān)功能,其核心方法如下:

方法 含義
getReturnType 獲取方法返回類型
invoke 調(diào)用方法
isBridge 是否為橋接方法。橋接方法是 JDK 1.5 引入泛型后,為了使Java的泛型方法生成的字節(jié)碼和 1.5 版本前的字節(jié)碼相兼容,由編譯器自動生成的方法。我們可以通過Method.isBridge()方法來判斷一個方法是否是橋接方法。
isDefault 是否為默認(rèn)方法

實(shí)例如下:

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));


    }
}

輸出結(jié)果:

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 判斷某對象是否是該類的實(shí)例
isAssignableFrom 判定此 Class 對象所表示的類或接口與指定的 Class 參數(shù)所表示的類或接口是否相同,或是否是其超類或超接口。如果是則返回 true;否則返回 false。
getClassLoader 獲取加載當(dāng)前類的ClassLoader
getResourceAsStream 根據(jù)該ClassLoader加載資源
getResource 根據(jù)該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));
    }
}

輸出結(jié)果:

Runnable isInstance Task:true
Task isInstance Task:true
Task isAssignableFrom Task:true
Runnable isAssignableFrom Task :true
3. 動態(tài)代理
代理是基本的設(shè)計(jì)模式之一,它是我們?yōu)榱颂峁╊~外的或不同的操作,而插入的用來代替“實(shí)際”對象的對象。這些操作通常與“實(shí)際”對象通信,因此代理通常充當(dāng)中間人的角色。

例如,我們已有一個Handler接口,和一個實(shí)現(xiàn)類HandlerImpl,現(xiàn)需要對其進(jìn)行性能統(tǒng)計(jì),使用代理模式,代碼如下:

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

/**
 * Handler 實(shí)現(xiàn)
 */
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代理
* 實(shí)現(xiàn)Handler接口,記錄耗時情況,并將請求發(fā)送給目標(biāo)對象 */ 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"); }

采用代理模式,比較優(yōu)雅的解決了該問題,但如果Handler接口存在多個方法,并且需要對所有方法進(jìn)行性能監(jiān)控,那HandlerProxy的復(fù)雜性將會提高。
Java動態(tài)代理比代理更進(jìn)一步,因?yàn)樗梢詣討B(tài)的創(chuàng)建代理并動態(tài)的處理對所代理方法的調(diào)用。在動態(tài)代理上所做的所有調(diào)用都會被重定向到單一的調(diào)用處理器上。

3.1. InvocationHandler
InvocationHandler 是由動態(tài)代理處理器實(shí)現(xiàn)的接口,對代理對象的方法調(diào)用,會路由到該處理器上進(jìn)行統(tǒng)一處理。

其只有一個核心方法:

/**
* proxy : 代理對象
* method : 調(diào)用方法
* args : 調(diào)用方法參數(shù)
**/
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
3.2. Proxy
Proxy 用于生成代理對象。

其核心方法為:

/**
* 獲取代理類
* loader : 類加載器 * interfaces: 類實(shí)現(xiàn)的接口 * */ Class getProxyClass(ClassLoader loader, Class... interfaces); /* * 生成代理對象
* loader : 類加載器 * interfaces : 類實(shí)現(xiàn)的接口 * h : 動態(tài)代理回調(diào) */ Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h); /* * 判斷是否為代理類
* * cl : 待判斷類 */ public static boolean isProxyClass(Class cl); /* * 獲取代理對象的InvocationHandler
* * proxy : 代理對象 */ InvocationHandler getInvocationHandler(Object proxy);
3.3. demo
對于之前的性能監(jiān)控,使用Java動態(tài)代理怎么實(shí)現(xiàn)?
/**
 * 定義代理方法回調(diào)處理器
 */
public class CostInvocationHandler implements InvocationHandler {
    // 目標(biāo)對象
    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 {
            // 將請求轉(zhuǎn)發(fā)給目標(biāo)對象
            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內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。 目前有不少框架用它來做服務(wù)的擴(kuò)展發(fā)現(xiàn),它是一種動態(tài)替換發(fā)現(xiàn)的機(jī)制。

具體用法是在JAR包的"META-INF/services/"目錄下建立一個文件,文件名是接口的全限定名,文件的內(nèi)容可以有多行,每行都是該接口對應(yīng)的具體實(shí)現(xiàn)類的全限定名。然后使用 ServiceLoader.load(Interface.class) 對插件進(jìn)行加載。

假定,現(xiàn)有個場景,需要對消息進(jìn)行處理,但消息處理器的實(shí)現(xiàn)需要放開,及可以動態(tài)的對處理器進(jìn)行加載,當(dāng)有新消息到達(dá)時,依次調(diào)用處理器對消息進(jìn)行處理,讓我們結(jié)合SPI和反射構(gòu)造一個簡單的Plugin系統(tǒng)。

首先我們需要一個插件接口和若干個實(shí)現(xiàn)類:

/**
 * 插件接口
 */
public interface Handler {
    void handle(String msg);
}
/**
 * 實(shí)現(xiàn)1
 */
public class Handler1 implements Handler{
    @Override
    public void handle(String msg) {
        System.out.println("Handler1:" + msg);
    }
}
/**
 * 實(shí)現(xiàn)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

其次,我們實(shí)現(xiàn)DispatcherInvocationHandler類繼承自InvocationHandler接口,將方法調(diào)用分發(fā)給目標(biāo)對象。

/**
 * 分發(fā)處理器
* 將請求挨個轉(zhuǎn)發(fā)給目標(biāo)對象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目標(biāo)對象集合 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){ // 將請求轉(zhuǎn)發(fā)給目標(biāo)對象 method.invoke(target, args); } return null; } }

實(shí)現(xiàn)主流程,通過SPI加裝插件,將插件作為轉(zhuǎn)發(fā)對象實(shí)例化DispatcherInvocationHandler,在通過Proxy構(gòu)建動態(tài)代理對象,最后調(diào)用handle方法進(jìn)行業(yè)務(wù)處理。

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,以進(jìn)行分發(fā)處理
        DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers);
        // 生成代理對象
        Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler);
        // 調(diào)用handle方法
        proxy.handle("Test");
    }

運(yùn)行結(jié)果如下:

Handler1:Test
Handler2:Test
5. 總結(jié)
Java類型系統(tǒng)、反射、動態(tài)代理,作為Java的高級應(yīng)用,大量用于各大框架中。對其的掌握有助于加深對框架的理解。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/76818.html

相關(guān)文章

  • MyBatis初步

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

    MASAILA 評論0 收藏0
  • Java 高級面試知識點(diǎn)匯總!

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

    since1986 評論0 收藏0
  • Java動態(tài)編程初探

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

    趙連江 評論0 收藏0

發(fā)表評論

0條評論

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