Java 反射相关整理
Posted windy杨树
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 反射相关整理相关的知识,希望对你有一定的参考价值。
1. Class 类
- Class 是一个类,封装了当前对象所对应的类的信息,一个类中有属性,方法,构造器等。
- 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
- Class 对象只能由系统建立对象,一个类(而不是一个对象)在 Java 虚拟机中只会有一个 Class 实例。
- Class 对象的由来是将 class 文件读入内存,并为之创建一个 Class 对象。
获取 Class 类对象的三种方法
- 使用 Class.forName 静态方法,当知道类的全路径名时,可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
- 使用 .class 方法,这种方法适合在编译前就知道操作的 Class。
Class clz = String.class;
- 使用类对象的
getClass()
方法。
String str = new String("Hello"); Class clz = str.getClass();
Class 类的常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象。 |
Object newInstance() | 调用缺省构造函数,返回该 Class 对象的一个实例。 |
getName() | 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 |
Class getSuperClass() | 返回当前 Class 对象的父类 Class 对象。 |
Class [] getInterfaces() | 获取当前 Class 对象的接口。 |
ClassLoader getClassLoader() | 返回该类的类加载器。 |
2. Java 反射(Reflection)
- Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
- 反射是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。
- 反射机制就是可以把一个类、类的成员(函数,属性),当成一个对象来操作。
- Java 反射主要提供以下功能。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法)。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
2.1 通过反射访问构造函数
- 为了能够动态获取对象构造方法的信息,需要创建一个 Constructor 类型的对象或者数组。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
- 创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。
Constructor 对象方法名称 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false。 |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型。 |
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型。 |
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示采用默认无参的构造方法。 |
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对象。 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数。 |
public void testConstructor() throws Exception{ String className = "com.java.test.Person"; Class<Person> clazz = (Class<Person>) Class.forName(className); // 获取全部 Constructor 对象 Constructor<Person> [] constructors = (Constructor<Person>[]) Class.forName(className).getConstructors(); // 获取某一个,需要参数列表 Constructor<Person> constructor = clazz.getConstructor(String.class, int.class); // 调用构造器的 newInstance() 方法创建对象 Object obj = constructor.newInstance("test", 1); }
Modifier 对象的静态方法名称 | 说明 |
---|---|
isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false。 |
isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false。 |
isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false。 |
isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false。 |
isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false。 |
toString(int mod) | 以字符串形式返回所有修饰符。 |
int modifiers = con.getModifiers(); //获取构造方法的修饰符整数。 boolean isPubiic = Modifier.isPublic(modifiers); //判断修饰符整数是否为 public。 public string ailModifSers = Modifier.toString(modifiers);
2.2 通过反射执行方法(获取方法)
-
要动态获取一个对象方法的信息,需要创建一个 Method 类型的对象或者数组。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>...parameterTypes)
-
如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。
objectCiass.getDeclaredConstructor("max",int.class,String.class); objectClass.getDeclaredConstructor("max",new Ciass[]{int.class,String.class});
Method 对象静态方法名称 | 说明 |
---|---|
getName() | 获取该方法的名称。 |
getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型。 |
getRetumType() | 以 Class 对象的形式获得该方法的返回值类型。 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型。 |
invoke(Object obj,Object...args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型。 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否 则返回 false。 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数。 |
public void testMethod() throws Exception{ Class clazz = Class.forName("com.java.test.Person"); // 获取取 clazz 对应类中的所有方法,但不能获取 private 方法,且获取从父类继承来的所有方法。 Method[] methods = clazz.getMethods(); // 获取所有方法,包括私有方法,获取所有声明的方法,且只获取当前类的方法 methods = clazz.getDeclaredMethods(); // 获取指定的方法,需要参数名称和参数列表,无参则不需要 // 方法 public void setName(String name) Method method = clazz.getDeclaredMethod("setName", String.class); // 方法 public void setAge(int age) // 方法用于反射,只能对应 int.class,不能对应 Integer.class method = clazz.getDeclaredMethod("setAge", int.class); // 执行方法 // invoke 第一个参数表示执行某个对象的方法,剩下的参数是执行方法时需要传入的参数 // 私有方法的执行,必须在调用 invoke 之前加上 method.setAccessible(true); Object obje = clazz.newInstance(); method.invoke(obje,2); }
2.3 通过反射访问成员变量
-
通过方法访问成员变量时将返回 Field 类型的对象或数组。
- getFields()
- getField(String name)
- getDeclaredFields()
- getDeclaredField(String name)
-
返回的 Field 对象代表一个成员变量。
object.getDeciaredField("price");
Field 的方法名称 | 说明 |
---|---|
getName() | 获得该成员变量的名称。 |
getType() | 获取表示该成员变量的 Class 对象。 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型。 |
set(Object obj,Object value) | 将指定对象 obj 中成员变量的值设置为 value。 |
getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值。 |
setlnt(0bject obj,int i) | 将指定对象 obj 中成员变量的值设置为 i。 |
setFloat(Object obj,float f) | 将指定对象 obj 中成员变量的值设置为 f。 |
getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值。 |
setBoolean(Object obj,boolean b) | 将指定对象 obj 中成员变量的值设置为 b。 |
getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值。 |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量。 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数。 |
public void testField() throws Exception{ String className = "com.java.test.Person"; Class clazz = Class.forName(className); // 获取所有字段,可以获取公用和私有的所有字段,但不能获取父类字段 Field[] fields = clazz.getDeclaredFields(); // 获取指定字段 Field field = clazz.getDeclaredField("name"); Person person = new Person("ABC",12); //获取指定对象的指定字段的值 Object val = field.get(person); //设置指定对象的指定对象 Field 值 field.set(person, "DEF"); // 如果字段是私有的,不管是读值还是写值,都必须先调用 setAccessible(true) 方法 field.setAccessible(true); field = clazz.getDeclaredField("age"); }
2.4 通过反射访问注解
- 定义一个注解。
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value={ElementType.METHOD}) public @interface AgeValidator { public int min(); public int max(); }
- 应用于方法上。
@AgeValidator(min=18,max=35) public void setAge(int age) { this.age = age; }
- 通过反射的方式为属性赋值,获取注解。
public void testAnnotation() throws Exception{ String className = "com.java.test.Person"; Class clazz = Class.forName(className); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("setAge", int.class); int val = 6; //获取指定名称的注解 Annotation annotation = method.getAnnotation(AgeValidator.class); if(annotation != null){ if(annotation instanceof AgeValidator){ AgeValidator ageValidator = (AgeValidator) annotation; if(val < ageValidator.min() || val > ageValidator.max()){ throw new RuntimeException("年龄非法"); } } } method.invoke(obj, 20); }
- 如果在程序中需要获取注解,然后根据获取注解的值进而判断赋值是否合法,那么类对象的创建和方法的创建必须通过反射而来。
3. 实现原理
3.1 Method 的获取
- 通过调用 Class 类的
getDeclaredMethod()
方法可以获取指定方法名和参数的方法对象 Method。 - 调用
checkMemberAccess()
方法进行一些权限检查。
@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()
方法从缓存或 Java 虚拟机获取 Class 中申明的方法列表。 - 调用
searchMethods()
方法从返回的方法列表里找到一个匹配名称和参数的方法对象(Method)。
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)); }
- 如果找到一个匹配的方法对象(Method),则重新拷贝一份返回。
- 实际调用了
Method.copy()
方法。
- 实际调用了
Method copy() { // This routine enables sharing of MethodAccessor objects // among Method objects which refer to the same underlying // method in the VM. (All of this contortion is only necessary // because of the "accessibility" bit in AccessibleObject, // which implicitly requires that new java.lang.reflect // objects be fabricated for each reflective call on Class // objects.) 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; }
- 因为每次调用
getDeclaredMethod()
方法返回的方法对象(Method)都是一个新的对象,且新对象的 root 属性都指向原来的 Method 对象,如果频繁的调用,则最好的方式就是将 Method 缓存起来。
privateGetDeclaredMethods 的缓存实现
privateGetDeclaredMethods()
方法会从缓存或 Java 虚拟机获取 Class 中申明的方法列表。
private Method[] privateGetDeclaredMethods(boolean publicOnly) { checkInitted(); Method[] res; ReflectionData<T> rd = reflectionData(); if (rd != null) { res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods; if (res != null) return res; } // No cached value available; request value from VM res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly)); if (rd != null) { if (publicOnly) { rd.declaredPublicMethods = res; } else { rd.declaredMethods = res; } } return res; }
- 延迟创建和缓存 ReflectionData 数据对象。
private ReflectionData<T> reflectionData() { SoftReference<ReflectionData<T>> reflectionData = this.reflectionData; int classRedefinedCount = this.classRedefinedCount; ReflectionData<T> rd; 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 return newReflectionData(reflectionData, classRedefinedCount); }
- ReflectionData 数据对象,用来缓存从 Java 虚拟机中读取类的一些属性数据。
private static class ReflectionData<T> { volatile Field[] declaredFields; volatile Field[] publicFields; volatile Method[] declaredMethods; volatile Method[] publicMethods; volatile Constructor<T>[] declaredConstructors; volatile Constructor<T>[] 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; } }
- ReflectionData 数据对象实现为 SoftReference 类型,在内存紧张时可能会被回收,也可以通过 -XX:SoftRefLRUPolicyMSPerMB 参数控制回收时机,发生 GC 就将其回收。
- 如果 ReflectionData 数据对象被回收后又执行了反射方法,那只能通过
newReflectionData()
方法重新创建一个新的 ReflectionData 对象。
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData, int classRedefinedCount) { if (!useCaches) return null; while (true) { ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount); // try to CAS it... 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; } } }
- 通过
unsafe.compareAndSwapObject()
方法重新设置 reflectionData 字段。
reflectionDataOffset = objectFieldOffset(fields, "reflectionData"); ...... static <T> boolean casReflectionData(Class<?> clazz, SoftReference<ReflectionData<T>> oldData, SoftReference<ReflectionData<T>> newData) { return unsafe.compareAndSwapObject(clazz, reflectionDataOffset, oldData, newData); }
privateGetDeclaredMethods()
方法中如果通过reflectionData()
方法获得的 ReflectionData 数据对象不为空,则尝试从 ReflectionData 数据对象中获取declaredMethods
属性。
if (rd != null) { res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods; if (res != null) return res; }
- 如果是第一次创建 ReflectionData 数据对象或者 ReflectionData 对象已被 GC 回收过,重新初始化后的类属性为空,则需要重新到 Java 虚拟机中获取一次,并赋值给 ReflectionData,便于下次调用使用缓存数据。
// No cached value available; request value from VM res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly)); if (rd != null) { if (publicOnly) { rd.declaredPublicMethods = res; } else { rd.declaredMethods = res; } }
3.2 Method 的调用
- 获取到指定的方法对象(Method)后,调用执行
invoke()
方法。 - 调用
quickCheckMemberAccess()
方法进行一些权限检查。
@CallerSensitive 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); }
- 实际最终执行的是 MethodAccessor 对象的
invoke()
方法。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 对象,如果存在则复用,否则调用 ReflectionFactory 对象的
newMethodAccessor()
方法生成一个 MethodAccessor 对象。- 其中 noInflation 为关闭 Inflation 机制,可通过 -Dsun.reflect.noInflation=true 设置。
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; } }
- 如果使用了 Inflation 机制(noInflation = false),那么这里的实现使用了代理模式,将 NativeMethodAccessorImpl 对象交给 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; } }
- 那么 MethodAccessor 调用的
invoke()
方法,实际调用了 DelegatingMethodAccessorImpl 对象的invoke()
方法,最终调用了被代理的 NativeMethodAccessorImpl 对象的invoke()
方法。- NativeMethodAccessorImpl 对象的
invoke()
方法中会判断调用次数是否超过阀值(numInvocations)。 - 如果超过该阀值,会生成另外一个 MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。
- numInvocations 默认为 15,可以通过 -Dsun.reflect.inflationThreshold= 设置次数。
- NativeMethodAccessorImpl 对象的
private static int inflationThreshold = 15; ...... 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); }
- 实际的 MethodAccessor 接口实现有两个版本,一个是 Native 版本,一个是 Java 版本。
- Native 版本的实现是 NativeMethodAccessorImpl 对象。
- Java 版本的实现是 MethodAccessorImpl。
Inflation 机制
- 初次加载字节码实现反射,使用
Method.invoke()
和Constructor.newInstance()
加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍,这使得那些频繁使用反射的应用需要花费更长的启动时间。 - 为了避免这种痛苦的加载时间,第一次加载的时候重用了 Java 虚拟机的入口,之后再切换到字节码实现的实现。
- Native 版本一开始启动很快,随着运行时间变长,速度变慢。
- Java 版本一开始加载慢,随着运行时间变长,速度变快。
- 所以当第一次加载时会发现使用的是 NativeMethodAccessorImpl 对象的实现,当反射调用次数超过 15(numInvocations) 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。
generateMethod()
方法在生成 MethodAccessorImpl 对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass()
创建对应的 class 对象。
return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() { public MagicAccessorImpl run() { try { return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance(); } catch (IllegalAccessException | InstantiationException var2) { throw new InternalError(var2); } } });
- 在
ClassDefiner.defineClass()
方法实现中,每被调用一次都会生成一个 DelegatingClassLoader 类加载器对象。- 每次生成新的类加载器是为了性能考虑(从设计来看,本身不希望这些类一直存在于内存当中,在需要的时候存在即可)。
- 在某些情况下可以直接卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果使用原来的类加载器,可能导致这些新创建的类一直无法被卸载。
- 每次生成新的类加载器是为了性能考虑(从设计来看,本身不希望这些类一直存在于内存当中,在需要的时候存在即可)。
static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) { ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { public ClassLoader run() { return new DelegatingClassLoader(var4); } }); return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null); }
invoke()
方法的内部使用了代理的设计模式来实现最大化性能。
参考资料
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
http://www.importnew.com/23902.html
http://www.importnew.com/23560.html
http://www.importnew.com/21211.html
以上是关于Java 反射相关整理的主要内容,如果未能解决你的问题,请参考以下文章