什么是反射?
反射,一種計算機處理方式。是程序可以訪問、檢測和修改它本身狀態或行為的一種能力。java反射使得我們可以在程序運行時動態加載一個類,動態獲取類的基本信息和定義的方法,構造函數,域等。除了檢閱類信息外,還可以動態創建類的實例,執行類實例的方法,獲取類實例的域值。反射使java這種靜態語言有了動態的特性。
類的加載
java反射機制是圍繞Class類展開的,在深入java反射原理之前,需要對類加載機制有一個大致的了解。jvm使用ClassLoader將字節碼文件(class文件)加載到方法區內存中:
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");
可見ClassLoader根據類的完全限定名加載類並返回了一個Class對象,而java反射的所有起源都是從這個class類開始的。
ReflectionData
為了提高反射的性能,緩存顯然是必須的。class類內部有一個useCaches靜態變量來標記是否使用緩存,這個值可以通過外部配置項sun.reflect.noCaches進行開關。class類內部提供了一個ReflectionData內部類用來存放反射數據的緩存,並聲明了一個reflectionData域,由於稍后進行按需延遲加載並緩存,所以這個域並沒有指向一個實例化的ReflectionData對象。
//標記是否使用緩存,可以通過外部配置項sun.reflect.noCaches進行禁用。
private static boolean useCaches = true;
static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
//注意這是個SoftReference,在內存資源緊張的時候可能會被回收。volatile保證多線程環境下的讀寫的正確性
private volatile transient SoftReference<ReflectionData<T>> reflectionData;
//J主要用於和ReflectionData中的redefinedCount進行比較,如果兩個值不相等,說明ReflectionData緩存的數據已經過期了。
private volatile transient int classRedefinedCount = 0;
獲取類的構造函數
在獲取一個類的class后,我們可以通過反射獲取一個類的所有構造函數,class類內部封裝了如下方法用於提取構造函數:
//publicOnly指示是否只獲取public的構造函數,我們常用的getConstructors方法是只返回public的構造函數,而getDeclaredConstructors返回的是所有構造函數,由於java的構造函數不會繼承,所以這里不包含父類的構造函數。
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
checkInitted(); //主要是讀取了sun.reflect.noCaches配置。
Constructor<T>[] res;
ReflectionData<T> rd = reflectionData();//這里緩存中讀取reflectionData,如果還沒有緩存,則創建一個reflectionData並設置到緩存。但是注意這個ReflectionData可能只是個空對象,里面並沒有任何數據。
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;//檢查緩存中是否有數據
}
//沒有緩存數據可用,從這里開始需要從jvm中去獲取數據。
if (isInterface()) {
res = new Constructor[0];//接口沒有構造函數
} else {
res = getDeclaredConstructors0(publicOnly);//native方法,從jvm中獲取
}
//如果代碼執行到了這里,說明需要更新緩存了,將之前從jvm中請求到的數據放置到緩存中。
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}
上面的代碼片段比較簡單,主要就是讀取配置,檢查緩存中是否有有效數據,如果有,直接從緩存中返回,如果沒有,調用native的方法從jvm中請求數據,然后設置到緩存。比較重要的是reflectionData()
這個調用。這個方法主要是用於延遲創建並緩存ReflectionData對象,注意是對象,里面並沒有保存反射數據,這些數據只有在第一次執行相應的反射操作后才會被填充。下面是這個方法的實現代碼:
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
//檢查緩存是否有效,如果有效,從緩存中直接返回reflectionData。
if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
//無法使用到緩存,創建新的reflectionData
return newReflectionData(reflectionData, classRedefinedCount);
}
下面看一下newReflectionData()的實現:
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {
if (!useCaches) return null;
//這里使用while-cas模式更新reflectionData,主要是考慮多線程並發更新的問題,可能有另外一個線程已經更新了reflectionData,並且設置了有效的緩存數據,如果這里再次更新就把緩存數據覆蓋了。
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;//cas成功,那么我們swap的這個新對象是有效的。
}
//重新讀取oldReflectionData,為下次重試cas做准備。
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
//先判斷這個oldReflectionData是否有效。如果是無效的數據,需要去重試cas一個新的ReflectionData了。
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;//reflectionData中已經是有效的緩存數據了,直接返回這個reflectionData
}
}
}
上面的幾個代碼片段是反射獲取一個類的構造函數的主要方法調用。主要流程就是先從class內部的reflectionData緩存中讀取數據,如果沒有緩存數據,那么就從jvm中去請求數據,然后設置到緩存中,供下次使用。
執行構造函數
在通過反射獲取到一個類的構造函數我們,一般我們會試圖通過構造函數去實例化聲明這個構造函數的類,如下:
MyClass myClass = (MyClass) constructor.newInstance();
下面來看看這個newInstance()到底發生了什么:
public T newInstance(Object... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
//首先判斷語言級別的訪問檢查是否被覆蓋了(通過setAccess(true)方法可以將private的成員變成public),如果沒有被覆蓋,需要進行訪問權限檢查。
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//枚舉無法通過反射創建實例
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
//創建ConstructorAccessor對象,並進行緩存
ConstructorAccessor ca = constructorAccessor;
if (ca == null) {
ca = acquireConstructorAccessor();
}
//通過ConstructorAccessor來執行newInstance
return (T) ca.newInstance(initargs);
}
上面代碼主要就是做權限檢查,如果權限通過,則通過執行acquireConstructorAccessor()獲取一個ConstructorAccessor來真正執行newInstance,acquireConstructorAccessor()這個方法實現比較簡單,真正工作的是ReflectionFactory.newConstructorAccessor(),這個方法的主要工作就是為一個Constructor動態生成針對ConstructorAccessor接口的實現ConstructorAccessorImpl,下面來看一下這個方法實現:
public ConstructorAccessor newConstructorAccessor(Constructor c) {
checkInitted();
Class declaringClass = c.getDeclaringClass();
//如果聲明這個構造函數的類部是抽象類,則返回一個InstantiationExceptionConstructorAccessorImpl,這其實也是個ConstructorAccessor,只是它對newInstace的實現是直接拋出一個InstantiationException異常
if (Modifier.isAbstract(declaringClass.getModifiers())) {
return new InstantiationExceptionConstructorAccessorImpl(null);
}
//如果是Class,同上
if (declaringClass == Class.class) {
return new InstantiationExceptionConstructorAccessorImpl
("Can not instantiate java.lang.Class");
}
//如果是ConstructorAccessorImpl子類,這里會造成無限循環,所以直接通過native方式實例化這個類
if (Reflection.isSubclassOf(declaringClass,
ConstructorAccessorImpl.class)) {
return new BootstrapConstructorAccessorImpl(c);
}
//判斷是否啟用了Inflation機制,默認是啟用了。
//如果沒有啟用Inflation機制,那么通過asm操作字節碼的方式來生成一個ConstructorAccessorImpl類。
//如果啟用了,那么在執行inflationThreshold(默認15次)次數之前,是通過navite調用來執行newInstance,超過這個次數之后,才會通過asm來生成類。
//Inflation機制主要是在執行時間和啟動時間上做一個平衡,native方式執行慢但是第一次執行不耗費任何時間,asm生成代碼的方式執行快(20倍),但是第一次生成需要耗費大量的時間。
//可以通過sun.reflect.noInflation和sun.reflect.inflationThreshold配置型來進行動態配置
if (noInflation) {
return new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(),
c.getExceptionTypes(),
c.getModifiers());
} else {
NativeConstructorAccessorImpl acc =
new NativeConstructorAccessorImpl(c);
DelegatingConstructorAccessorImpl res =
new DelegatingConstructorAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
由上面代碼可見,在生成ConstructorAccessorImpl的操作上,一共提供多種版本的實現:直接拋出異常的實現,native執行的實現,asm生成代碼的實現。native的實現比較簡單,asm 版本的主要就是字節碼操作了,比較復雜,暫時不做深入了。