什么是反射?
反射可以在运行时动态加载类,创建对象。这种类和对象在编译期间都是不确定的,只有在运行到对应的反射代码才能确定下来。反射还可以获取对象自身的信息,包括属性、方法等,并且可以打破访问修饰符的限制,设置并访问私有的属性,获取并调用私有的方法。
通过反射加载类
反射,最刚开始接触其实是在jdbc的学习中。
看下面获取mysql连接的代码
//反射加载mysql驱动
Class.forName(“com.mysql.jdbc.Driver”);
String url = “jdbc:mysql://localhost:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url);
Class.forName用来加载com.mysql.jdbc.Driver这个驱动类,并且返回Class对象,但是这里并没有去接收它的返回值,那么DriverMannger是如何得到这个驱动类的呢?
看下com.mysql.jdbc.Driver的实现就知道了
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
可以看到mysql驱动类中有这一段静态块,在加载驱动类时,初始化的阶段会执行静态块里的代码,加载驱动类到DriverManager.
此外对于Class.forName需要了解一点就是,通过调用方的类加载器反射加载该类。
通过反射创建对象
Class clazz = Class.forName("com.domain.Person");
//创建对象,注意必须要有空构造器
Object obj = clazz.newInstance();
//两种方法判断某个对象时某一个类的对象
System.out.println(obj instanceof Person);
System.out.println(clazz.isInstance(obj));
通过反射获取对象的域
Class clazz = Class.forName("com.domain.Person");
Object obj = clazz.newInstance();
//可获取到私有域
//getField只可获得公开域
Field field = clazz.getDeclaredField("name");
//私有域 可以设置为可以访问,打破访问修饰符的访问控制权限
field.setAccessible(true);
//为obj的name属性设置值 yang
field.set(obj,"yang");
//获取输出
System.out.println(field.get(obj));
//静态域
Field field1 = clazz.getField("type");
field1.set(clazz,"人");
System.out.println(field1.get(clazz));
通过反射调用对象的方法
//获取方法,可以获取到私有方法
Method method = clazz.getDeclaredMethod("show",int.class);
// 私有方法设置可访问,打破访问修饰符的访问权限控制
// method.setAccessible(true);
//执行方法
method.invoke(obj,1);
//静态方法
Method method1 = clazz.getDeclaredMethod("say",String.class);
method1.invoke(clazz,"hello");
method深入
1、method的get方法
//这个方法读取缓存找那个的 method对象,通过public only来过滤私有和公开方法
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;
}
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
reflectionData是软引用对象,在内存不足时会被回收,如果被回收了在需要用到他,又需要从jvm中取一把。
把从缓存中取出并过滤好的方法数组拿出来,按要求的方法签名去找对应方法
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;
}
//注意下面这个判断,不要看反了,
//这里是说,如果没有找到这个方法,直接返回出去null。否则就copy这个方法返回出去,
//所以由此可知,每次取method,得到的都不是同一个对象,所以能复用method就尽量复用
return (res == null ? res : getReflectionFactory().copyMethod(res));
}
ok,看到这里,我试了一下,取两次method,然后输出hashCode一看,,居然一样,不信邪,又来几次,还是一样,一度怀疑人生。
后来仔细一看才发现method重写了hashCode,它的hashCode跟他的签名有关,跟地址无关。
所以,判断两个对象是否是同一个对象,坚决不能用hashCode。
要用==,
没错用==就明显对了。
2、method的invoke方法
invoke最终调用到MethodAccessor 的invoke
public interface MethodAccessor {
Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}
method里有个root对象,也就是缓存中的method,也就是这个method是通过root复制出来的,如果root里有MethodAccessor ,就直接使用,否则就需要创建。
MethodAccessor 有三个实现类:
DelegatingMethodAccessorImpl
NativeMethodAccessorImpl
GeneratedMethodAccessorXXX
其中DelegatingMethodAccessorImpl是最终注入到method的MethodAccessor中的,也就是最终要调用到DelegatingMethodAccessorImpl的invoke,DelegatingMethodAccessorImpl作为一个代理类存在,委托执行NativeMethodAccessorImpl 或者 GeneratedMethodAccessorXXX【反射生成】的invoke
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;
}
}
ok,现在的核心在NativeMethodAccessorImpl
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 {
//在15次之后,设置对应的代理对象为GeneratedMethodAccessorXXX,
//这个反射得到的类中invoke,其实就是普通的方法调用
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);
}
//在方法执行的前15次默认都是调用原生的native方法
return invoke0(this.method, var1, var2);
}
void setParent(DelegatingMethodAccessorImpl var1) {
this.parent = var1;
}
private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
其中我上面说的是15次就是ReflectionFactory.inflationThreshold()这个方法返回的,这个15当然也不是一尘不变的,我们可以通过-Dsun.reflect.inflationThreshold=xxx来指定,我们还可以通过-Dsun.reflect.noInflation=true来直接绕过上面的15次NativeMethodAccessorImpl调用,和-Dsun.reflect.inflationThreshold=0的效果一样的。
为什么要有两种方式去执行方法的调用?
1、原生的在启动时性能较好,时间长了,性能就会变差
2、反射生成类的在启动时性能不好,因为需要去反射生成类,启动一段时间后,性能就很好了。
所以两者进行了结合,先使用原生,一段时间后,使用反射生成类。
并发调用method产生线程安全问题?
在调用15次后,生成反射类来实现调用的方法并没有加锁,也就是并不是线程安全的。假设在外部method的调用层也没有进行锁的控制,那么就会出现一些问题。
假设1000个线程同时进来,反射类,那么就会生成1000个类,虽然最后留下来的就一个类,而且程序也都能正常秩序,但是会有999个类在未被卸载之前会占用内存,在极端情况下就会OOM。
GeneratedMethodAccessorXXX如何卸载?
首先明确两点:
1、GeneratedMethodAccessorXXX 需要被卸载,为什么?
假设GeneratedMethodAccessorXXX 都不被卸载,当程序中大量使用反射调用对象时,很可能因为GeneratedMethodAccessorXXX 的大量生产而导致OOM
2、JVM虚拟机自带的类加载器能不能卸载类?
在JVM运行期间,JVM会持有默认的类加载器,默认的类加载器又会持有他们加载的Class对象,所以这些Class对象都是可以触及的,所以在运行期间不可能卸载类。
那么如何实现GeneratedMethodAccessorXXX 的卸载,最简单的方式就是自定义类加载器去实现GeneratedMethodAccessorXXX 的加载,因为用户自定义的类加载器所加载的类可以被卸载。
比如JSP类加载器,每个JSP对应一个类加载器,当发现有JSP修改,就会卸载原来的类加载器和类,重新创建类加载器,加载修改后的jsp所编译的字节码文件。
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() {
//可以看到GeneratedMethodAccessorXXX 确实使用自定义的类加载器 DelegatingClassLoader
return new DelegatingClassLoader(var4);
}
});
return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
}