博客原文:http://www.sczyh30.com/posts/Java/java-reflection-1/
- 這老哥寫的特別好java
反射(Reflection)是Java 程序開發語言的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。
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反射機制能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制直接建立對象,即便這個對象的類型在編譯期是未知的。
反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。數組
Java反射框架主要提供如下功能:安全
不少人都認爲反射在實際的Java開發應用中並不普遍,其實否則。
當咱們在使用IDE(如Eclipse,IDEA)時,當咱們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裏就會用到反射。
反射最重要的用途就是開發各類通用框架。
不少框架(好比Spring)都是配置化的(好比經過XML文件配置JavaBean,Action之類的),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射——運行時動態加載須要加載的對象。
舉一個例子,在運用Struts 2框架的開發中咱們通常會在struts.xml裏去配置Action,好比:app
1
2
3
4
5
6
|
<action name="login"
class="org.ScZyhSoft.test.action.SimpleLoginAction"
method="execute">
<result>/shop/shop-index.jsp</result>
<result name="error">login.jsp</result>
</action>
|
配置文件與Action創建了一種映射關係,當View層發出請求時,請求會被StrutsPrepareAndExecuteFilter攔截,而後StrutsPrepareAndExecuteFilter會去動態地建立Action實例。
——好比咱們請求login.action,那麼StrutsPrepareAndExecuteFilter就會去解析struts.xml文件,檢索action中name爲login的Action,並根據class屬性建立SimpleLoginAction實例,並用invoke方法來調用execute方法,這個過程離不開反射。
對與框架開發人員來講,反射雖小但做用很是大,它是各類容器實現的核心。而對於通常的開發者來講,不深刻框架開發則用反射用的就會少一點,不過了解一下框架的底層機制有助於豐富本身的編程思想,也是頗有益的。框架
上面咱們提到了反射能夠用於判斷任意對象所屬的類,得到Class對象,構造任意一個對象以及調用一個對象。這裏咱們介紹一下基本反射功能的實現(反射相關的類通常都在java.lang.relfect包裏)。jsp
方法有三種
(1)使用Class類的forName靜態方法:oop
1
2
3
4
5
|
public static Class<?> forName(String className)
```
在JDBC開發中經常使用此方法加載數據庫驅動:
```java
Class.forName(driver);
|
(2)直接獲取某一個對象的class,好比:post
1
2
|
Class<?> klass =
int.class;
Class<?> classInt = Integer.TYPE;
|
(3)調用某個對象的getClass()方法,好比:
1
2
|
StringBuilder str =
new StringBuilder("123");
Class<?> klass = str.getClass();
|
通常地,咱們用instanceof關鍵字來判斷是否爲某個類的實例。同時咱們也能夠藉助反射中Class對象的isInstance()方法來判斷是否爲某個類的實例,它是一個Native方法:
1
|
public native boolean isInstance(Object obj);
|
經過反射來生成對象主要有兩種方式。
(1)使用Class對象的newInstance()方法來建立Class對象對應類的實例。
1
2
|
Class<?> c = String.class;
Object str = c.newInstance();
|
(2)先經過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來建立實例。這種方法能夠用指定的構造器構造類的實例。
1
2
3
4
5
6
7
|
//獲取String所對應的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器建立實例
Object obj = constructor.newInstance(
"23333");
System.out.println(obj);
|
獲取某個Class對象的方法集合,主要有如下幾個方法:
getDeclaredMethods()方法返回類或接口聲明的全部方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
1
|
public Method[] getDeclaredMethods() throws SecurityException
|
getMethods()方法返回某個類的全部公用(public)方法,包括其繼承類的公用方法。
1
|
public Method[] getMethods() throws SecurityException
|
getMethod方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象
1
|
public Method getMethod(String name, Class<?>... parameterTypes)
|
只是這樣描述的話可能難以理解,咱們用例子來理解這三個方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package org.ScZyhSoft.common;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test1 {
public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c = methodClass.class;
Object object = c.newInstance();
Method[] methods = c.getMethods();
Method[] declaredMethods = c.getDeclaredMethods();
//獲取methodClass類的add方法
Method method = c.getMethod(
"add", int.class, int.class);
//getMethods()方法獲取的全部方法
System.out.println(
"getMethods獲取的方法:");
for(Method m:methods)
System.out.println(m);
//getDeclaredMethods()方法獲取的全部方法
System.out.println(
"getDeclaredMethods獲取的方法:");
for(Method m:declaredMethods)
System.out.println(m);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}
|
程序運行的結果以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
getMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
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()
getDeclaredMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
|
能夠看到,經過getMethods()獲取的方法能夠獲取到父類的方法,好比java.lang.Object下定義的各個方法。
獲取類構造器的用法與上述獲取方法的用法相似。主要是經過Class類的getConstructor方法獲得Constructor類的一個實例,而Constructor類有一個newInstance方法能夠建立一個對象實例:
1
|
public T newInstance(Object ... initargs)
|
此方法能夠根據傳入的參數來調用對應的Constructor建立對象實例~
主要是這幾個方法,在此再也不贅述:
getFiled: 訪問公有的成員變量
getDeclaredField:全部已聲明的成員變量。但不能獲得其父類的成員變量
getFileds和getDeclaredFields用法同上(參照Method)
當咱們從類中獲取了一個方法後,咱們就能夠用invoke()方法來調用這個方法。invoke方法的原型爲:
1
2
3
|
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
|
下面是一個實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class test1 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = methodClass.class;
//建立methodClass的實例
Object obj = klass.newInstance();
//獲取methodClass類的add方法
Method method = klass.getMethod(
"add",int.class,int.class);
//調用method對應的方法 => add(1,4)
Object result = method.invoke(obj,
1,4);
System.out.println(result);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}
|
關於invoke()方法的詳解,後面我會專門寫一篇文章來深刻解析invoke的過程。
數組在Java裏是比較特殊的一種類型,它能夠賦值給一個Object Reference。下面咱們看一看利用反射建立數組的例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
public static void testArray() throws ClassNotFoundException {
Class<?> cls = Class.forName(
"java.lang.String");
Object array = Array.newInstance(cls,
25);
//往數組裏添加內容
Array.set(array,
0,"hello");
Array.set(array,
1,"Java");
Array.set(array,
2,"fuck");
Array.set(array,
3,"Scala");
Array.set(array,
4,"Clojure");
//獲取某一項的內容
System.out.println(Array.get(array,
3));
}
|
其中的Array類爲java.lang.reflect.Array類。咱們經過Array.newInstance()建立數組對象,它的原型是:
1
2
3
4
|
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
|
而newArray()方法是一個Native方法,它在Hotspot JVM裏的具體實現咱們後邊再研究,這裏先把源碼貼出來
1
2
|
private static native Object newArray(Class<?> componentType, int length)
throws NegativeArraySizeException;
|
源碼目錄:openjdk\hotspot\src\share\vm\runtime\reflection.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
if (element_mirror == NULL) {
THROW_0(vmSymbols::java_lang_NullPointerException());
}
if (length < 0) {
THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
}
if (java_lang_Class::is_primitive(element_mirror)) {
Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
}
else {
Klass* k = java_lang_Class::as_Klass(element_mirror);
if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
return oopFactory::new_objArray(k, length, THREAD);
}
}
|
另外,Array類的set()和get()方法都爲Native方法,在HotSpot JVM裏分別對應Reflection::array_set和Reflection::array_get方法,這裏就不詳細解析了。
因爲反射會額外消耗必定的系統資源,所以若是不須要動態地建立一個對象,那麼就不須要用反射。另外,反射調用方法時能夠忽略權限檢查,所以可能會破壞封裝性而致使安全問題。