從Class源碼看反射
日常敲碼中,如果想要在程式運行階段訪問某個類的所有資訊,並支援修改類的狀態或者行為的話,肯定會用到反射,而反射靠的就是Class類。Java的動態代理也用到了這個東西,所以了解其基本操作在苦逼的CRUD中會添加一絲絲樂趣(有點意思)。
首先來看看Class的操作有哪些?
public final class Class<T> {}
上述程式碼可知,Class是一個由final修飾的泛型類,所以並不能直接通過new Class()獲取其實例。那麼應該如何獲取呢?
//直接通過類的靜態變數來獲取
Class<Integer> intClass = Integer.class;
//通過實例變數的getClass方法
Integer integer = new Integer(0);
Class<? extends Integer> aClass = integer.getClass();
//通過Class.forName("類的全限定名")
Class<?> aClass1 = Class.forName("java.lang.Integer");
上述三種就是獲取某個Class的class實例的方式,需要注意的是,JVM只會載入一個Class實例,也就是說上述三種方式獲取到的class實例都是一樣的。
而在運用反射的時候,Class.forName是最常用的一種方式。而Class.forName底層會指向forName0這個本地方法
(1)name:類的全限定名
(2)initialize:是否初始化這個類
(3)loader:類載入器
(4)caller:調用Class.forName所在類的Class,比如A類程式碼塊里有Class.forName,那麼caller就是A的class實例。
通過Class類可以獲取類的實例,構造方法,欄位,成員方法,介面等資訊。獲取之後可以通過API進行相應的操作。
接下來看一下獲取到class實例之後怎麼獲取當前類的實例以及構造方法。
上述兩種方式都是調用默認的無參構造進行實例化對象,那麼怎麼通過公共或私有的有參構造獲取實例呢?
//屬性
int a;
String b ;
//公共有參構造
public ClassSource(int a) {
this.a = a;
}
//私有有參構造
private ClassSource(String b) {
this.b = b;
}
public static void main(String[] args){
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取public有參構造
Constructor<?> constructor = aClass.getConstructor(int.class);
ClassSource o1 = (ClassSource) constructor.newInstance(2);
System.out.println("屬性【a】的值為:"+ o1.a);
//獲取private有參構造
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class);
//這個有貓膩
constructor1.setAccessible(true);
ClassSource o2 = (ClassSource) constructor1.newInstance("abc");
System.out.println("屬性【b】的值為:"+ o2.b);
}
上述程式碼運行的結果如下:
而獲取私有構造函數最重要的是要setAccessible(true)這個方法,點進去看一下,這個方法最後是給override欄位賦值的,可用於類的欄位、方法以及構造函數。
public void setAccessible(boolean flag) throws SecurityException {
//這個「安全管理器」,允許應用程式在進行某些不安全或敏感操作
//之前執行安全策略,如果不允許執行,則通過拋異常阻止操作。
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
private static void setAccessible0(AccessibleObject obj,
boolean flag){
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
//如果是Class.class,則拋異常
}
}
//最終是設置此屬性
obj.override = flag;
}
//訪問級別是否可以被覆蓋,可用於欄位,方法,構造函數
boolean override;
需要注意的是,在程式碼中用反射操作當前類私有欄位、私有方法或其他私有屬性時,並不需要setAccessible(true),例如在A類中用反射操作A類的私有屬性。只有在A類中操作其他類的私有屬性才需要設置setAccessible(true)。
不管是Class.newInstance還是Constructor.newInstance,底層調用的都是ConstructorAccessor(構造函數訪問器)介面的newInstance方法。
(1)Class.newInstance
//標識所有公共屬性
public static final int PUBLIC = 0;
//標識所有私有屬性
public static final int DECLARED = 1;
public T newInstance(){
//省略部分程式碼
// 檢查有沒有存在已載入過的構造器
if (cachedConstructor == null) {
//省略部分程式碼
try {
Class<?>[] empty = {};
//獲取默認私有無參構造
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
//省略部分程式碼
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
//省略部分程式碼
try {
//調用無參構造的newInstance方法
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
//省略部分程式碼
return null;
}
}
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
int which)
{
//獲取構造函數,重點看這個方法,參數是false
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
//省略匹配構造函數參數的程式碼
}
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
//檢查是否已被初始化,如果否,則進行安全檢查
checkInitted();
Constructor<T>[] res;
//反射的數據
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;
}
// 檢查是否是介面
if (isInterface()) {
//程式碼省略
} else {
//底層調用的是本地方法
res = getDeclaredConstructors0(publicOnly);
}
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}
如上,可以看到Class.newInstance底層是調用無參構造的newInstance方法。
(2)Constructor.newInstance
public T newInstance(Object ... initargs){
//override就是是否可覆蓋級別訪問
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
public interface ConstructorAccessor {
Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
}
可以看到,Class.newInstance調用的是無參構造的newInstance,而構造函數調用的是ConstructorAccessor介面類的newInstance,所以最終調用的都是ConstructorAccessor介面類的newInstance。
最終調用的是NativeConstructorAccessorImpl的newInstance方法。
【注】以下僅演示操作方法,源碼不貼,防止篇幅太長。
獲取實例以及構造方法之後,來看一下如何訪問,修改類欄位資訊。
//public屬性
public int a;
//private屬性
private String b;
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取實例
Object instance = aClass.newInstance();
//根據欄位名獲取公共屬性並進行賦值
Field a = aClass.getField("a");
a.set(instance,2);
System.out.println("a的值為:"+a.get(instance));
//根據欄位名獲取私有屬性並進行賦值
Field b = aClass.getDeclaredField("b");
b.setAccessible(true);
b.set(instance,"abc");
System.out.println("b的值為"+b.get(instance));
}
上述程式碼運行結果為:
接下來瞧一下如何利用class訪問類的成員方法:
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取實例
Object instance = aClass.newInstance();
//獲取公共方法並調用
Method sayPublicHello = aClass.getMethod("sayPublicHello");
sayPublicHello.invoke(instance);
//獲取私有方法並調用
Method sayPrivateHello = aClass.getDeclaredMethod("sayPrivateHello", int.class);
sayPrivateHello.setAccessible(true);
sayPrivateHello.invoke(instance,1);
}
public void sayPublicHello(){
System.out.println("sayPublicHello");
}
private void sayPrivateHello(int param){
System.out.println("sayPrivateHello,參數:"+param);
}
上述程式碼運行結果為:
還可以使用「==」判斷數據類型
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
System.out.println(aClass == ClassSource.class);
之前遇到過用了很多反射的程式碼,看起來很噁心。但其實了解過後反而覺得反射是個很強大的東西,包括JDK動態代理,spring AOP,事務底層都是用的反射,研究之後有利於了解更多底層的知識。
=======================================================
我是Liusy,一個喜歡健身的程式設計師。
歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多乾貨。