您好,登錄后才能下訂單哦!
問題
1.反射真的慢么?
2.動態(tài)代理會創(chuàng)建很多臨時class?
3.屬性通過反射讀取怎么實現(xiàn)的?
當(dāng)我們在IDE中編寫代碼的時候,打一個點號,IDE會自動彈出對應(yīng)的屬性和方法名,當(dāng)我們在debug的時候,IDE會將方法運行時方法內(nèi)局部變量和外部實例上屬性的值都展示出來,spring中的IOC和AOP,以及一個RPC框架中,我們反序列化,consumer的代理,以及provider的調(diào)用都會用到j(luò)ava的反射功能,有人說使用反射會慢,那么到底慢在哪里呢?
反射
反射使JAVA語言有了動態(tài)編譯的功能,也就是在我們編碼的時候不需要知道對象的具體類型,但是在運行期可以通過Class.forName()獲取一個類的class對象,在通過newInstance獲取實例。
先看下java.lang.reflect包下的幾個主要類的關(guān)系圖,當(dāng)然動態(tài)代理的工具類也在該包下。
AnnotatedElement
作為頂級接口,這個接口提供了獲取注解相關(guān)的功能,我們在方法,類,屬性,構(gòu)造方法上都可以加注解,所以下面的Field,Method,Constructor都有實現(xiàn)這個接口,以下是我們經(jīng)常用的兩個方法,jdk8以后,接口里面可以通過default修飾方法實現(xiàn)了
Annotation[] getAnnotations(); //獲取目標(biāo)對象(方法和屬性)上的所有注解
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead
// of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}
GenericDeclaration
提供了獲取泛型相關(guān)的功能,只有方法和構(gòu)造方法上支持泛型,所以只有Method,Constructor實現(xiàn)了該接口
Member
作為一個對象內(nèi)部方法和屬性的聲明的抽象,包含了名稱,修飾符,所在的類,其中修飾符包含了 static final public private volatile 等,通過一個整數(shù)表示,每一個類型在二進制中占一個位.
public Class<?> getDeclaringClass();
public String getName();
public int getModifiers();
以下為Modifier類部分代碼
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
public static boolean isPublic(int mod) {
return (mod & PUBLIC) != 0;
}
AccessibleObject
這是一個類,提供了權(quán)限管理的功能,例如是否允許在反射中在外部調(diào)用一個private方法,獲取一個private屬性的值,所以method,constructor,field都繼承該類,下面這段代碼展示了如何在反射中訪問一個私有的成員變量,class對象的構(gòu)造方法不允許對外.
private static void setAccessible0(AccessibleObject obj, boolean flag)
throws SecurityException
{
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Cannot make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
}
boolean override;
public boolean isAccessible() {
return override;
}
以下為 Field里面通過field.get(原始對象)獲取屬性值得實現(xiàn),先通過override做校驗,如果沒有重載該權(quán)限,則需要校驗訪問權(quán)限
public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
return getFieldAccessor(obj).get(obj);
}
下面我們看看如何通過反射修改Field里面屬性的值
通過上面的代碼,我們可以看出jdk將Field屬性的讀取和寫入委托給FieldAccessor,那么如何獲取FieldAccessor呢
class UnsafeFieldAccessorFactory {
UnsafeFieldAccessorFactory() {
}
static FieldAccessor newFieldAccessor(Field var0, boolean var1) {
Class var2 = var0.getType();
boolean var3 = Modifier.isStatic(var0.getModifiers());
boolean var4 = Modifier.isFinal(var0.getModifiers());
boolean var5 = Modifier.isVolatile(var0.getModifiers());
boolean var6 = var4 || var5;
boolean var7 = var4 && (var3 || !var1);
if (var3) {
UnsafeFieldAccessorImpl.unsafe.ensureClassInitialized(var0.getDeclaringClass());
return (FieldAccessor) ((!var6)
? ((var2 == Boolean.TYPE)
? new UnsafeStaticBooleanFieldAccessorImpl(var0)
: ((var2 == Byte.TYPE)
? new UnsafeStaticByteFieldAccessorImpl(var0)
: ((var2 == Short.TYPE)
? new UnsafeStaticShortFieldAccessorImpl(var0)
: ((var2 == Character.TYPE)
? new UnsafeStaticCharacterFieldAccessorImpl(var0)
: ((var2 == Integer.TYPE)
? new UnsafeStaticIntegerFieldAccessorImpl(var0)
: ((var2 == Long.TYPE)
? new UnsafeStaticLongFieldAccessorImpl(var0)
: ((var2 == Float.TYPE)
? new UnsafeStaticFloatFieldAccessorImpl(var0)
: ((var2 == Double.TYPE)
? new UnsafeStaticDoubleFieldAccessorImpl(var0)
: new UnsafeStaticObjectFieldAccessorImpl(var0)))))))))
: ((var2 == Boolean.TYPE)
? new UnsafeQualifiedStaticBooleanFieldAccessorImpl(var0, var7)
: ((var2 == Byte.TYPE)
? new UnsafeQualifiedStaticByteFieldAccessorImpl(var0, var7)
: ((var2 == Short.TYPE)
? new UnsafeQualifiedStaticShortFieldAccessorImpl(var0, var7)
: ((var2 == Character.TYPE)
? new UnsafeQualifiedStaticCharacterFieldAccessorImpl(var0, var7)
: ((var2 == Integer.TYPE)
? new UnsafeQualifiedStaticIntegerFieldAccessorImpl(var0, var7)
: ((var2 == Long.TYPE)
? new UnsafeQualifiedStaticLongFieldAccessorImpl(var0, var7)
: ((var2 == Float.TYPE)
? new UnsafeQualifiedStaticFloatFieldAccessorImpl(var0, var7)
: ((var2 == Double.TYPE)
? new UnsafeQualifiedStaticDoubleFieldAccessorImpl(var0, var7)
: new UnsafeQualifiedStaticObjectFieldAccessorImpl(var0, var7))))))))));
} else {
return (FieldAccessor) ((!var6)
? ((var2 == Boolean.TYPE)
? new UnsafeBooleanFieldAccessorImpl(var0)
: ((var2 == Byte.TYPE) ? new UnsafeByteFieldAccessorImpl(var0)
: ((var2 == Short.TYPE)
? new UnsafeShortFieldAccessorImpl(var0)
: ((var2 == Character.TYPE)
? new UnsafeCharacterFieldAccessorImpl(var0)
: ((var2 == Integer.TYPE)
? new UnsafeIntegerFieldAccessorImpl(var0)
: ((var2 == Long.TYPE) ? new UnsafeLongFieldAccessorImpl(var0)
: ((var2 == Float.TYPE)
? new UnsafeFloatFieldAccessorImpl(var0)
: ((var2 == Double.TYPE) ? new UnsafeDoubleFieldAccessorImpl(var0)
: new UnsafeObjectFieldAccessorImpl(var0)))))))))
: ((var2 == Boolean.TYPE)
? new UnsafeQualifiedBooleanFieldAccessorImpl(var0, var7)
: ((var2 == Byte.TYPE)
? new UnsafeQualifiedByteFieldAccessorImpl(var0, var7)
: ((var2 == Short.TYPE)
? new UnsafeQualifiedShortFieldAccessorImpl(var0, var7)
: ((var2 == Character.TYPE)
? new UnsafeQualifiedCharacterFieldAccessorImpl(var0, var7)
: ((var2 == Integer.TYPE)
? new UnsafeQualifiedIntegerFieldAccessorImpl(var0, var7)
: ((var2 == Long.TYPE)
? new UnsafeQualifiedLongFieldAccessorImpl(var0, var7)
: ((var2 == Float.TYPE)
? new UnsafeQualifiedFloatFieldAccessorImpl(var0, var7)
: ((var2 == Double.TYPE)
? new UnsafeQualifiedDoubleFieldAccessorImpl(var0, var7)
: new UnsafeQualifiedObjectFieldAccessorImpl(var0, var7))))))))));
}
}
}
以上代碼可以發(fā)現(xiàn),通過工廠模式根據(jù)field屬性類型以及是否靜態(tài)來獲取,為什么會有這樣的劃分呢
首先,jdk是通過UNSAFE類對堆內(nèi)存中對象的屬性進行直接的讀取和寫入,要讀取和寫入首先需要確定屬性所在的位置,也就是相對對象起始位置的偏移量,而靜態(tài)屬性是針對類的不是每個對象實例一份,所以靜態(tài)屬性的偏移量需要單獨獲取
其實通過該偏移量我們可以大致推斷出一個實例內(nèi)每個屬性在堆內(nèi)存的相對位置,以及分別占用多大的空間,有了位置信息,我們還需要這個字段的類型,以方便執(zhí)行器知道讀幾個字節(jié)的數(shù)據(jù),并且如何進行解析,目前提供了8大基礎(chǔ)類型(char vs Charector)和數(shù)組和普通引用類型。 java虛擬機為了保證每個對象所占的空間都是8個字節(jié)倍數(shù),有時候為了避免兩個volatile字段存放在同一個緩存行,所以有時候會再某些字段上做空位填充
以下為UnSafe類的部分代碼
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
public native int getInt(Object var1, long var2);
public native void putInt(Object var1, long var2, int var4);
public native Object getObject(Object var1, long var2);
public native void putObject(Object var1, long var2, Object var4);
public native boolean getBoolean(Object var1, long var2);
public native void putBoolean(Object var1, long var2, boolean var4);
public native byte getByte(Object var1, long var2);
public native long objectFieldOffset(Field var1);
@Deprecated
public int fieldOffset(Field var1) {
return Modifier.isStatic(var1.getModifiers())?(int)this.staticFieldOffset(var1):(int)this.objectFieldOffset(var1);
}
然后我們在來看看通過反射來調(diào)用方法
同樣,jdk通過MethodAccessor來進行method的調(diào)用,java虛擬機提供了兩種模式來支持method的調(diào)用 一個是NativeMethodAccessorImpl 一個是通過ASM字節(jié)碼直接動態(tài)生成一個類在invoke方法內(nèi)部調(diào)用目標(biāo)方法,由于是動態(tài)生成所以jdk中沒有其源碼,但jdk提供了DelegatingMethodAccessorImpl委派模式以方便在運行過程中可以動態(tài)切換字節(jié)碼模式和native模式,我們可以看下生成MethodAccessor的代碼
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
NativeMethodAccessorImpl(Method var1) {
this.method = var1;
}
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(var3);
}
return invoke0(this.method, var1, var2);
}
void setParent(DelegatingMethodAccessorImpl var1) {
this.parent = var1;
}
private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
可以看到JDK內(nèi)部通過numInvocations判斷如果該反射調(diào)用次數(shù)超過ReflectionFactory.inflationThreshold()則用字節(jié)碼實現(xiàn),如果小于該值則采用native實現(xiàn),native的調(diào)用比字節(jié)碼方式慢很多, 動態(tài)實現(xiàn)和本地實現(xiàn)相比執(zhí)行效率要快20倍,因為動態(tài)實現(xiàn)無需經(jīng)過JAVA,C++再到JAVA的轉(zhuǎn)換,之前在jdk6以前有個工具ReflectAsm就是采用這種方式提升執(zhí)行效率,不過在jdk8以后,也提供了字節(jié)碼方式,由于許多反射只需要執(zhí)行一次,然而動態(tài)方式生成字節(jié)碼十分耗時,所以jdk提供了一個閾值默認15,當(dāng)某個反射的調(diào)用次數(shù)小于15的話就走本地實現(xiàn),大于15則走動態(tài)模式,而這個閾值可以在jdk啟動參數(shù)里面做配置
反射為什么慢
經(jīng)過以上優(yōu)化,其實反射的效率并不慢,在某些情況下可能達到和直接調(diào)用基本相同的效率,但是在首次執(zhí)行或者沒有緩存的情況下還是會有性能上的開銷,主要在以下方面
Class.forName();會調(diào)用本地方法,我們用到的method和field都會在此時加載進來,雖然會進行緩存,但是本地方法免不了有JAVA到C+=在到JAVA得轉(zhuǎn)換開銷
class.getMethod(),會遍歷該class所有的公用方法,如果沒匹配到還會遍歷父類的所有方法,并且getMethods()方法會返回結(jié)果的一份拷貝,所以該操作不僅消耗CPU還消耗堆內(nèi)存,在熱點代碼中應(yīng)該盡量避免,或者進行緩存
invoke參數(shù)是一個object數(shù)組,而object數(shù)組不支持java基礎(chǔ)類型,而自動裝箱也是很耗時的
反射的運用
spring ioc
spring加載bean的流程基本都用到了反射機制
獲取類的實例 通過構(gòu)造方法getInstance(靜態(tài)變量初始化,屬性賦值,構(gòu)造方法)
如果實現(xiàn)了BeanNameAware接口,則用反射注入bean賦值給屬性
如果實現(xiàn)了BeanFactoryAware接口,則設(shè)置 beanFactory
如果實現(xiàn)了ApplicationContextAware,則設(shè)置ApplicationContext
調(diào)用BeanPostProcesser的預(yù)先初始化方法
如果實現(xiàn)了InitializingBean,調(diào)用AfterPropertySet方法
調(diào)用定制的 init-method()方法 對應(yīng)的直接 @PostConstruct
調(diào)用BeanPostProcesser的后置初始化完畢的方法
序列化
fastjson可以參考ObjectDeserializer的幾個實現(xiàn) JavaBeanDeserializer和ASMJavaBeanDeserializer
動態(tài)代理
jdk提供了一個工具類來動態(tài)生成一個代理,允許在執(zhí)行某一個方法時進行額外的處理
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
class HWInvocationHandler implements InvocationHandler{
//目標(biāo)對象
private Object target;
public HWInvocationHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------插入前置通知代碼-------------");
//執(zhí)行相應(yīng)的目標(biāo)方法
Object rs = method.invoke(target,args);
System.out.println("------插入后置處理代碼-------------");
return rs;
}
}
我們分析下這個方法的實現(xiàn),首先生成的代理對象,需要實現(xiàn)參數(shù)里面聲明的所有接口,接口的實現(xiàn)應(yīng)給委托給InvocationHandler進行處理,invocationHandler里面可以根據(jù)method聲明判斷是否需要做增強,所以所生成的代理類里面必須能夠獲取到InvocationHandler,在我們無法知道代理類的具體類型的時候,我們可以通過反射從構(gòu)造方法里將InvocationHandler傳給代理類的實例 所以 總的來說生成代理對象需要兩步
獲取代理類的class對象
通過class對象獲取構(gòu)造方法,通過反射生成代理類的實例,并將InvocationHandler傳人
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
* 生成代理類
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
//獲取代理類的構(gòu)造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//獲取代理類的實例,并且將invocationhandler傳人
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
...
}
}
下面我們在看下 getProxyClass0 如何獲取代理類的class對象,這里idk通過WeakCache來緩存已經(jīng)生成的class對象,因為生成該class通過字節(jié)碼生成還是很耗時,同時為了解決之前由于動態(tài)代理生成太多class對象導(dǎo)致內(nèi)存不足,所以這里通過弱引用WeakReference來緩存所生成的代理對象class,當(dāng)發(fā)生GC的時候如果該class對象沒有其他的強引用將會被直接回收 生成代理類的class在ProxyGenerator的generateProxyClass方法內(nèi)實現(xiàn),該方法返回一個byte[]數(shù)組,最后通過一個本地方法加載到虛擬機,所以可以看出生成該對象還是非常耗時的
//生成字節(jié)碼數(shù)組
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//加載進虛擬機
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
private byte[] generateClassFile() {
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
Class[] var1 = this.interfaces;
int var2 = var1.length;
int var3;
Class var4;
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
Method[] var5 = var4.getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
this.addProxyMethod(var8, var4);
}
}
this.methods.add(this.generateConstructor());
...
}
//生成一個帶invocationhandler參數(shù)的構(gòu)造方法
private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
DataOutputStream var2 = new DataOutputStream(var1.code);
this.code_aload(0, var2);
this.code_aload(1, var2);
var2.writeByte(183);
var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
var2.writeByte(177);
var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];
return var1;
}
上面的流程可以簡單歸納為
增加hashcode,equals,toString方法
增加所有接口中聲明的未實現(xiàn)方法
增加一個方法參數(shù)為java/lang/reflect/InvocationHandler的構(gòu)造方法
其他靜態(tài)初始化數(shù)據(jù)
動態(tài)代理的應(yīng)用
spring-aop
spring aop默認基于jdk動態(tài)代理來實現(xiàn),我們來看下下面這個經(jīng)典的面試問題
一個類里面,兩個方法A和方法B,方法B上有加注解做事物增強,那么A調(diào)用this.B為什么沒有事物效果?
因為spring-aop默認基于jdk的動態(tài)代理實現(xiàn),最終執(zhí)行是通過生成的代理對象的,而代理對象執(zhí)行A方法和B方法其實是調(diào)用的InvocationHandler里面的增強后的方法,其中B方法是經(jīng)過InvocationHandler做增強在方法前后增加了事物開啟和提交的代碼,而真正執(zhí)行代碼是通過methodB.invoke(原始對象) 而A方法的實現(xiàn)內(nèi)部雖然包含了this.B方法 但其實是調(diào)用了methodA.invoke(原始對象),而這一句代碼相當(dāng)于調(diào)用的是原始對象的methodA方法,而這里面的this.B()方法其實是調(diào)用的原始對象的B方法,沒有進行過事物增強,而如果是通過cglib做字節(jié)碼增強,生成這個類的子類,這種調(diào)用this.B方法是有事物效果的
rpc consumer
有過RMI開發(fā)經(jīng)驗的人可能會很熟悉,為什么在對外export rmi服務(wù)的時候會分別在client和server生成兩個stub文件,其中client的文件其實就是用動態(tài)代理生成了一個代理類 這個代理類,實現(xiàn)了所要對外提供服務(wù)的所有接口,每個方法的實現(xiàn)其實就是將接口信息,方法聲明,參數(shù),返回值信息通過網(wǎng)絡(luò)發(fā)給服務(wù)端,而服務(wù)端收到請求后通過找到對應(yīng)的實現(xiàn)然后用反射method.invoke進行調(diào)用,然后將結(jié)果返回給客戶端
其實其他的RPC框架的實現(xiàn)方式大致和這個類似,只是客戶端的代理類,可能不僅要將方法聲明通過網(wǎng)絡(luò)傳輸給服務(wù)提供方,也可以做一下服務(wù)路由,負載均衡,以及傳輸一些額外的attachment數(shù)據(jù)給provider
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。