一般来说能设置类加载器的地方只有Thread.currentThread().setContextClassLoader(classloader);如果想在默认类的类加载器中修改自定义的类加载器应该怎么做呢?
首先Class中没有能修改ClassLoader的地方,所以只能用其他方法修改
在Class的第694行发现有classloader,我们只需要获取并改写就行了
Field field = Class.class.getDeclaredField("classloader");
field.setAccessible(true);
System.out.println(field);
看起来应该没问题,但是运行后发现报错了,找不到这个字段,但是在Class很明显能看到这个字段是存在,看来这个字段的设定很特殊,不能获取,但是这时就放弃就没意义了,跟着getDeclaredField方法追踪代码时发现2574行的代码中有一个函数非常特别。
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;
}
可以看到有一个方法是filterField,从名字就能看出是过滤字段,所以非常显然,这里有一个会过滤字段的存在,进入Reflection代码,可以看到两个非常显然的两个字段fieldFilterMap和methodFilterMap,见名知意,保存过滤的字段,可以试试把这两个字段取出来看看
Field field = Reflection.class.getDeclaredField("fieldFilterMap");
field.setAccessible(true);
System.out.println(field);
可惜,这个字段也是不能取的,原本想的是取出来清空就好了,也许还有其他方法
Class中的getDeclaredFields0会返回字段数组,传入的参数是publicOnly,有点类似选择是否是public的形式,可以用反射得到这个函数,然后运行试试
Method gdf = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
gdf.setAccessible(true);
//传入想要获取的类的class
Field[] fields= (Field[]) gdf.invoke(Class.class, false);
for (Field field : fields) {
System.out.println(field);
}
从运行结果可以看到一个字段classloader,这就是我们想要修改的,既然得到了,我们就可以把这个字段单独取出,然后修改
URLClassLoader ucl = (URLClassLoader) TestRun.class.getClassLoader();
TestClassLoader tcl = new TestClassLoader(ucl.getURLs());
for (Field field : fs) {
if(field.getName().equals("classLoader")) {
field.setAccessible(true);
field.set(TestRun.class, tcl);
break;
}
}
现在来做个测试
TestClassLoader:
import java.net.URL;
import java.net.URLClassLoader;
public class TestClassLoader extends URLClassLoader{
public TestClassLoader(URL[] urls) {
super(urls,null);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("Get a Class:"+name);
return super.findClass(name);
}
public void addURL(URL url) {
super.addURL(url);
}
}
TestRun:
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import sun.reflect.Reflection;
public class TestRun {
public static void main(String[] args) {
try {//没修改classloader
Class<?> clazz = Class.forName("TestClass");
System.out.println("find class");
} catch (ClassNotFoundException e) {
System.err.println("not find class");
}
URLClassLoader ucl = (URLClassLoader) TestRun.class.getClassLoader();
TestClassLoader tcl = new TestClassLoader(ucl.getURLs());
try {
//Class类中的2583行代码的模仿
Method gdf = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
gdf.setAccessible(true);
Field[] fs = (Field[]) gdf.invoke(Class.class, false);
for (Field field : fs) {
if(field.getName().equals("classLoader")) {
field.setAccessible(true);
field.set(TestRun.class, tcl);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
tcl.addURL(new File("testclass").toURI().toURL());
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
try {//修改类加载器后的结果
Class<?> clazz = Class.forName("TestClass");
System.out.println("find class");
} catch (ClassNotFoundException e) {
System.err.println("not find class");
}
}
}
运行结果:
not find class
Get a Class:TestClass
find class
可以很明显的看到,类被发现了,而且是被Class加载的