一、介紹
Java反射是指在運(yùn)行時檢查或操作類、接口、字段或方法的能力。通過使用反射,可以在運(yùn)行時獲取類的信息、構(gòu)造對象、執(zhí)行方法、訪問或修改字段等,而不需要事先知道類的結(jié)構(gòu)。在Java中,反射功能由java.lang.reflect包提供支持。通過使用java.lang.reflect類中的Class、Constructor、Method、Field等類,可以實(shí)現(xiàn)對類的各種操作。使用反射可以實(shí)現(xiàn)一些高級的動態(tài)操作,比如動態(tài)代碼生成、插件系統(tǒng)、對象序列化等。
public class Student {
public String name;
private int age;
final boolean isGraduation = Boolean.FALSE;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
private void study() {
System.out.println("學(xué)生正在學(xué)習(xí)!!!");
}
public void setName(String name) {
System.out.println("setName: " + name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge: " + age);
this.age = age;
}
@Override
public String toString() {
return "Student " + name + " is " + age + "-years old";
}
}
【正射】:
我們在編寫代碼時,當(dāng)需要使用到某一個類的時候,都會先了解這個類是做什么的,然后實(shí)例化這個類,接著用實(shí)例化好的對象進(jìn)行操作,這就是正射。
Student s1 = new Student("小明", 12);
打?。篠tudent 小明 is 12-years old
【反射】:
反射就是,一開始并不知道我們要初始化的類對象是什么,自然也無法使用 new 關(guān)鍵字來創(chuàng)建對象了。這就需要借助反射工具(api)來實(shí)例化我們需要訪問的類。JDK從1.1開始就支持反射,使用java.lang.reflect包里的工具。
Class<?> studentClass = Class.forName("reflectiontest.Student");
Constructor<?> studentClassConstructor = studentClass.getConstructor(String.class, int.class);
Object s2 = studentClassConstructor.newInstance("小紅", 10);
打?。篠tudentg 小紅 is 10-years old
所以,這兩段代碼的區(qū)別就在于,正射是在運(yùn)行前就已經(jīng)知道要運(yùn)行的類是Student;反射是在程序運(yùn)行的時候才通過字符串"reflectiontest.Student"知道操作的類。
Class類
一個類中有屬性,方法,構(gòu)造器等,每個類可能都不一樣,所以需要一個類,用來描述類,這就是Class。Class是用來描述類的類,它封裝了當(dāng)前對象所對應(yīng)的類的信息。Java通過以下兩種方式在運(yùn)行時識別對象的類型和類的信息:
- RTTI(Run-Time Type Identification)運(yùn)行時類型識別
- 反射機(jī)制
Class類是一個對象照鏡子的結(jié)果,對象可以看到自己有哪些屬性,方法,構(gòu)造器,實(shí)現(xiàn)了哪些接口等等,對于每個類而言,JRE 都為其保留一個不變的 Class 類型的對象。一個類(而不是一個對象)在 JVM 中只會有一個Class實(shí)例。

獲取Class對象的四種方式:
- 類的.class方法
Class<Student> clazz = Student.class;
- 使用
Class.forName()方法
Class<?> clazz= Class.forName("reflectiontest.Student");
- 使用實(shí)例對象的
getClass()方法
Student s1 = new Student("小明", 12);
Class<Student> clazz = s1.getClass();
- 根據(jù)類加載器獲取
URL url = new URL("file:XXX\\Student.java");
ClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<Student> clazz= classLoader.loadClass("reflectiontest.Student");
下面列舉Class類常用方法和說明,
| 方法 | 說明 |
|---|---|
| static Class forName(String className) | 返回指定類名name的Class 對象 |
| Object newInstance() | 調(diào)用缺省構(gòu)造函數(shù),返回該Class對象的一個實(shí)例(已過時) |
| String getName() | 返回該Class對象所表示的實(shí)體(類、接口、數(shù)組類、基本類型或void)名稱 |
| Class getSuperclass() | 返回當(dāng)前Class對象的父類Class對象 |
| String getPackageName() | 返回當(dāng)前Class對象所在的包名 |
| Class[] getInterfaces() | 返回當(dāng)前Class對象實(shí)現(xiàn)的接口 |
| ClassLoader getClassLoader() | 返回該類的類加載器 |
| Constructor[] getConstructors() | 返回該類的所有公開構(gòu)造方法 |
| Constructor[] getDeclaredConstructors() | 返回該類的所有構(gòu)造方法 |
| Method[] getMethods() | 返回該類的所有公開方法,包括繼承的公開方法 |
| Method[] getDeclaredMethods() | 返回該類的所有方法,不包括繼承的方法 |
| Field[] getFields() | 返回該類的所有公開屬性,包括繼成的公開屬性 |
| Field[] getDeclaredFields() | 返回該類的所有屬性,不包括集成的屬性 |
上面方法中的Constructor、Method、Field均來自java.lang.reflect包。
Class對象是反射機(jī)制的核心,只有獲取到一個類的Class對象,才能通過Class對象包含的類信息還原最初的類,即反射的核心要義。下面列舉幾個常見的例子。
- 反射實(shí)例化對象
// Class.forName反射獲取class
Class<?> studentClass = Class.forName("reflectiontest.Student");
// 獲取指定參數(shù)類型的構(gòu)造方法
Constructor<?> constructor = studentClass.getConstructor(String.class, int.class);
// 通過newInstance實(shí)例化class
Object student = constructor.newInstance("小紅", 10);
- 反射獲取私有方法
Method study = studentClass.getDeclaredMethod("study");
// 私有方法需要設(shè)置accessible=true以開放其訪問權(quán)限
study.setAccessible(true);
// 調(diào)用study方法
study.invoke(student);
- 反射獲取私有屬性
Field age = studentClass.getDeclaredField("age");
// 私有屬性設(shè)置accessible=true以開放其訪問權(quán)限
age.setAccessible(true);
// 獲取student實(shí)例的age值
int intAge = (int) age.get(student);
// 設(shè)置實(shí)例student的age=9
age.set(student, 9);
- 修改final修飾的屬性值(危險操作)
Field isGraduation = studentClass.getDeclaredField("isGraduation");
isGraduation.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
// 把isGraduation中的final修飾符去掉
modifiersField.setInt(isGraduation, isGraduation.getModifiers() & ~Modifier.FINAL);
// 修改student中isGraduation=true
isGraduation.setBoolean(student, true);
注意,只有非內(nèi)聯(lián)優(yōu)化的變量才能通過反射修改值,內(nèi)聯(lián)優(yōu)化的final屬性是無法被修改的。這種做法是不推薦的,因?yàn)樗鼤茐膄inal屬性的設(shè)計(jì)初衷,可能會導(dǎo)致不可預(yù)測的行為和安全問題。在實(shí)際開發(fā)中,應(yīng)該遵循final屬性的設(shè)計(jì)原則,不要試圖繞過它的限制。
二、原理
參考 https://www.cnblogs.com/yougewe/p/10125073.html
三、應(yīng)用
1. Android LayoutInlater
我們在使用xml進(jìn)行界面布局的時候,layout.xml里的每個節(jié)點(diǎn)就是通過反射轉(zhuǎn)換成對應(yīng)View的Java類的。在Activity的onCreate生命周期函數(shù)中調(diào)用setContentView(layoutResId)來綁定布局,最終會調(diào)到LayoutInflater.inflate(layoutResId)中,
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
...
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
view = createView(context, name, null, attrs);
...
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
...
// 緩存中查找View的構(gòu)造方法
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
if (constructor == null) {
// 反射獲取View的Class對象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
// 獲取構(gòu)造方法
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
...
}
// 實(shí)例化View
final View view = constructor.newInstance(args);
return view;
...
}
2. 動態(tài)代理
動態(tài)代理是通過反射機(jī)制動態(tài)生成代理者對象的一種設(shè)計(jì)模式,區(qū)別于靜態(tài)代理,動態(tài)代理在運(yùn)行時按需動態(tài)生成目標(biāo)代理對象,解決了靜態(tài)代理模式服務(wù)對象單一、擴(kuò)展性低的問題。JDK提供了實(shí)現(xiàn)動態(tài)代理的頂層類java.util.reflect.Proxy,可以通過Proxy類的靜態(tài)方法Proxy.newProxyInstance()動態(tài)創(chuàng)建一個類的代理類,并實(shí)例化。由它創(chuàng)建的代理類都是Proxy類的子類。
/**
* 定義目標(biāo)對象的抽象接口
*/
public interface ISubject {
void buy();
}
/**
* 實(shí)現(xiàn)目標(biāo)類(實(shí)現(xiàn) ISubject 接口)
*/
public class Buyer implements ISubject {
@Override
public void buy() {
System.out.println("Buyer buy");
}
}
/**
* 聲明調(diào)用處理類(實(shí)現(xiàn) InvocationHandler 接口)
*/
public class SubjectInvocationHandler implements InvocationHandler {
private final ISubject mTarget;
public SubjectInvocationHandler(ISubject target) {
this.mTarget = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy invoke, proxy = " + proxy.getClass() + ", mTarget = " + mTarget.getClass());
return method.invoke(mTarget, args);
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
SubjectInvocationHandler handler = new SubjectInvocationHandler(buyer);
ISubject buyerProxy = (ISubject) Proxy.newProxyInstance(
buyer.getClass().getClassLoader(),
buyer.getClass().getInterfaces(),
handler);
buyerProxy.buy();
}
}
打印:
proxy invoke, proxy = class com.sun.proxy.$Proxy0, mTarget = class reflectiontest.dynamicproxy.realobj.Buyer
Buyer buy
打印結(jié)果顯示生成了中間com.sun.proxy.$Proxy0,我們通過在main方法中加下面這段代碼,保留生成的代碼。變量為 true 時,將會在工程目錄下生成 $Proxy0 的 class 文件。
// JDK 1.8以前
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 1.8以后
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// 或
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
package com.sun.proxy;
public final class $Proxy0 extends Proxy implements ISubject {
...
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
...
public final void buy() throws {
try {
// h是父類Proxy中的InvocationHandler類型成員變量
// 通過代理類訪問目標(biāo)對象的方法
// 最終會通過 super.h.invoke() 回調(diào)到我們重寫的 InvocationHandler 實(shí)現(xiàn)類的 invoke() 中
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
...
// 通過反射獲取目標(biāo)類實(shí)現(xiàn)方法
m3 = Class.forName("reflectiontest.dynamicproxy.ISubject").getMethod("buy");
...
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
下面簡單看看這個$Proxy0是怎么生成的,
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
...
// 通過構(gòu)造器實(shí)例化代理對象
return cons.newInstance(new Object[]{h});
...
}
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces) {
// optimization for single interface
if (interfaces.length == 1) {
...
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
...
}
}
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
Constructor<?> build() {
// 根據(jù)module和interfaces信息構(gòu)造代理的Class對象,代理默認(rèn)含有有個InvocationHandler參數(shù)類型的構(gòu)造方法
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
...
cons = proxyClass.getConstructor(constructorParams);
...
return cons;
}
3. 注解
四、思考
1. 反射解決了什么問題
- 動態(tài)加載類:Java反射允許在運(yùn)行時動態(tài)加載類,而不是在編譯時就確定需要使用的類。
- 運(yùn)行時獲取類信息:通過反射可以在運(yùn)行時獲得類的信息,如類名、方法名、字段名、注解等,這使得可以在運(yùn)行時動態(tài)地操作類和對象。
- 動態(tài)調(diào)用方法:使用反射可以在運(yùn)行時動態(tài)調(diào)用類的方法,這對于框架和庫的設(shè)計(jì)和使用非常有用。
- 修改私有字段和方法:反射允許訪問和修改類的私有字段和方法,這在某些情況下是很有用的,但也需要小心使用,以避免破壞類的封裝性。
應(yīng)用場景一:有的類是我們在編寫程序的時候無法使用new一個對象來實(shí)例化對象的。例如:
- 調(diào)用的是來自網(wǎng)絡(luò)的二進(jìn)制.class文件,而沒有其.java代碼
- 注解。運(yùn)行時注解包含的元數(shù)據(jù)只能通過反射獲取
應(yīng)用場景二:動態(tài)加載(可以最大限度的體現(xiàn)Java的靈活性,并降低類的耦合性:多態(tài))。有的類可以在用到時再動態(tài)加載到j(luò)vm中,這樣可以減少jvm的啟動時間,同時更重要的是可以動態(tài)的加載需要的對象(多態(tài))
- 動態(tài)代理
應(yīng)用場景三:避免將程序?qū)懰赖酱a里。比如Java通過new關(guān)鍵字創(chuàng)建了一個對象并編譯成class字節(jié)碼文件,如果我想替換這個對象,就得重新編譯一個新的class文件,但如果使用反射Class.forName(String name)就可以從配置文件中讀取要實(shí)例化的對象而無需重新編譯。
- Spring框架中通過xml配置JavaBean
2. 效率問題
下面舉個例子,
public class DynamicProxyTest {
public static long timeDiff(long old) {
return System.currentTimeMillis() - old;
}
public static void main(String[] args) {
long numTrials = (long) Math.pow(10, 7);
long millis = System.currentTimeMillis();
for (int i = 0; i < numTrials; i++) {
new Student();
}
System.out.println("Normal instaniation took: "
+ timeDiff(millis) + "ms");
millis = System.currentTimeMillis();
Class<Student> c = Student.class;
for (int i = 0; i < numTrials; i++) {
c.getConstructor().newInstance();
}
System.out.println("Reflecting instantiation took:"
+ timeDiff(millis) + "ms");
}
}
打印:
Normal instaniation took: 83ms
Reflecting instantiation took:510ms
Java反射機(jī)制性能低下有以下幾個原因:
- 動態(tài)類型檢查:在使用反射時,編譯器無法對代碼進(jìn)行類型檢查,而是在運(yùn)行時才能確定訪問對象的類型。這意味著在每次使用反射時都需要做類型檢查和轉(zhuǎn)換操作,增加了運(yùn)行時的開銷。比如對方法參數(shù)的裝包和拆包。
- 方法調(diào)用開銷:通過反射調(diào)用方法時,通常需要使用Method對象的invoke()方法,這會涉及方法查找、參數(shù)類型匹配等操作,相比直接調(diào)用方法會更耗時。
- 訪問控制檢查:反射機(jī)制允許改變類的私有字段或方法的訪問權(quán)限,因此在訪問私有成員時需要進(jìn)行額外的訪問控制檢查,增加了開銷。
- 緩存未命中:由于反射操作是動態(tài)的,不同于直接調(diào)用方法或訪問字段,因此緩存機(jī)制可能失效,導(dǎo)致性能下降。
- 無法使用編譯器優(yōu)化:由于反射操作是動態(tài)的,所以無法使用某些編譯器優(yōu)化,比如內(nèi)聯(lián)優(yōu)化,因?yàn)檎{(diào)用的具體方法在運(yùn)行時才確定。
3. 安全問題
反射允許在運(yùn)行時動態(tài)調(diào)用類的私有資源,這可能會引起意料之外的結(jié)果,造成程序功能失常。下面以單例為例,單例允許全局有且僅有一個實(shí)例,因此一般私有化其構(gòu)造方法,但反射能通過修改構(gòu)造方法權(quán)限實(shí)例化對象,造成單例失效。下面嘗試一下單例模式的防反射攻擊。
- 餓漢模式
public class SingleClass {
private final static SingleClass mInstance = new SingleClass();
private SingleClass() {
if (mInstance != null) {
throw new RuntimeException("單例模式不允許重復(fù)創(chuàng)建!");
}
}
public static SingleClass getInstance() {
return mInstance;
}
}
public static void main(String[] args) {
Constructor<SingleClass> constructor = SingleClass.class.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
異常:

- 懶漢模式
public class SingleClass {
private volatile static SingleClass mInstance;
private SingleClass() {
if (mInstance != null) {
throw new RuntimeException("單例模式不允許重復(fù)創(chuàng)建!");
}
}
public static SingleClass getInstance() {
if (mInstance == null) {
synchronized (SingleClass.class) {
if (mInstance == null) {
mInstance = new SingleClass();
}
}
}
return mInstance;
}
}
public static void main(String[] args) {
SingleClass singleClass = SingleClass.getInstance();
log(singleClass.toString());
Constructor<SingleClass> constructor = SingleClass.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingeClass reflectionSingeClass = (SingeClass) constructor.newInstance();
log(reflectionSingeClass.toString());
}

但是如果將getInstance和反射初始化調(diào)整順序,單例就會被破壞。

如果在單例中增加標(biāo)志位flag,如下,
private SingleClass() {
if (flag) {
throw new RuntimeException("單例模式不允許重復(fù)創(chuàng)建!");
} else {
flag = true;
}
}
這樣也是不行的,因?yàn)樽兞縡lag值可以通過反射修改。
綜上,這種局限于構(gòu)造順序的防反射攻擊方法僅對餓漢模式有作用,對于懶漢模式是沒有作用的。