如何使用反射
在开发过程中我们经常会使用到反射,反射使用起来也很简单,在java runtime下通过方法名和参数就可以反射获取到相应的方法,并通过方法实例的invoke方法调用这个反射出来的方法。当然也可以通过全路径类名反射获取到类实例,这对于我们开发一些中间件或者框架是非常有用的,这样就实现了很多动态的黑科技。
public class ReflectTest {
public static void main(String[] args) throws Exception {
Proxy target = new Proxy();
Method method = Proxy.class.getDeclaredMethod("hello");
method.invoke(target);
Student student = Class.forName("com.monkey01.Student");
student.run();
}
static class Proxy {
public void hello() {
System.out.println("hello world!");
}
}
}
反射实现源码分析
getDeclaredMethod
反射获取类方法是通过getDeclaredMethod()方法来实现的,这个方法的入参为name方法名,parameterTypes参数类型,参数类型是可变参数。
getDeclaredMethod实现中首先检查反射的方法是否是声明为可访问类型,对于private的方法无法反射访问,会抛出异常。先通过调用privateGetDeclaredMethods方法获取到所有的方法列表,再将获取到的方法列表作为searchMethods方法的入参,进行方法查找。
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;
}
privateGetDeclaredFields
privateGetDeclaredFields方法为Class类的内部方法,通过reflectionData()方法从JVM或者缓存中获取方法列表。
private Field[] privateGetDeclaredFields(boolean publicOnly) {
checkInitted();
Field[] res;
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;
if (res != null) return res;
}
// No cached value available; request value from VM
res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicFields = res;
} else {
rd.declaredFields = res;
}
}
return res;
}
先从缓存中获取方法列表,如果没有泽通过newReflectionData方法创建新的反射对象,所有的反射对象都继承自ReflectionData类。
// Lazily create and cache 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这个内部类,ReflectionData是用来缓存从JVM里读取类的属性数据。ReflectionData对象是SoftReference类型的,因为软引用类型在VM资源紧张的时候会被自动回收,所以在使用的时候还是要注意下。
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;
}
}
searchMethods
searchMethods方法实现比较简单就是根据方法名称和入参类型进行筛选,返回符合条件的一个方法对象,如果找到一个Method则copy一个返回。
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));
}
copyMethod
getReflectionFactory的copyMethod底层调用了Method的copy方法。
public Method copyMethod(Method arg) {
return arg.copy();
}
copy
每次调用getDeclaredMethod方法返回的Method对象都是copy方法new的一个新对象,并且对象的root属性都指向原来的Method对象,所以如果要经常调用,最好把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;
}
invoke
从Method的invoke方法源码可以发现,通过Method对象调用方法的时候核心是通过Method对象里的methodAccessor对象去调用具体方法。
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是个接口有NativeMethodAccessorImpl、DelegatingMethodAccessorImpl、MethodAccessorImpl 3个实现,下面我们看下最核心的NativeMethodAccessorImpl,其他几种实现最终都会到原生方法访问实现类中的invoke方法,invoke底层其实和forName一样会通过jni去调用.dll动态链接库的方法invoke0来实现方法的调用,native其实是在JVM的栈中找到这个方法,然后通过方法实例根据参数进行方法运行。
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);
}
forName
forName方法也是我们使用反射时候经常用到的方法,通过forName可以反射动态反射获取到对象实例,forName也有3个重载方法,可以使用JVM默认的classLoader加载类,也可以通过自定义的ClassLoader去加载类。可以看到每个重载方法最后都会调用到forName0方法,这个方法也是native方法,是用jni去调用.dll动态链接库的方法,所以通过java反编译工具是看不到具体源码的,需要下载源码包才能看到。native方法底层实现了在JVM中通过指定的classloader类加载器根据类的全路径去找到已经编译好的class并去动态创建这个类的实例。
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
总结
反射其实看起来比较神奇,但是看了源码后会发现其实内部不是特别复杂,不过在我们写的中间件和框架中经常会用到这个黑科技,包括很多其他语言也有类似运行时反射的机制,这也是一种比较灵活和方便模块解耦的方法,我们在工作中如果遇到需要模块解耦或者需要动态创建类的地方可以考虑采用反射机制来实现相关功能。
网友评论