Java反射実現原理分析
25887 ワード
一、反射の使い方
1、Class反射クラスの取得方法
(1)getClassメソッドで:
Proxy proxy = new ProxyImpl();
Class proxyClazz = proxy.getClass();
(2)Classを通過する.forNameメソッド
Proxy proxy = new ProxyImpl();
Class proxyClazz = Class.forName("com.dh.yjt.SpringBootDemo.test.Reflect.Proxy");
(3)通過する.class
Proxy proxy = new ProxyImpl();
Class proxyClazz = Proxy.class;
2、タイプ情報の取得
反射の大きな利点は、実行中にオブジェクトのタイプ情報を取得できることです.たとえば、再実行中にオブジェクトメソッド情報を取得し、このメソッドを実行する必要がある場合は、次のようにします.
まず、実装クラスのメソッドが異なるレベルで表示されるインタフェースとその実装クラスを作成します.
class ProxyImpl implements Proxy{
public void run(){
System.out.println("run");
}
public void publicFun(){
System.out.println("publicFun");
}
private void privateFun(){
System.out.println("privateFun");
}
void packageFun(){
System.out.println("packageFun");
}
protected void protectedFun(){
System.out.println("protectedFun");
}
}
interface Proxy{
public void run();
}
次に、反射によってオブジェクトのメソッドを取得し、次のことを実行できます.
@Test
public void fun() throws Exception {
Proxy proxy = new ProxyImpl();
proxy.run();
callHiddenMethod(proxy,"publicFun");
callHiddenMethod(proxy,"privateFun");
callHiddenMethod(proxy,"packageFun");
callHiddenMethod(proxy,"protectedFun");
}
void callHiddenMethod(Object a, String methodName) throws Exception {
Method g = a.getClass().getDeclaredMethod(methodName);//
g.setAccessible(true);//
g.invoke(a);// ,
}
出力結果:
run
publicFun
privateFun
packageFun
protectedFun
反射によってオブジェクトのすべての可視性を実行できる方法がわかります.
二、反射実現原理
1、Methodオブジェクトの取得
上記の例から分かるように、
Class
クラスを呼び出すgetDeclaredMethod
は、指定されたメソッド名およびパラメータのメソッドオブジェクトMethod:
を取得することができる @CallerSensitive
public Method getDeclaredMethod(String name, Class>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
ここで、
privateGetDeclaredMethods
メソッドは、キャッシュまたはJVMから宣言されたメソッドのリストを取得し、Class
メソッドは、返されるメソッドのリストから名前とパラメータに一致するメソッドオブジェクトを見つけます. private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
ReflectionData rd = reflectionData();// reflectionData, 、
if (rd != null) {// , reflectionData
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// No cached value available; request value from VM
// , JVM
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
ここで、
searchMethods
の方法は以下のように実現される.private ReflectionData reflectionData() {
SoftReference> reflectionData = this.reflectionData;// Class reflectionData
int classRedefinedCount = this.classRedefinedCount;
ReflectionData rd;
// reflectionData
if (useCaches && reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
// reflectionData
return newReflectionData(reflectionData, classRedefinedCount);
}
reflectionData()
メソッドの実装から分かるように、reflectionData()
オブジェクトはreflectionData
タイプであり、メモリが緊張しているときに回収される可能性があることを示しているが、SoftReference
パラメータで回収のタイミングを制御することもでき、GCが発生すればそれを回収し、-XX:SoftRefLRUPolicyMSPerMB
が回収された後に反射メソッドが実行されると、reflectionData
メソッドでこのようなオブジェクトを再作成するしかない. private ReflectionData newReflectionData(SoftReference> oldReflectionData, int classRedefinedCount) {
if (!useCaches) return null;// ,
while (true) {
ReflectionData rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
// CAS
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// else retry
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
ReflectionDataは次のように定義されています.
private static class ReflectionData {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor[] declaredConstructors;
volatile Constructor[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
まとめ:
newReflectionData
メソッドでは、privateGetDeclaredMethods
で得られたreflectionData()
オブジェクトが空でなければ、ReflectionData
オブジェクトからReflectionData
プロパティを取得しようと試み、初めて、またはGCで回収された後、再初期化されたクラスプロパティが空であれば、JVMに再取得し、declaredMethods
に値を付与する必要があり、次回の呼び出しでキャッシュデータを使用することができる.そして
ReflectionData
メソッドは、返されるメソッドのリストから、名前とパラメータに一致するメソッドオブジェクトを見つけます. private static Method searchMethods(Method[] methods, String name, Class>[] parameterTypes) {
Method res = null;
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}
return (res == null ? res : getReflectionFactory().copyMethod(res));
}
一致する
searchMethods
が見つかった場合、Method
メソッドに戻ります. Method copy() {
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Method");
Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
res.root = this;
// Might as well eagerly propagate this if already present
res.methodAccessor = methodAccessor;
return res;
}
ピット1防止:コードから分かるように、
Method.copy()
メソッドを呼び出すたびに返されるgetDeclaredMethod
オブジェクトは、実際には新しいオブジェクトであり、新しいオブジェクトのMethod
属性は、コピー前のroot
オブジェクトを指し、頻繁に呼び出す必要がある場合は、Method
オブジェクトをキャッシュすることが望ましい.2、methodを実行する
指定されたメソッドオブジェクト
Method
を取得すると、そのMethod
メソッドを呼び出すことができ、invoke
は以下のように実現される. public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
ここでの
invoke
オブジェクトはMethodAccessor
メソッドの実装の鍵であり、最初はinvoke
が空であり、methodAccessor
を呼び出して新しいacquireMethodAccessor
オブジェクトを生成する必要があり、MethodAccessor
自体がインタフェースであり、以下のように実現される. private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
MethodAccessor
メソッドでは、acquireMethodAccessor
クラスのReflectionFactory
によって、主にnewMethodAccessor
インタフェースを実現するオブジェクトが作成され、以下のように実現される. public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;
}
}
MethodAccessor
クラスには、2つの重要なフィールドがあります.ReflectionFactory
(デフォルトnoInflation
)とfalse
(デフォルト15)です.inflationThreshold
メソッドでは、checkInitted
と-Dsun.reflect.inflationThreshold=xxx
でこの2つのフィールドを再設定でき、一度だけ設定できます.-Dsun.reflect.noInflation=true
がnoInflation
である場合、方法false
はいずれもnewMethodAccessor
オブジェクトを返し、DelegatingMethodAccessorImpl
のクラス実装:class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;
DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
this.setDelegate(var1);
}
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
return this.delegate.invoke(var1, var2);
}
void setDelegate(MethodAccessorImpl var1) {
this.delegate = var1;
}
}
実は、
DelegatingMethodAccessorImpl
オブジェクトは、被エージェントの呼び出しを担当するエージェントオブジェクトです.DelegatingMethodAccessorImpl
delegate
メソッドinvoke
パラメータは現在delegate
オブジェクトなので、最終的にはNativeMethodAccessorImpl
Method
メソッドで呼び出されたのはinvoke
オブジェクトNativeMethodAccessorImpl
の方法は、以下のように実現される. 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);
}
ここでは
invoke
クラスのReflectionFactory
が用いられ、inflationThreshold
が15回のdelegate
メソッドを呼び出した後、呼び出しを継続するとinvoke
クラスのMethodAccessorGenerator
メソッドによりgenerateMethod
オブジェクトが生成され、MethodAccessorImpl
オブジェクトに設定され、次にdelegate
が実行されると、新しく作成されたMethod.invoke
オブジェクトのMethodAccessor
メソッドが呼び出される.踏み込み防止2:Methodのinvokeメソッドを複数回実行すると、15回を超えるごとに新しい
invoke()
オブジェクトが作成されるため、メモリが増加します.参照先:
1、Java反射メカニズムの応用実践http://www.importnew.com/24042.html
2、Java方法の反射の実現原理を深く分析するhttps://www.jianshu.com/p/3ea4a6b57f87
3、偽の愚かな説-一緒にGCの血事件から反射原理について話しますhttps://mp.weixin.qq.com/s/5H6UHcP6kvR2X5hTj_SBjA?
転載先:https://www.cnblogs.com/aiqiqi/p/10691116.html