本文基於 JDK8,Oracle官網對反射的解釋是
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
反射使 Java 代碼可以發現有關已加載類的字段,方法和構造函數的信息,並在安全性限制內使用反射對這些字段,方法和構造函數進行操作。
簡而言之,指在 Java 程序運行時
-
給定的一個類(Class)對象,通過反射獲取這個類(Class)對象的所有成員結構。
-
給定的一個具體的對象,能夠動態地調用它的方法及對任意屬性值進行獲取和賦值。
這種動態獲取類的內容,創建對象、以及動態調用對象的方法及操作屬性的機制為反射。即使該對象的類型在編譯期間是未知,該類的 .class 文件不存在,也可以通過反射直接創建對象。
優勢
-
增加程序的靈活性,避免將固有的邏輯程序寫死到代碼里
-
代碼簡潔,可讀性強,可提高代碼的復用率
劣勢
-
相較直接調用,在量大的情景下反射性能下降
-
存在一些內部暴露和安全隱患
二、反射的運用
反射的源 java.lang.Class,Class 類是 JDK 對我們自定義的類和內置類的統一描述,Class 類中存儲了類運行時的類型信息。在 Class 類,可以獲取如下圖所示類的公共信息。Class 類與反射的聯系密切相關,Class 類和 java.lang.reflect 一起對反射提供了支持。
定義兩個類 Boy 和 Person,Person 作為 Boy 的父類,作為接下演示這個部分反射用法的類。
Person.class
public class Person { public String name; private int age; public void talk() { System.out.println(name + "is talking"); } }
Boy.class
public class Boy extends Person { public int height; private int weight; public static String description; public Boy() {} private Boy(int height) { this.height = height; } public Boy(int height, int weight) { this.height = height; this.weight = weight; } public static void playBasketball() { System.out.println("play basketball!"); } public static void playBall(String ballType) { System.out.println("play " + ballType + "!"); } private void pickUpGirl() { System.out.println("pick up girl!"); } public int getWeight() { return weight; } public int getHeight() { return height; } }
2.1 獲取 Class 對象實例的四種方法
Class<Boy> clazz = Boy.class; // 通過類的 class 屬性 Class<?> clazz2 = new Boy().getClass(); // 通過運行時類對象的 getClass 方法獲取 Class<?> clazz3 = Class.forName("com.hncboy.corejava.reflection.Boy"); // 通過類的全限定名獲取 Class<?> clazz4 = Main.class.getClassLoader().loadClass("com.hncboy.corejava.reflection.Boy"); // 通過類加載器獲取
2.2 反射獲取類的基本信息
一個類的基本信息包含了修飾符,class 關鍵字,類名,父類,接口等信息,常用方法如下:
int modifier = clazz.getModifiers(); // 獲取類的修飾符 Package pack = clazz.getPackage(); // 獲取類的包名 String fullClassName = clazz.getName(); // 獲取類的全路徑名稱 String simpleClassName = clazz.getSimpleName(); // 獲取類的簡單名稱 ClassLoader classLoader = clazz.getClassLoader(); // 獲取類的類加載器 Class<?>[] interfacesClasses = clazz.getInterfaces(); // 獲取類實現的接口列表 Class<?> superClass = clazz.getSuperclass(); // 獲取類的父類 Annotation[] annotations = clazz.getAnnotations(); // 獲取類的注解列表
通過一個測試類,測試以上方法:
public class Test { public static void main(String[] args) { Class<Boy> clazz = Boy.class; // 獲取類的修飾符,如果有多個修飾符,取相加后的結果 int modifiers = clazz.getModifiers(); System.out.println("modifiers: " + modifiers); System.out.println("modifiers toString: " + Modifier.toString(modifiers)); // 獲取類的包名 Package pack = clazz.getPackage(); System.out.println("package: " + pack); // 獲取類的全路徑名稱:包名 + 類名 String fullClassName = clazz.getName(); System.out.println("fullClassName: " + fullClassName); // 獲取類的簡單名稱:只有類名 String simpleClassName = clazz.getSimpleName(); System.out.println("simpleClassName: " + simpleClassName); // 獲取類的類加載器 ClassLoader classLoader = clazz.getClassLoader(); System.out.println("classLoader: " + classLoader); // 獲取類實現的接口列表 Class<?>[] interfacesClasses = clazz.getInterfaces(); System.out.println("interfacesClasses: " + Arrays.toString(interfacesClasses)); // 獲取類的父類 Class<?> superClass = clazz.getSuperclass(); System.out.println("superClass: " + superClass); // 獲取類的注解列表 Annotation[] annotations = clazz.getAnnotations(); System.out.println("annotations: " + Arrays.toString(annotations)); } }
運行輸出結果如下所示:
modifiers: 1 modifiers toString: public package: package com.hncboy.corejava.reflection fullClassName: com.hncboy.corejava.reflection.Boy simpleClassName: Boy classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2 interfacesClasses: [] superClass: class com.hncboy.corejava.reflection.Person annotations: []
其中 getModifiers 以整數形式編碼返回此類或接口的 Java 語言修飾符。通過查 java.lang.reflect.Modifier 的 API 可知,返回結果有如下類型,所有修飾符的值都為二進制運算的結果,通過位運算判斷修飾符是最快的。
2.2 反射對字段的操作
字段的信息存儲在 Field 類中, Field 提供有關類或接口的單個字段的信息,以及對它們的動態訪問,並且可以對變量進行修改。反射字段可以是類(靜態)字段或實例字段。常用方法如下:
Field[] fields = clazz.getFields(); // 獲取類中所有的公有字段 Field[] declaredFields = clazz.getDeclaredFields(); // 獲取類中定義的字段 Field nameField = clazz.getField("name"); // 獲取指定名稱的公有字段 Field declaredField = clazz.getDeclaredField("likeDesc"); // 獲取指定名稱類中定義的字段 int modifiersField = likeDescField.getModifiers(); // 獲取字段的修飾 nameField.setAccessible(true); // 指定字段強制訪問 nameField.set(person, "hncboy"); // 成員字段賦值(需指定對象) descriptionField.set(null, "hncboy"); // 靜態字段賦值
通過一個測試類,測試以上方法:
public class Test { public static void main(String[] args) throws Exception { Class<Boy> clazz = Boy.class; // 獲取類中所有的公有字段,包含繼承的 Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println(field); } // 獲取指定名稱的公有字段,包含繼承的 Field nameField = clazz.getField("name"); System.out.println(nameField); // 獲取本類中定義的所有字段,不包含繼承的,包含私有的 Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { System.out.println(field); } // 獲取本類中指定名稱類中定義的字段 Field weightField = clazz.getDeclaredField("weight"); System.out.println(weightField.getModifiers()); // 給指定字段賦值(需指定對象) Boy boy = clazz.newInstance(); // 將該字段設置為強制訪問 weightField.setAccessible(true); weightField.set(boy, 120); System.out.println(boy.getWeight()); // 靜態字段賦值,靜態字段不需要指定對象 Field descField = clazz.getField("description"); descField.set(null, "靜態屬性"); System.out.println(Boy.description); } }
運行輸出結果如下所示:
public int com.hncboy.corejava.reflection.Boy.height public static java.lang.String com.hncboy.corejava.reflection.Boy.description public java.lang.String com.hncboy.corejava.reflection.Person.name public java.lang.String com.hncboy.corejava.reflection.Person.name public int com.hncboy.corejava.reflection.Boy.height private int com.hncboy.corejava.reflection.Boy.weight public static java.lang.String com.hncboy.corejava.reflection.Boy.description 2 120 靜態屬性
在直接訪問私有 private 變量 weight 時,會報如下的錯誤,不能訪問 Boy 類的私有變量。通過 Field 繼承的 java.lang.reflect.AccessibleObject 類中的 setAccessible(boolean flag) 可以開啟權限,setAccessible 方法通過調用 native setAccessible0 方法取消 Java 語言訪問檢查權限。
Exception in thread "main" java.lang.IllegalAccessException: Class com.hncboy.corejava.reflection.Test can not access a member of class com.hncboy.corejava.reflection.Boy with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Field.set(Field.java:761) at com.hncboy.corejava.reflection.Test.main(Test.java:41)
2.3 反射對方法的操作
方法的信息存儲在 Method 類中, Method 提供有關類或接口上單個方法的信息,以及對單個方法的訪問。反射方法可以是類方法或實例方法(包括抽象方法)。常用方法如下:
Method[] methods = clazz.getMethods(); // 獲取類中所有的公有方法 Method[] declaredMethods = clazz.getDeclaredMethods(); // 獲取本類的所有方法 Method talkMethod = clazz.getMethod("talk", String.class); // 獲取類中指定名稱和參數的公有方法 Method pugMethod = clazz.getDeclaredMethod("pickUpGirls"); // 獲取本類中定義的指定名稱和參數的方法 int modifiers = pugMethod.getModifiers(); // 獲取方法的修飾符 talkMethod.invoke(boy, "I tell you"); // 指定對象進行成員方法的調用 pugMethod.setAccessible(true); // 指定方法的強制訪問 pickUpGirlsMethod.invoke(null); // 指定靜態方法的調用
通過一個測試類,測試以上方法:
public class Test { public static void main(String[] args) throws Exception { Class<Boy> clazz = Boy.class; // 獲取類中定義的方法,包含繼承的(Object) Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } // 獲取類中指定的方法(無參) Method talkMethod = clazz.getMethod("talk"); System.out.println(talkMethod.getName()); // 獲取類中指定的方法(有參) Method playMethod = clazz.getMethod("playBall", String.class); System.out.println(playMethod.getName()); // 獲取本類中的所有方法,不包含繼承,包含私有的 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method); } // 獲取本類中特定的的方法 Method declaredMethod = clazz.getDeclaredMethod("pickUpGirl"); System.out.println(declaredMethod.getName()); // 底層是基於構造器的創建無參構造器 Boy boy = clazz.newInstance(); // 調用公有有參方法 playMethod.invoke(boy, "足球"); // 調用私有無參方法,需要設置強制訪問 declaredMethod.setAccessible(true); declaredMethod.invoke(boy); // 調用靜態方法 Method playBasketBallMethod = clazz.getDeclaredMethod("playBasketball"); playBasketBallMethod.invoke(null); } }
運行輸出結果如下所示:
public static void com.hncboy.corejava.reflection.Boy.playBasketball() public int com.hncboy.corejava.reflection.Boy.getWeight() public int com.hncboy.corejava.reflection.Boy.getHeight() public static void com.hncboy.corejava.reflection.Boy.playBall(java.lang.String) public void com.hncboy.corejava.reflection.Person.talk() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() talk playBall private void com.hncboy.corejava.reflection.Boy.pickUpGirl() public static void com.hncboy.corejava.reflection.Boy.playBasketball() public int com.hncboy.corejava.reflection.Boy.getWeight() public int com.hncboy.corejava.reflection.Boy.getHeight() public static void com.hncboy.corejava.reflection.Boy.playBall(java.lang.String) pickUpGirl play 足球! pick up girl! play basketball!
在通過 getMethods 獲取所有父類的公有方法,Boy 類的父類包含 Person 類和 Object 類,所以總共輸出 14 個公有方法。getMethod 或 getDeclaredMethod 方法獲取指定方法名無參的方法時,參數可以省略,直接傳入方法名,獲取帶參數的方法時,如果類型錯誤會報 NoSuchMethodException 異常,如下所示。通過 method 的 invoke 方法傳入實例對象調用實例方法,調用靜態方法傳入 null 即可。
Exception in thread "main" java.lang.NoSuchMethodException: com.hncboy.corejava.reflection.Boy.playBall(int) at java.lang.Class.getMethod(Class.java:1786) at com.hncboy.corejava.reflection.Test.main(Test.java:29)
2.4 反射對構造器的操作
構造器的信息存儲在 Constructor 類中, Constructor 提供有關類的單個構造函數的信息,以及對類的訪問。常用方法如下:
Constructor<?>[] constructors = clazz.getConstructors(); // 獲取類中所有的公有構造器 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 獲取類中所有的構造器 Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(); // 獲取類中無參的構造器 Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, String.class); // 獲取類中有參構造器 int modifiers = constructor.getModifiers(); // 獲取構造器的修飾符 declaredConstructor.newInstance(); // 構造器實例對象 declaredConstructor.setAccessible(true); // 指定構造器的強制訪問 constructor.newInstance("hncboy"); // 有參構造調用 clazz.newInstance(); // 直接調用默認無參構造
通過一個測試類,測試以上方法:
public class Test { public static void main(String[] args) throws Exception { Class<Boy> clazz = Boy.class; // 獲取類中所有的公有構造器 Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor); } // 獲取類中所有的構造器,包含私有的 Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : declaredConstructors) { System.out.println(constructor); } // 獲取類中無參的構造器 Constructor<?> noParamsConstructor = clazz.getDeclaredConstructor(); System.out.println(noParamsConstructor); // 獲取類中指定參數構造器 Constructor<?> constructor1 = clazz.getDeclaredConstructor(int.class); Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, int.class); System.out.println(noParamsConstructor.getModifiers()); System.out.println(constructor1.getModifiers()); System.out.println(constructor2.getModifiers()); // 調用構造器 Boy boy = (Boy) noParamsConstructor.newInstance(); System.out.println(boy); constructor1.setAccessible(true); boy = (Boy) constructor1.newInstance(177); System.out.println(boy.getHeight()); } }
運行輸出結果如下所示:
public com.hncboy.corejava.reflection.Boy(int,int) public com.hncboy.corejava.reflection.Boy() public com.hncboy.corejava.reflection.Boy(int,int) private com.hncboy.corejava.reflection.Boy(int) public com.hncboy.corejava.reflection.Boy() public com.hncboy.corejava.reflection.Boy() 1 2 1 com.hncboy.corejava.reflection.Boy@4b67cf4d 177
getConstructors 方法獲取類中的所有公有構造器,構造器不能繼承,所有只能獲取本類中的。getDeclaredConstructors 獲取本類的所有構造器,包含私有的。在獲取特定參數構造器時,傳入的要與構造器的參數一樣,如 int.class 不能寫成 Integer.class,因為自動拆箱是在編譯過程中的,而反射是在運行期間的。
通過反射,可使用 Class.newInstance() 或 Constructor.newInstance() 兩種方式創建對象。Class 類下的 newInstance 是弱類型,只能調用無參的構造方法,如果沒有默認構造方法,會拋出 InstantiationException 實例化異常,通過源碼可知,該方法的本質上是 return tmpConstructor.newInstance((Object[])null); ,也是調用 Constructor 的 newInstance 方法 。 而 Constructor 類下的 newInstance 可以調用任意參數的構造器。
三、反射破壞單例
單例模式
-
私有化構造函數
-
全局唯一的公有訪問點
-
對外提供獲取實例的靜態方法
3.1 定義餓漢式 Hungry
public class Hungry { private static final Hungry INSTANCE = new Hungry(); private Hungry() {} public static Hungry getInstance() { return INSTANCE; } }
3.2 定義懶漢式 Lazy
public class Lazy { private static Lazy instance; private Lazy() {} public static Lazy getInstance() { if (instance == null) { synchronized (Lazy.class) { if (instance == null) { instance = new Lazy(); } } } return instance; } }
3.3 定義 SingletonDestroyer
public class SingletonDestroyer { public static void main(String[] args) throws Exception { // 破壞懶漢模式 Lazy lazyInstance = Lazy.getInstance(); Constructor<Lazy> lazyConstructor = Lazy.class.getDeclaredConstructor(); lazyConstructor.setAccessible(true); Lazy lazyInstanceReflect = lazyConstructor.newInstance(); System.out.println(lazyInstance); System.out.println(lazyInstanceReflect); // 破壞餓漢模式 Hungry hungryInstance = Hungry.getInstance(); Constructor<Hungry> hungryConstructor = Hungry.class.getDeclaredConstructor(); hungryConstructor.setAccessible(true); Hungry hungryInstanceReflect = hungryConstructor.newInstance(); System.out.println(hungryInstance); System.out.println(hungryInstanceReflect); } }
運行結果如下,通過反射機制可以破環單例模式,將私有化的構造器通過強制訪問創建對象。
com.hncboy.corejava.reflection.Lazy@4b67cf4d
com.hncboy.corejava.reflection.Lazy@7ea987ac
com.hncboy.corejava.reflection.Hungry@12a3a380
com.hncboy.corejava.reflection.Hungry@29453f44
四、反射實現簡單 Spring IOC Bean 實例創建
IOC(Inversion of Control) 控制反轉,他是一種設計思想,並非實際的技術,最核心的思想就是將預先設計的對象實例創建的控制權交給程序(IOC 容器)。 IOC 容器本質上是一個 K-V 結構的 Map。IOC 的實現原理就是工廠模式加反射機制。
通過 Spring 文檔可查看 Bean 實例的三種創建方式:
-
Instantiation with a Constructor 通過構造器實例化
-
Instantiation by Using an Instance Factory Method 通過靜態工廠實例化
-
Instantiation by Using an Instance Factory Method 通過實例工廠實例化
步驟如下:
4.1 添加三個類 A,B,C
public class A { public A() { System.out.println("調用 A 的無參構造器"); } public static B createBInstance() { System.out.println("調用 A 的靜態方法 createBInstance"); return new B(); } public C createCInstance() { System.out.println("調用 A 的實例方法 createCInstance"); return new C(); } } class B {} class C {}
4.2 添加 spring-ioc.xml
通過模擬該配置文件來進行對象的創建
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 創建方式1:無參構造器創建 A 對象--> <bean id="a" class="com.hncboy.corejava.reflection.A"/> <!-- 創建方式2:靜態工廠創建,調用 A 的 createBObj 方法來創建名為 b 的對象放入容器 --> <bean id="b" class="com.hncboy.corejava.reflection.A" factory-method="createBInstance"/> <!-- 創建方式3:實例工廠創建,調用實例 a 的 createBObj 方法來創建名為 c 的對象放入容器 --> <bean id="c" factory-bean="a" factory-method="createCInstance"/> </beans>
4.3 添加 BeanConfig
/** * 存放 bean 的基本信息 */ public class BeanConfig { private String id; private String clazz; private String factoryMethod; private String factoryBean; /* getter、setter 省略 */ }
4.4 添加 IOCContainer
/** * 定義 map 存放 map */ public class IOCContainer { private static Map<String, Object> container = new HashMap<>(); public static void putBean(String id, Object object) { container.put(id, object); } public static Object getBean(String id) { return container.get(id); } }
4.5 添加 Init
創建方式1:無參構造器創建。bean 的內容包括 id 和 clazz
創建方式2:靜態工廠創建。bean 的內容包括 id 和 factory-method
創建方式3:實例工廠創建。bean 的內容包括 id,factory-bean 和 factory-method
public class Init { public static void main(String[] args) throws Exception { List<BeanConfig> beanConfigs = parseXmlToBeanConfig(); // 將解析的 BeanConfig 進行實例化 for (BeanConfig beanConfig : beanConfigs) { if (beanConfig.getClazz() != null) { Class<?> clazz = Class.forName(beanConfig.getClazz()); if (beanConfig.getFactoryMethod() != null) { // 創建方式2:靜態工廠創建 Method method = clazz.getDeclaredMethod(beanConfig.getFactoryMethod()); IOCContainer.putBean(beanConfig.getId(), method.invoke(null)); } else { // 創建方式1:無參構造器創建 IOCContainer.putBean(beanConfig.getId(), clazz.newInstance()); } } else if (beanConfig.getId() != null) { // 創建方式3:實例工廠創建 Object object = IOCContainer.getBean(beanConfig.getFactoryBean()); Method method = object.getClass().getDeclaredMethod(beanConfig.getFactoryMethod()); IOCContainer.putBean(beanConfig.getId(), method.invoke(object)); } else { System.out.println("缺少配置,無法創建對象!"); } } } /** * 模擬解析 XML 中的 bean * * @return */ private static List<BeanConfig> parseXmlToBeanConfig() { List<BeanConfig> beanConfigs = new ArrayList<>(); // 模擬無參構造器創建對象 BeanConfig beanConfig1 = new BeanConfig(); beanConfig1.setId("a"); beanConfig1.setClazz("com.hncboy.corejava.reflection.A"); beanConfigs.add(beanConfig1); // 模擬靜態工廠創建對象 BeanConfig beanConfig2 = new BeanConfig(); beanConfig2.setId("b"); beanConfig2.setClazz("com.hncboy.corejava.reflection.A"); beanConfig2.setFactoryMethod("createBInstance"); beanConfigs.add(beanConfig2); // 模擬實例工廠創建對象 BeanConfig beanConfig3 = new BeanConfig(); beanConfig3.setId("c"); beanConfig3.setFactoryBean("a"); beanConfig3.setFactoryMethod("createCInstance"); beanConfigs.add(beanConfig3); return beanConfigs; } }
運行結果如下:
調用 A 的無參構造器
調用 A 的靜態方法 createBInstance
調用 A 的實例方法 createCInstance