JAVA反射教程-全網(wǎng)最全

Java-Reflection(JAVA反射)

全網(wǎng)最全--轉載請著名

定義

Java-Reflection(JAVA反射)是在運行狀態(tài)中丸冕,對于任意一個類,都能夠知道這個類的所有屬性和方法除盏;對于任意一個對象叉橱,都能夠通過Java-Reflection來調用它的任意方法和屬性(不管是公共的還是私有的)。
這種動態(tài)獲取信息以及動態(tài)調用對象方法的行為被稱為java的反射機制者蠕。

用途

與普通的類編譯靜態(tài)生成不一樣的是窃祝,Java-Reflection能夠動態(tài)的生成類,所以Java-Reflection是Java框架開發(fā)的重要的一部分踱侣,很多優(yōu)秀的開源框架都是通過反射完成的粪小。

Java-Reflection可以在在日常的第三方應用開發(fā)過程中,會遇到某個類的某個成員變量抡句、方法或是屬性是私有的或是只對系統(tǒng)應用開放探膊,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法。

使用主要種類

與Java-Reflection相關種類主要分為:

類名 用途
Class類 代表類的實體待榔,在運行的Java應用程序中表示類和接口
Field類 代表類的成員變量(成員變量也稱為類的屬性)
Method類 代表類的方法
Constructor類 代表類的構造方法
Annotation類 代表類的注解

基礎技術

Class類

獲得類相關的方法

方法 用途
asSubclass(Class<U> clazz) 把傳遞的類的對象轉換成代表其子類的對象
Cast 把對象轉換成代表類或是接口的對象
getClassLoader() 獲得類的加載器
getClasses() 返回一個數(shù)組逞壁,數(shù)組中包含該類中所有公共類和接口類的對象
getDeclaredClasses() 返回一個數(shù)組流济,數(shù)組中包含該類中所有類和接口類的對象
forName(String className) 根據(jù)類名返回類的對象
getName() 獲得類的完整路徑名字
newInstance() 創(chuàng)建類的實例
getPackage() 獲得類的包
getSimpleName() 獲得類的名字
getSuperclass() 獲得當前類繼承的父類的名字
getInterfaces() 獲得當前類實現(xiàn)的類或是接口
getAnnotation(Class<T> annotationClass) 獲取類上面的注解
isAnnotationPresent(Class<T> annotationClass) 判斷是否對應的注解

例:使用forName來創(chuàng)建一個指定類的實例:

// aClass實例相當于Consumer實例
Class aClass = Class.forName("com.Test.Consumer");
Object o =aClass.newInstance();

獲得類中屬性相關的方法

方法 用途
getField(String name) 獲得某個公有的屬性對象
getFields() 獲得所有公有的屬性對象
getDeclaredField(String name) 獲得某個屬性對象
getDeclaredFields() 獲得所有屬性對象

例:獲取一個類中的私有對象:

Field field = c4.getDeclaredField("name");
String name = field.get(o);

獲得類中構造器相關的方法

方法 用途
getConstructor(Class...<?> parameterTypes) 獲得該類中與參數(shù)類型匹配的公有構造方法
getConstructors() 獲得該類的所有公有構造方法
getDeclaredConstructor(Class...<?> parameterTypes) 獲得該類中與參數(shù)類型匹配的構造方法
getDeclaredConstructors() 獲得該類所有構造方法

例:獲取一個類中的某個私有構造函數(shù):

Class[] p = {String.class};
constructors = c4.getDeclaredConstructor(p)

獲得類中方法相關的方法

方法 用途
getMethod(String name, Class...<?> parameterTypes) 獲得該類某個公有的方法
getMethods() 獲得該類所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 獲得該類某個方法
getDeclaredMethods() 獲得該類所有方法

例:獲取一個類中的某個私有方法:

Class[] p4 = {String.class};
Method method = c4.getDeclaredMethod("welcome",p4);

Field類

Field類的成員變量(成員變量也稱為類的屬性)。

方法 用途
equals(Object obj) 屬性與obj相等則返回true
get(Object obj) 獲得obj中對應的屬性值
set(Object obj, Object value) 設置obj中對應屬性值
getType() 返回屬性聲明類型的class對象
getGenericType() 返回屬性聲明類型的Type對象
getAnnotation(Class<T> annotationClass) 獲取變量\屬性上面的注解
isAnnotationPresent(Class<T> annotationClass) 判斷是否對應的注解

例:設置一個實例中的某個對象/屬性的值:

Class c1 = Class.forName("com.test.bean.Xxx");
Object o = c1.newInstance();
Field field2 = c1.getField("sex");

field2.set(o, 1);

注意腌闯,其中getType() 和 getGenericType()的區(qū)別 :

  1. 首先是返回的類型不一樣绳瘟,一個是Class對象一個是Type接口。
  2. 如果屬性是一個泛型姿骏,從getType()只能得到這個屬性的接口類型糖声。但從getGenericType()還能得到這個泛型的參數(shù)類型。
  3. getGenericType()如果當前屬性有簽名屬性類型就返回工腋,否則就返回 Field.getType()姨丈。

Constructor類

Constructor代表類的構造方法。

方法 用途
newInstance(Object... initargs) 根據(jù)傳遞的參數(shù)創(chuàng)建類的對象
getAnnotation(Class<T> annotationClass) 獲取構造函數(shù)上面的注解
isAnnotationPresent(Class<T> annotationClass) 判斷是否對應的注解

例:使用一個類的構造方法來創(chuàng)建實例:

Class c2 = Class.forName("com.test.bean.Xxx");
Class[] p = {String.class, Integer.class, Integer.class, String.class};
Constructor constructor1 = c2.getConstructor(p);

Object o1 = constructor1.newInstance("碼云", 1, 57, "資本家");

Method類

Method代表類的方法擅腰。

方法 用途
invoke(Object obj, Object... args) 傳遞object對象及參數(shù)調用該對象對應的方法
getParameterTypes() 獲取方法所有的參數(shù)類型
getReturnType() 獲取方法返回對象的類型
getParameterTypes() 返回方法中參數(shù)對象的Class類型
getGenericParameterTypes() 返回方法中參數(shù)對象的Type類型
getAnnotation(Class<T> annotationClass) 獲取方法上面的注解
isAnnotationPresent(Class<T> annotationClass) 判斷是否對應的注解

例:使用一個 類的方法類 來 調用某個類的實例中的方法:

Class c2 = Class.forName("com.test.bean.Xxx");

Method method1 = c2.getMethod("sout");
method.invoke(o1);

注意蟋恬,其中getParameterTypes()和getGenericParameterTypes()的區(qū)別:

  1. 首先是返回的類型不一樣,一個是Class對象一個是Type接口趁冈。
  2. 如果屬性是一個泛型歼争,從getParameterTypes()只能得到這個屬性的接口類型。但從getGenericParameterTypes()還能得到這個泛型的參數(shù)類型渗勘。

公共屬性和方法

Field類沐绒、Constructor類、Method類等等都具有一些公共屬性和方法:

方法 用途
setAccessible(Boolean flag) 設置可訪問性
getModifiers() 返回此對象表示的可執(zhí)行文件的 Java修飾符常量
getName() 返回該類型對象的名稱

對于私有屬性和私有構造器旺坠、私有方法乔遮,如果需要進行反射設置、修改內容必須先要設置其可訪問性取刃,否則會拋出IllegalAccessException異常

method1.setAccessible(true);

其中Class蹋肮、和Constructor都擁有一個newInstance方法,都可以用來實例化對象璧疗。但是:

Class.newInstance() 只能夠調用無參的構造函數(shù)坯辩,即默認的構造函數(shù); 如果該類的無參構造已經(jīng)被私有化則會拋出IllegalAccessException異常
Constructor.newInstance() 可以根據(jù)傳入的參數(shù)崩侠,調用任意構造構造函數(shù)漆魔。

大部分時間,雖然使用Class.newInstance方法創(chuàng)建反射實例非常方便却音,但是為了容錯性考慮改抡,推薦使用Constractor.newInstance方法。

詳解技術

上面只是簡單的了解和應用了下Java反射的能力系瓢,要進一步進行應用雀摘,就得學習一些詳細的內容。

一.泛型

1八拱、聲明一個需要被參數(shù)化(parameterizable)的類/接口。
2、使用一個參數(shù)化類肌稻。

當你聲明一個類或者接口的時候你可以指明這個類或接口可以被參數(shù)化清蚀,java.util.List接口就是典型的例子。你可以運用泛型機制創(chuàng)建一個標明存儲的是String類型list爹谭,這樣比你創(chuàng)建一個Object的list要更好枷邪,new ArrayList<String>()

你不能在運行期獲知一個被參數(shù)化的類型的具體參數(shù)類型是什么诺凡,但是你可以在用到這個被參數(shù)化類型的方法以及變量中找到他們东揣,換句話說就是獲知他們具體的參數(shù)化類型,所以看看Java-Reflection中泛型的使用問題腹泌。

方法返回的泛型類型

如果你獲得了java.lang.reflect.Method對象嘶卧,那么你就可以獲取到這個方法的返回類型信息。如果方法返回一個帶有泛型類那么你就可以獲得這個泛型類的具體參數(shù)化類型,而不是僅僅只是一個類凉袱。

比如這兒定義了一個類這個類:

  public class MyClass {

  protected List<String> stringList = ...;

  public List<String> getStringList(){
    return this.stringList;
  }
}

我們可以獲取getStringList()方法的泛型返回類型芥吟,換句話說,我們可以檢測到getStringList()方法返回的是List<String>而不僅僅只是一個List专甩。

Method method = MyClass.class.getMethod("getStringList", null);
// 返回方法返回對象類型
Type returnType = method.getGenericReturnType();
// 比較是否為參數(shù)化類型
if(returnType instanceof ParameterizedType){
    ParameterizedType type = (ParameterizedType) returnType;
    Type[] typeArguments = type.getActualTypeArguments();
    for(Type typeArgument : typeArguments){
        Class typeArgClass = (Class) typeArgument;
        System.out.println("typeArgClass = " + typeArgClass);
        // 打印結果:"typeArgClass = java.lang.String"
    }
}

其中的ParameterizedTypeType的子類钟鸵,為參數(shù)化類型,就是帶有類型參數(shù)的類型涤躲,即常說的泛型棺耍,如:List<T>、Map<Integer, String>种樱、List<? extends Number>蒙袍。

getActualTypeArguments()方法為返回表示此類型【實際類型參數(shù)】的 Type 對象的數(shù)組,也就是其中的泛型對象缸托。

方法參數(shù)中的泛型類型

當然同樣可以通過反射來獲取方法參數(shù)的泛型類型:

比如將其上面的方法修改一些:

public void setStringList(List<String> list){
    this.stringList = list;
  }

那么左敌,可以像這樣來獲取方法的參數(shù)中的泛型參數(shù):

method = Myclass.class.getMethod("setStringList", List.class);
// 返回方法參數(shù)類型
Type[] genericParameterTypes = method.getGenericParameterTypes();

for(Type genericParameterType : genericParameterTypes){
    if(genericParameterType instanceof ParameterizedType){
        ParameterizedType aType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = aType.getActualTypeArguments();
        for(Type parameterArgType : parameterArgTypes){
            Class parameterArgClass = (Class) parameterArgType;
            System.out.println("parameterArgClass = " + parameterArgClass);
        }
    }
}

其實和上方返回泛型獲取相似,只是方法變了俐镐。

類中變量泛型類型

上面都是說方法中的泛型矫限,說說類上的變量、屬性的泛型:

一個定義了一個名為stringList的成員變量的類佩抹。

public class MyClass {
  public List<String> stringList = ...;
}

獲取其中變量stringList返回的泛型類型

Field field = MyClass.class.getField("stringList");

Type genericFieldType = field.getGenericType();

if(genericFieldType instanceof ParameterizedType){
    ParameterizedType aType = (ParameterizedType) genericFieldType;
    Type[] fieldArgTypes = aType.getActualTypeArguments();
    for(Type fieldArgType : fieldArgTypes){
        Class fieldArgClass = (Class) fieldArgType;
        System.out.println("fieldArgClass = " + fieldArgClass);
    }
}

二.注解

注解是Java 5的一個新特性叼风。注解是插入你代碼中的一種注釋或者說是一種元數(shù)據(jù)(meta data)。這些注解信息可以在編譯期使用預編譯工具進行處理(pre-compiler tools)棍苹,也可以在運行期使用Java反射機制進行處理无宿。下面是一個類注解的例子:

@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

下面是MyAnnotation注解的定義:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

public @interface MyAnnotation {
  public String name();
  public String value();
}

其中簡單解析一下這個注解上面內容:

@Retention(RetentionPolicy.RUNTIME)表示這個注解存在于JAVA虛擬機運行期,可以在運行期通過反射訪問枢里。如果你沒有在注解定義的時候使用這個指示那么這個注解的信息不會保留到運行期孽鸡,那么這樣反射就無法獲取它的信息蹂午。
@Target(ElementType.TYPE) 表示這個注解只能用在類型上面(比如類跟接口)。你同樣可以把Type改為Field或者Method彬碱,或者你可以不用這個指示豆胸,這樣的話你的注解在類,方法和變量上就都可以使用了巷疼。

獲取類上的注解

通過反射可以獲取類上的注解信息:

 Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

對于方法上晚胡、構造函數(shù)上、屬性變量上的獲取反射都類似嚼沿。

獲取方法參數(shù)的注解

對于方法的參數(shù)也可以進行注解:

public class TheClass {
  public static void doSomethingElse(
        @MyAnnotation(name="aName", value="aValue") String parameter){
  }
}

通過反射可以獲取方法參數(shù)上的注解信息:

Method method = ... //獲取方法對象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
  Class parameterType = parameterTypes[i++];

  for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("param: " + parameterType.getName());
        System.out.println("name : " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
  }
}

需要注意的是Method.getParameterAnnotations()方法返回一個注解類型的二維數(shù)組估盘,每一個方法的參數(shù)包含一個注解數(shù)組。

三.數(shù)組

利用反射機制來處理數(shù)組會有點棘手骡尽。

尤其是當你想要獲得一個數(shù)組的Class對象遣妥,比如int[]等等。

其中Java-Reflection中需要通過java.lang.reflect.Array這個類來處理數(shù)值爆阶。注意不要把java.util.Arrays與其混淆燥透。java.util.Arrays是一個提供了遍歷數(shù)組,將數(shù)組轉化為集合等工具方法的類辨图。

反射創(chuàng)建/訪問數(shù)組

Java反射機制通過java.lang.reflect.Array類來創(chuàng)建數(shù)組班套。

int``[] intArray = (``int``[]) Array.newInstance(``int``.``class``, ``3``);

Array.newInstance()方法的第一個參數(shù)表示了我們要創(chuàng)建一個什么類型的數(shù)組。第二個參數(shù)表示了這個數(shù)組的空間是多大故河。

通過Java反射機制同樣可以訪問數(shù)組中的元素吱韭。具體可以使用Array.get(…)和Array.set(…)方法來訪問。

Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);

System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));

獲取數(shù)組Class對象

正確情況下鱼的,相較于普通對象來說理盆,數(shù)組的Class對象獲取起來有些特殊:

Class intArray = Class.forName(``"[I"``);

上面便是獲取int數(shù)組的Class對象。在JVM中字母I代表int類型凑阶,左邊的‘[’代表我想要的是一個int類型的數(shù)組猿规,這個規(guī)則同樣適用于其他的原生數(shù)據(jù)類型。

而對于普通對象類型的數(shù)組有一點細微的不同宙橱,比如說字符串數(shù)組:

Class stringArrayClass = Class.forName(``"[Ljava.lang.String;"``);

注意‘[L’的右邊是類名姨俩,類名的右邊是一個‘;’符號。這個的含義是一個指定類型的數(shù)組师郑。

對于普通原生數(shù)據(jù)類型环葵,你不能通過名稱直接得到:

// 直接獲取的化,這樣都會拋出ClassNotFoundException
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");

不通過反射的話你可以這樣來獲取數(shù)組的Class對象:

Class stringArrayClass = String[].``class``;

結合情況宝冕,通常會用下面這個輔助方法來獲取普通對象以及原生對象的Class對象:

public Class getClass(String className){
  if("int" .equals(className)) return int .class;
  if("long".equals(className)) return long.class;
  ...
  return Class.forName(className);
}
...
Class theClass = getClass(theClassName);
    

這是一個特別的方式來獲取指定類型的指定數(shù)組的Class對象张遭。無需使用類名或其他方式來獲取這個Class對象。

高級技術

Java的反射不僅僅只是簡單的進行類的操作和模擬地梨,一些動態(tài)技術也是屬于Java反射實現(xiàn)的菊卷。下面來進行講解更高級缔恳、復雜的技術點。

一.動態(tài)類的加載和重載

Java允許你在JVM(JAVA虛擬機)中運行期動態(tài)加載和重載類的烁,但是這個功能并沒有像人們希望的那么簡單直接褐耳。

需要一題的是:JAVA加載類ClassLoader不屬于Java反射API,而Java動態(tài)類加載特性是Java反射機制的一部分而不是Java核心平臺的一部分渴庆。

了解動態(tài)類的加載和重載,之前我們需要先了解一些相關內容:

前置內容

1.類加載器

所有Java應用中的類都是被java.lang.ClassLoader類的一系列子類加載的雅镊。

因此要想動態(tài)加載類的話也必須使用java.lang.ClassLoader的子類襟雷。

一個類一旦被加載時,這個類引用的所有類也同時會被加載仁烹。所以類加載過程是一個遞歸的模式耸弄,所有相關的類都會被加載。但并不一定是一個應用里面所有類都會被加載卓缰,與這個被加載類的引用鏈無關的類是不會被加載的计呈,直到有引用關系的時候它們才會被加載。

2.類加載順序

在Java中類的加載是一個有序的順序征唬。當你新創(chuàng)建一個標準的Java類加載器時你必須提供它的父加載器捌显。

當一個類加載器被調用來加載一個類的時候,首先會調用這個加載器的父加載器來加載总寒。如果從父加載器無法找到這個類扶歪,這時候這個加載器才會嘗試去加載這個類。所以加載類的時候是優(yōu)先從父加載器來加載摄闸,然后在考慮自己的加載器善镰。

所以類加載器 加載類的順序如下:
1、檢查這個類是否已經(jīng)被加載年枕。
2炫欺、如果沒有被加載,則首先調用父加載器加載熏兄。
3品洛、如果父加載器不能加載這個類,則嘗試加載這個類霍弹。

當然當你實現(xiàn)一個有重載類功能的類加載器毫别,它的順序與上述會有些不同。類重載不會請求的他的父加載器來進行加載典格。

動態(tài)類的加載

動態(tài)加載一個類十分簡單岛宦。你要做的就是獲取一個類加載器然后調用它的loadClass()方法。下面是個例子:

public class MainClass {
  public static void main(String[] args){
    ClassLoader classLoader = MainClass.class.getClassLoader();
    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

這里獲取了啟動類的類加載器耍缴,然后調用類加載器的loadClass方法砾肺,通過包名來動態(tài)加載了一個類挽霉。

這里大伙可能會問,“使用類加載器的loadClass方法加載類变汪,和Class類的forName方法獲取類有什么不一樣呢侠坎?不都是返回了一個對于類的Class對象嗎?”

這里說明下兩者的區(qū)別:

  • Class.forName(className)方法裙盾,內部實際調用的方法是 Class.forName(className,true,classloader);
    第2個boolean參數(shù)表示類是否需要初始化实胸, Class.forName(className)默認是需要初始化。一旦初始化番官,就會觸發(fā)目標類對象的 static塊代碼執(zhí)行庐完,static參數(shù)也也會被再次初始化。
  • ClassLoader.loadClass(className)方法徘熔,內部實際調用的方法是 ClassLoader.loadClass(className,false);
    第2個 boolean參數(shù)门躯,表示目標對象是否進行鏈接,false表示不進行鏈接酷师,不進行鏈接意味著不進行包括初始化等一些列步驟讶凉,那么靜態(tài)塊和靜態(tài)對象就不會得到執(zhí)行

所以Class.forName(className) 是初始化加載類,而ClassLoader.loadClass(className)方法只是加載類山孔,不會初始化懂讯。

動態(tài)類的重載

相比于動態(tài)類加載,動態(tài)類的重載會顯得復雜些饱须。

Java內置的類加載器在加載一個類之前會檢查它是否已經(jīng)被加載域醇,如果被加載將會直接去獲取那個類,而不是重新加載蓉媳。因此重載一個類是無法使用Java內置的類加載器的譬挚,如果想要實現(xiàn)重載一個類的話,你需要手動繼承ClassLoader定義一個自己的加載器子類酪呻。

除此之外减宣,所有被加載的類都需要被鏈接。這個過程是通過ClassLoader.resolve()方法來完成的玩荠。由于這是一個final方法漆腌,因此這個方法在ClassLoader的子類中是無法被重寫的。resolve()方法是不會允許給定的ClassLoader實例鏈接一個類兩次阶冈。所以每當你想要重載一個類的時候你都需要New一個新的ClassLoader的子類闷尿。

自定義類重載

上面說了,不能使用已經(jīng)加載過類的類加載器來重載一個類女坑。因此你需要其他的ClassLoader實例來重載這個類填具。

但是大伙或許知道,JAVA應用中的類都是使用類的全名(包名 + 類名)作為一個唯一標識來讓ClassLoader加載的,這意味著劳景,類1被類加載器A加載誉简,如果類加載器B又加載了類1,那么兩個加載器加載出來的類1其實是不同的盟广,相當于new出來的闷串。這就與重載(重新加載)的概念不同了...

所以要到達重載的效果,就需要將加載類進行繼承了一個超類并且也實現(xiàn)了一個接口筋量。

public class MyObject extends MyObjectSuperClass implements AnInterface2{
    //... body of class ... override superclass methods
    //    or implement interface methods
}

設置一個自定義加載器烹吵,對loadClass方法進行重寫,如果你想用來重載類的話你可能會設計很多加載器毛甲。并且你也不會像下面這樣將需要加載的類的路徑硬編碼(hardcore)到你的代碼中:

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();
            while(data != -1){
                buffer.write(data);
                data = input.read();
            }
            input.close();
            byte[] classData = buffer.toByteArray();
            return defineClass("reflection.MyObject",
                    classData, 0, classData.length);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

利用超類或者接口進行重載:

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject");

    AnInterface2       object1 =
            (AnInterface2) myObjectClass.newInstance();

    MyObjectSuperClass object2 =
            (MyObjectSuperClass) myObjectClass.newInstance();

    //重載類需要創(chuàng)建一個新的類加載器
    classLoader = new MyClassLoader(parentClassLoader);
    myObjectClass = classLoader.loadClass("reflection.MyObject");
        
    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();
}

二.動態(tài)代理

靜態(tài)代理簡介

在講述動態(tài)代理前遇西,我們先聊聊“靜態(tài)代理”疟丙。

靜態(tài)代理其實是Java中的一種設計模式涌矢,利用接口和實現(xiàn)類來實現(xiàn)進行擴展使用用押。

使用場景膨更,打個比方:

某個賣票場景有兩種模式厨喂,一種為線下贷笛,一種為線上靶庙,它們都有實現(xiàn)了一個售票接口叮叹,里面一種功能艾栋,“售票”。

大家都知道線上的肯定會比線下的貴(ps:不然房租不要錢的嗎蛉顽?零元購蝗砾?)

所以在其他思路下,我們或許會單獨進行重寫“售票"功能携冤,讓其價格更改悼粮。

但在靜態(tài)代理模式下,我們只需要實現(xiàn)該接口后曾棕,將起線上對象注入扣猫,然后再通過重寫中調用線上對象然后,再額外算入XXX稅等翘地。

描述所得代碼:

interface Worker {
    // 售票;
    void sell();
}
/**
** 線上
*/
class online implements Worker {
    @Override
    public void sell() {
        System.out.println("需支付300元票費");
    }
}
/**
** 線下
*/
class Offline implements Worker {

    // 私有一個被代理類的父類引用,這樣做是為了適應所有的被代理類對象,只要實現(xiàn)了接口就好;
    private Worker online;

    // 傳入被代理類對象,這里的作用是初始化"代理類"中的"被代理類"對象;
    public Offline(Worker online) {
        this.online = online;
    }

    /**
     * 增強服務和功能;
     */
    @Override
    public void sell() {
        // 代理實現(xiàn)線上服務;
        online.sell();
        // 額外服務;
        noQueue();
    }

    // 代理類本身自帶功能;
    public void noQueue() {
        System.out.println("需支付40元線下稅");
    }
}

可能大伙會覺得這樣做不是太麻煩了嗎申尤?直接重寫方法不行嗎?

但是靜態(tài)代理模式主要是運用于某些特殊情況下衙耕,如當引入第三方依賴時昧穿,無法復寫第三方Jar包的類時,又想進行實現(xiàn)擴展的話,那使用靜態(tài)代理即可實現(xiàn)第三方包Jar包功能擴展橙喘。

動態(tài)代理簡介

了解了靜態(tài)代理后时鸵,我們不能發(fā)現(xiàn)雖然靜態(tài)代理帶來擴展的優(yōu)點,但是缺點不少:

  • 可維護性低渴杆。由于代理類和被代理類都實現(xiàn)了同一個接口寥枝,如果接口發(fā)生了更改宪塔,那么被代理類和所有的代理類都要進行修改,比如接口新增一個方法囊拜,那么所有的代理類和被代理類都要重寫這個方法某筐,這無疑增加了巨大的工作量。
  • 可重用性低冠跷。通過觀察可以發(fā)現(xiàn)南誊,代理類們的代碼大體上其實是差不多的,但是由于個別的差異蜜托,導致我們不得不重新寫一個新的代理類抄囚。

那么我們開始動態(tài)代理的講解。

動態(tài)代理 即 利用Java反射機制在運行期動態(tài)的創(chuàng)建接口的實現(xiàn)類的行為橄务。

創(chuàng)建個代理類來代替實際需要的類幔托,利用這個代理來實現(xiàn)原類的功能,這一行為就叫做代理蜂挪。而能夠在其Java編譯后在JVM(Java虛擬機)中運行時動態(tài)實現(xiàn)代理的重挑,被稱為動態(tài)代理。

其中 動態(tài)代理 的用途十分廣泛棠涮,比如Spring AOP谬哀、Hibernate數(shù)據(jù)查詢、測試框架的后端mock严肪、RPC遠程調用史煎、Java注解對象獲取、日志驳糯、用戶鑒權篇梭、全局性異常處理、性能監(jiān)控结窘,甚至事務處理等都使用到了動態(tài)代理很洋。

在這之前,我們得了解InvocationHandler-調用處理程序 和 Proxy類-代理類

InvocationHandler

InvocationHandler是一個接口類隧枫,里面只有一個方法喉磁,invoke,專門用來實現(xiàn)代理類的功能官脓。

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

它接受三個參數(shù):代理類协怒,對象真實方法,傳遞參數(shù)卑笨。這個方法的效果和Method中的invoke類似孕暇。返回的Object即方法返回內容。

其中第一個參數(shù),大部分時間動態(tài)代理不會使用妖滔。

所以要實現(xiàn)一個動態(tài)代理隧哮,就需要創(chuàng)建一個動態(tài)代理類的調用處理程序。而每一個動態(tài)代理類的調用處理程序都必須實現(xiàn)這個InvocationHandler接口座舍,并且每個代理類的實例都關聯(lián)到了實現(xiàn)該接口的 動態(tài)代理類調用處理程序 中沮翔。

當我們通過動態(tài)代理對象調用一個方法時候,這個方法的調用就會被轉發(fā)到實現(xiàn)InvocationHandler接口類的invoke方法來調用曲秉,從而實現(xiàn)動態(tài)代理類采蚀。

創(chuàng)建一個自定義動態(tài)代理的調用處理程序類,例如:

public class WorkHandler implements InvocationHandler{
    // 需要代理的類
    private Object obj;
    public WorkHandler() {
        // TODO Auto-generated constructor stub
    }
    // 構造函數(shù)
    public WorkHandler(Object obj) {
        this.obj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在真實的對象執(zhí)行之前我們可以添加自己的操作
        System.out.println("代理處理開始承二。榆鼠。。");
        Object invoke = method.invoke(obj, args);
        //在真實的對象執(zhí)行之后我們可以添加自己的操作
        System.out.println("方法代理完畢!");
        System.out.println("代理處理結束亥鸠。妆够。。");
        return invoke;
    }
}

創(chuàng)建代理

創(chuàng)建代理前负蚊,了解下Proxy這個類责静。

Proxy指的是java.lang.reflect.Proxy,顧名思義盖桥,就是專門用作代理的。

它擁有很多方法题翻,但其中我們最常用的是它的newProxyInstance方法揩徊。

你可以通過使用Proxy.newProxyInstance()方法創(chuàng)建動態(tài)代理。其中newProxyInstance()方法有三個參數(shù):

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

1嵌赠、loader:類加載器(ClassLoader)塑荒,用來加載動態(tài)代理類。
2姜挺、interfaces:需要代理的類 的實現(xiàn)的接口齿税。
3、h:一個InvocationHandler對象炊豪,將其代理方法分派到的調用處理程序凌箕。

如下例子:

public interface Subject {
    public String SayHello(String name);
}
public class RealSubject implements Subject{
    @Override
    public String SayHello(String name) {
        return "hello " + name;
    }

    @Override
    public String toString() {
        return "RealSubject{}";
    }
}

我們創(chuàng)建一個RealSubject的動態(tài)代理:

public class ProxyMain {
    public static void main(String[] args) throws Exception {
        Subject real =new RealSubject();
        ClassLoader loader = ProxyMain.class.getClassLoader();

        // 創(chuàng)建一個InvocationHandler實例,用作處理代理邏輯
        InvocationHandler handler = new WorkHandler(real);
        // 創(chuàng)建代理
        Subject realSubjectProxy = (Subject)Proxy.newProxyInstance(loader, new Class[]{Subject.class}, handler);
    }
}

在執(zhí)行完這段代碼之后词渤,變量RealSubjectProxy包含一個MyInterface接口的的動態(tài)實現(xiàn)牵舱。所有對RealSubjectProxy的調用都被轉向到實現(xiàn)了InvocationHandler接口的handler上執(zhí)行。

我們利用這個動態(tài)代理類RealSubjectProxy調用一下RealSubject的方法試試:

System.out.println(realSubjectProxy.SayHello("我是一個代理類"));

結果輸出:

代理處理開始缺虐。芜壁。。
方法代理完畢!
代理處理結束。慧妄。顷牌。
hello 我是一個代理類

所以由此可見,在調用代理類使用方法后塞淹,其調用將自動轉到InvocationHandler實現(xiàn)類中的invoke方法中進行操作窟蓝。

同時我們使用getClass().getName()獲取一下這個動態(tài)代理對象的類名:

System.out.println(realSubjectProxy.getClass().getName());

結果輸出:

com.sun.proxy.$Proxy0

說明動態(tài)代理類對象依舊屬于Proxy類,不屬于原類窖铡。所以動態(tài)代理不會利用到原類進行操作疗锐,是完完全全以代理存在。

注意:Proxy.newProxyInstance得到的動態(tài)代理類只能強轉為 代理的類的接口 费彼,不能強轉為 代理的類滑臊。因為JDK自帶的動態(tài)代理必須實現(xiàn)接口,這也是JDK動態(tài)代理的缺點之一箍铲。

精簡創(chuàng)建代理步驟

在了解了動態(tài)代理創(chuàng)建后雇卷,創(chuàng)建Proxy還是有點麻煩,所以我們可以精簡下步驟:

修改自定義的InvocationHandler實例颠猴,新增一個bind方法关划,用作直接生成綁定后的動態(tài)代理類:

/**
 * @description: TODO
 * @author: Zhaotianyi
 * @time: 2021/10/19 14:44
 */
public class WorkHandler implements InvocationHandler {
    // 需要代理的類
    private Object object;

    /**
     * 生成綁定后的動態(tài)代理類
     *
     * @param object 需要代理的類
     * @return Object 動態(tài)代理類
     */
    public Object bind(Object object) {
        this.object = object;
        return Proxy.newProxyInstance(this.object.getClass().getClassLoader(),
                this.object.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnObject = null;
        ...
        returnObject = method.invoke(object, args);
        ...
        return returnObject;
    }
}

在需要動態(tài)代理時,我們只需要創(chuàng)建WorkHandler并調用bind方法即可快速創(chuàng)建一個動態(tài)代理類翘瓮。

更多擴展選擇

JAVA自帶的反射java.lang.reflect包實現(xiàn)的動態(tài)代理贮折,其目標類必須實現(xiàn)的某個接口,如果某個類沒有實現(xiàn)接口則不能生成代理對象资盅。所以這樣的動態(tài)代理功能是比較單一的调榄,目前第三方開源的CGLIB的代理則更為強大,被廣泛應用于大型第三方框架中呵扛,如SpringAOP每庆、Mybatis等。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末今穿,一起剝皮案震驚了整個濱河市缤灵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓝晒,老刑警劉巖腮出,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拔创,居然都是意外死亡利诺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門剩燥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慢逾,“玉大人立倍,你說我怎么就攤上這事÷绿玻” “怎么了口注?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長君珠。 經(jīng)常有香客問我寝志,道長,這世上最難降的妖魔是什么策添? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任材部,我火速辦了婚禮,結果婚禮上唯竹,老公的妹妹穿的比我還像新娘乐导。我一直安慰自己,他們只是感情好浸颓,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布物臂。 她就那樣靜靜地躺著,像睡著了一般产上。 火紅的嫁衣襯著肌膚如雪棵磷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天晋涣,我揣著相機與錄音仪媒,去河邊找鬼。 笑死谢鹊,一個胖子當著我的面吹牛规丽,可吹牛的內容都是我干的。 我是一名探鬼主播撇贺,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冰抢!你這毒婦竟也來了松嘶?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤挎扰,失蹤者是張志新(化名)和其女友劉穎翠订,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遵倦,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡尽超,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梧躺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片似谁。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡傲绣,死狀恐怖,靈堂內的尸體忽然破棺而出巩踏,到底是詐尸還是另有隱情秃诵,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布塞琼,位于F島的核電站菠净,受9級特大地震影響,放射性物質發(fā)生泄漏彪杉。R本人自食惡果不足惜毅往,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望派近。 院中可真熱鬧攀唯,春花似錦、人聲如沸构哺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曙强。三九已至残拐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碟嘴,已是汗流浹背溪食。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娜扇,地道東北人错沃。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像雀瓢,于是被迫代替她去往敵國和親枢析。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內容