淺談 Java 反射技術(shù)
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲Tang 。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
一、什么是反射?
反射 (Reflection) 是 Java 的特征之一,它允許運(yùn)行中的 Java 程序獲取自身的信息,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。
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.
簡而言之,通過反射,我們可以在運(yùn)行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機(jī)制可以動態(tài)地創(chuàng)建對象并調(diào)用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機(jī)制直接創(chuàng)建對象,即使這個對象的類型在編譯期是未知的。
二、反射的主要用途
很多人都認(rèn)為反射在實(shí)際的 Java 開發(fā)應(yīng)用中并不廣泛,其實(shí)不然。當(dāng)我們在使用 IDE(如 Eclipse,IDEA)時,當(dāng)我們輸入一個對象或類并想調(diào)用它的屬性或方法時,一按點(diǎn)號,編譯器就會自動列出它的屬性或方法,這里就會用到反射。
反射最重要的用途就是開發(fā)各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean),為了保證框架的通用性,它們可能需要根據(jù)配置文件加載不同的對象或類,調(diào)用不同的方法,這個時候就必須用到反射,運(yùn)行時動態(tài)加載需要加載的對象。
舉一個例子,在運(yùn)用 Struts 2 框架的開發(fā)中我們一般會在 struts.xml 里去配置 Action,比如:
- <action name="login"
- class="org.xxx.SimpleLoginAction"
- method="execute">
- <result>/shop/shop-index.jsp</result>
- <result name="error">login.jsp</result>
- </action>
配置文件與Action建立了一種映射關(guān)系,當(dāng) View層發(fā)出請求時,請求會被 StrutsPrepareAndExecuteFilter 攔截,然后 StrutsPrepareAndExecuteFilter 會去動態(tài)地創(chuàng)建 Action 實(shí)例。比如我們請求 login.action,那么 StrutsPrepareAndExecuteFilter就會去解析struts.xml文件,檢索action中name為login的Action,并根據(jù)class屬性創(chuàng)建SimpleLoginAction實(shí)例,并用invoke方法來調(diào)用execute方法,這個過程離不開反射。
對與框架開發(fā)人員來說,反射雖小但作用非常大,它是各種容器實(shí)現(xiàn)的核心。而對于一般的開發(fā)者來說,不深入框架開發(fā)則用反射用的就會少一點(diǎn),不過了解一下框架的底層機(jī)制有助于豐富自己的編程思想,也是很有益的。
三、反射的基本運(yùn)用
3.1、通過反射獲取class對象
通過反射獲取對象有三種方式
3.1.1、Class.forName()獲取
- public static Class<?> forName(String className)
- throws ClassNotFoundException {
- Class<?> caller = Reflection.getCallerClass();
- return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
- }
比如,在 JDBC 開發(fā)中常用此方法加載數(shù)據(jù)庫驅(qū)動
- Class.forName("包名.類名");
3.1.2、類名.class獲取
例如:
- Class<?> intClass = int.class;
- Class<?> integerClass = Integer.TYPE;
- #RelfectEntity類為本文的例子
- Class relfectEntity2 = RelfectEntity.class;
3.1.3、對象getClass()獲取
- StringBuilder str = new StringBuilder("123");
- Class strClass = str.getClass();
三種方法都可以實(shí)現(xiàn)獲取class對象,框架開發(fā)中,一般第一種用的比較多。
3.2、獲取類的構(gòu)造器信息
獲取類構(gòu)造器的用法,主要是通過Class類的getConstructor方法得到Constructor類的一個實(shí)例,而Constructor類有一個newInstance方法可以創(chuàng)建一個對象實(shí)例:
- public T newInstance(Object ... initargs)
3.3、獲取類的實(shí)例
通過反射來生成對象主要有兩種方式。
使用Class對象的newInstance()方法來創(chuàng)建Class對象對應(yīng)類的實(shí)例。
- Class c = String.class;
- Object str = c.newInstance();
先通過Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建實(shí)例。
- //獲取String所對應(yīng)的Class對象
- Class<?> c = String.class;
- //獲取String類帶一個String參數(shù)的構(gòu)造器
- Constructor constructor = c.getConstructor(String.class);
- //根據(jù)構(gòu)造器創(chuàng)建實(shí)例
- Object obj = constructor.newInstance("23333");
- System.out.println(obj);
這種方法可以用指定的構(gòu)造器構(gòu)造類的實(shí)例。
3.4、獲取類的變量
實(shí)體類:
- /**
- * 基類
- */
- public class BaseClass {
- public String publicBaseVar1;
- public String publicBaseVar2;
- }
- /**
- * 子類
- */
- public class ChildClass extends BaseClass{
- public String publicOneVar1;
- public String publicOneVar2;
- private String privateOneVar1;
- private String privateOneVar2;
- public String printOneMsg() {
- return privateOneVar1;
- }
- }
測試:
- public class VarTest {
- public static void main(String[] args) {
- //1.獲取并輸出類的名稱
- Class mClass = ChildClass.class;
- System.out.println("類的名稱:" + mClass.getName());
- System.out.println("----獲取所有 public 訪問權(quán)限的變量(包括本類聲明的和從父類繼承的)----");
- //2.獲取所有 public 訪問權(quán)限的變量(包括本類聲明的和從父類繼承的)
- Field[] fields = mClass.getFields();
- //遍歷變量并輸出變量信息
- for (Field field : fields) {
- //獲取訪問權(quán)限并輸出
- int modifiers = field.getModifiers();
- System.out.print(Modifier.toString(modifiers) + " ");
- //輸出變量的類型及變量名
- System.out.println(field.getType().getName() + " " + field.getName());
- }
- System.out.println("----獲取所有本類聲明的變量----");
- //3.獲取所有本類聲明的變量
- Field[] allFields = mClass.getDeclaredFields();
- for (Field field : allFields) {
- //獲取訪問權(quán)限并輸出
- int modifiers = field.getModifiers();
- System.out.print(Modifier.toString(modifiers) + " ");
- //輸出變量的類型及變量名
- System.out.println(field.getType().getName() + " " + field.getName());
- }
- }
- }
輸出結(jié)果:
- 類的名稱:com.example.java.reflect.ChildClass
- ----獲取所有 public 訪問權(quán)限的變量(包括本類聲明的和從父類繼承的)----
- public java.lang.String publicOneVar1
- public java.lang.String publicOneVar2
- public java.lang.String publicBaseVar1
- public java.lang.String publicBaseVar2
- ----獲取所有本類聲明的變量----
- public java.lang.String publicOneVar1
- public java.lang.String publicOneVar2
- private java.lang.String privateOneVar1
- private java.lang.String privateOneVar2
3.5、修改類的變量
修改子類
- /**
- * 子類
- */
- public class ChildClass extends BaseClass{
- public String publicOneVar1;
- public String publicOneVar2;
- private String privateOneVar1;
- private String privateOneVar2;
- public String printOneMsg() {
- return privateOneVar1;
- }
- }
測試:
- public class VarModfiyTest {
- public static void main(String[] args) throws Exception {
- //1.獲取并輸出類的名稱
- Class mClass = ChildClass.class;
- System.out.println("類的名稱:" + mClass.getName());
- System.out.println("----獲取ChildClass類中的privateOneVar1私有變量----");
- //2.獲取ChildClass類中的privateOneVar1私有變量
- Field privateField = mClass.getDeclaredField("privateOneVar1");
- //3. 操作私有變量
- if (privateField != null) {
- //獲取私有變量的訪問權(quán)
- privateField.setAccessible(true);
- //實(shí)例化對象
- ChildClass obj = (ChildClass) mClass.newInstance();
- //修改私有變量,并輸出以測試
- System.out.println("privateOneVar1變量,修改前值: " + obj.printOneMsg());
- //調(diào)用 set(object , value) 修改變量的值
- //privateField 是獲取到的私有變量
- //obj 要操作的對象
- //"hello world" 為要修改成的值
- privateField.set(obj, "hello world");
- System.out.println("privateOneVar1變量,修改后值: " + obj.printOneMsg());
- }
- }
- }
輸出結(jié)果:
- 類的名稱:com.example.java.reflect.ChildClass
- ----獲取ChildClass類中的privateOneVar1私有變量----
- privateOneVar1變量,修改前值: null
- privateOneVar1變量,修改后值:hello world
3.6、獲取類的所有方法
修改實(shí)體類
- /**
- * 基類
- */
- public class BaseClass {
- public String publicBaseVar1;
- public String publicBaseVar2;
- private void privatePrintBaseMsg(String var) {
- System.out.println("基類-私有方法,變量:" + var);
- }
- public void publicPrintBaseMsg(String var) {
- System.out.println("基類-公共方法,變量:" + var);
- }
- }
- /**
- * 子類
- */
- public class ChildClass extends BaseClass{
- public String publicOneVar1;
- public String publicOneVar2;
- private String privateOneVar1;
- private String privateOneVar2;
- public String printOneMsg() {
- return privateOneVar1;
- }
- private void privatePrintOneMsg(String var) {
- System.out.println("子類-私有方法,變量:" + var);
- }
- public void publicPrintOneMsg(String var) {
- System.out.println("子類-公共方法,變量:" + var);
- }
- }
測試:
- public class MethodTest {
- public static void main(String[] args) {
- //1.獲取并輸出類的名稱
- Class mClass = ChildClass.class;
- System.out.println("類的名稱:" + mClass.getName());
- System.out.println("----獲取所有 public 訪問權(quán)限的方法,包括自己聲明和從父類繼承的---");
- //2 獲取所有 public 訪問權(quán)限的方法,包括自己聲明和從父類繼承的
- Method[] mMethods = mClass.getMethods();
- for (Method method : mMethods) {
- //獲取并輸出方法的訪問權(quán)限(Modifiers:修飾符)
- int modifiers = method.getModifiers();
- System.out.print(Modifier.toString(modifiers) + " ");
- //獲取并輸出方法的返回值類型
- Class returnType = method.getReturnType();
- System.out.print(returnType.getName() + " " + method.getName() + "( ");
- //獲取并輸出方法的所有參數(shù)
- Parameter[] parameters = method.getParameters();
- for (Parameter parameter : parameters) {
- System.out.print(parameter.getType().getName() + " " + parameter.getName() + ",");
- }
- //獲取并輸出方法拋出的異常
- Class[] exceptionTypes = method.getExceptionTypes();
- if (exceptionTypes.length == 0){
- System.out.println(" )");
- } else {
- for (Class c : exceptionTypes) {
- System.out.println(" ) throws " + c.getName());
- }
- }
- }
- System.out.println("----獲取所有本類的的方法---");
- //3. 獲取所有本類的的方法
- Method[] allMethods = mClass.getDeclaredMethods();
- for (Method method : allMethods) {
- //獲取并輸出方法的訪問權(quán)限(Modifiers:修飾符)
- int modifiers = method.getModifiers();
- System.out.print(Modifier.toString(modifiers) + " ");
- //獲取并輸出方法的返回值類型
- Class returnType = method.getReturnType();
- System.out.print(returnType.getName() + " " + method.getName() + "( ");
- //獲取并輸出方法的所有參數(shù)
- Parameter[] parameters = method.getParameters();
- for (Parameter parameter : parameters) {
- System.out.print(parameter.getType().getName() + " " + parameter.getName() + ",");
- }
- //獲取并輸出方法拋出的異常
- Class[] exceptionTypes = method.getExceptionTypes();
- if (exceptionTypes.length == 0){
- System.out.println(" )");
- } else {
- for (Class c : exceptionTypes) {
- System.out.println(" ) throws " + c.getName());
- }
- }
- }
- }
- }
輸出:
- 類的名稱:com.example.java.reflect.ChildClass
- ----獲取所有 public 訪問權(quán)限的方法,包括自己聲明和從父類繼承的---
- public java.lang.String printOneMsg( )
- public void publicPrintOneMsg( java.lang.String arg0, )
- public void publicPrintBaseMsg( java.lang.String arg0, )
- public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
- public final native void wait( long arg0, ) throws java.lang.InterruptedException
- public final void wait( ) throws java.lang.InterruptedException
- public boolean equals( java.lang.Object arg0, )
- public java.lang.String toString( )
- public native int hashCode( )
- public final native java.lang.Class getClass( )
- public final native void notify( )
- public final native void notifyAll( )
- ----獲取所有本類的的方法---
- public java.lang.String printOneMsg( )
- private void privatePrintOneMsg( java.lang.String arg0, )
- public void publicPrintOneMsg( java.lang.String arg0, )
為啥會輸出這么多呢?
因?yàn)樗械念惸J(rèn)繼承object類,打開object類會發(fā)現(xiàn)有些公共的方法,所以一并打印出來了!
3.7、調(diào)用方法
- public class MethodInvokeTest {
- public static void main(String[] args) throws Exception {
- // 1.獲取并輸出類的名稱
- Class mClass = ChildClass.class;
- System.out.println("類的名稱:" + mClass.getName());
- System.out.println("----獲取ChildClass類的私有方法privatePrintOneMsg---");
- // 2. 獲取對應(yīng)的私有方法
- // 第一個參數(shù)為要獲取的私有方法的名稱
- // 第二個為要獲取方法的參數(shù)的類型,參數(shù)為 Class...,沒有參數(shù)就是null
- // 方法參數(shù)也可這么寫 :new Class[]{String.class}
- Method privateMethod = mClass.getDeclaredMethod("privatePrintOneMsg", String.class);
- // 3. 開始操作方法
- if (privateMethod != null) {
- // 獲取私有方法的訪問權(quán)
- // 只是獲取訪問權(quán),并不是修改實(shí)際權(quán)限
- privateMethod.setAccessible(true);
- // 實(shí)例化對象
- ChildClass obj = (ChildClass) mClass.newInstance();
- // 使用 invoke 反射調(diào)用私有方法
- // obj 要操作的對象
- // 后面參數(shù)傳實(shí)參
- privateMethod.invoke(obj, "hello world");
- }
- }
- }
輸出結(jié)果:
- 類的名稱:com.example.java.reflect.ChildClass
- ----獲取ChildClass類的私有方法privatePrintOneMsg---
- 子類-私有方法,變量:hello world
四、總結(jié)
由于反射會額外消耗一定的系統(tǒng)資源,因此如果不需要動態(tài)地創(chuàng)建一個對象,那么就不需要用反射。另外,反射調(diào)用方法時可以忽略權(quán)限檢查,因此可能會破壞封裝性而導(dǎo)致安全問題。
五、參考文章
sczyh30:深入解析Java反射
伯特:Java 反射由淺入深