我知道这不是一个好的需求。但我现在需要这样做。
我需要替换Class.getResourceAsStream(String)
但是ClassLoader.preDefineClass(String , ProtectionDomain)
检查全名的类名是否以java.
开头,如果是,则抛出异常并且不允许加载。
有没有办法绕过运行时安全检查,而不是编译时间?
真正的要求是许多旧项目需要迁移到新环境,因为其中一些项目可能已过时,其中一些项目可能无法找到源代码。我需要在新环境中模拟旧环境,以便这些古老的项目能够继续发挥作用。
我尝试了你给出的方式。
public class ClassLoaderTest extends ClassLoader {
@Override
public InputStream getResourceAsStream(String name) {
System.out.println("getResourceAsStream " + name);
return new ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("Load class " + name);
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[0];
try {
b = new byte[is.available()];
is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
Class clazz = Class.forName("com.Client", true, classLoaderTest);
Object client = clazz.newInstance();
Method method = clazz.getDeclaredMethod("method");
method.invoke(client);
}
public class Client {
public void method() {
System.out.println(this.getClass().getResourceAsStream("aaa"));
System.out.println(Class.class.getResourceAsStream("bbb"));
System.out.println("".getClass().getResourceAsStream("ccc"));
}
}
它真的适用于this.getClass().getResourceAsStream(String)
,但Class.class.getResourceAsStream(String)
/ "".getClass().getResourceAsStream(String)
。因为我无法使用我的自定义ClassLoader加载类
名称以java.
答案 0 :(得分:3)
你做不到。
此类由bootstrap类加载器加载。你无法改变这个人正在做的事情 - 它是用本地代码编写的。换句话说:这是JVM的核心 - 你不能篡改它。
这就是java安全概念的“整体思路”:用户无法“进入”JVM平台保证的事物。
鉴于OP的编辑,这是关于遗留代码的:似乎你想要挂钩Class.getResourceAsStream(String)
的不是 JVM的一部分 - 但属于您的拥有代码。
如果是这种情况,则“答案”就是:您必须确保所有 类都由您的类加载器加载。 getResourceAsStreeam()的javadoc明确指出:
查找具有给定名称的资源。搜索与给定类关联的资源的规则由类的定义类加载器实现。此方法委托给该对象的类加载器。
因此:学习如何使用自定义类加载器。例如,以here开头。
答案 1 :(得分:2)
尝试定义具有以java.
开头的限定名称的类的异常是the contract of defineClass
的一部分,但这是无关紧要的。任何在ClassLoader
上定义具有现有类名称的类的尝试只能有两种可能的结果之一:
如果现有类已由同一ClassLoader
定义,则尝试再次定义它将被拒绝并抛出异常。 defineClass
不会更改现有的类,这根本不在其功能范围内。
如果现有的类已由不同的ClassLoader
(或引导加载程序)加载,则可以在此ClassLoader
上定义具有相同限定名称的类,但它将是一个完全不同的类,与具有相同名称但不同的定义类加载器的类无关。
您无法为java.*
类定义具有相同名称和不同加载器的类,这只是一个额外的限制,但无论如何您的尝试都无法正常工作。
如果要在加载后替换类ClassLoader
,则只能使用代理,例如使用Instrumentation API的Java代理或调试器。或者,您可以使用-Xbootclasspath/p:…
命令行选项将自己的版本添加到引导类路径中。
当然,将其替换为特定版本仅适用于从中派生此版本的环境。此类应用程序也不允许公开部署。
更通用的解决方案是使用当前版本ClassLoader
的Instrumentation Agent并在运行中对其进行修改,但是,如果您必须选择开发字节码转换代理,那么它会很多更简单,更可靠地转换应用程序代码,只需用调用自定义方法替换Class.getResourceAsStream(String)
的所有调用,而无需触及不需要这样调整的代码。
答案 2 :(得分:1)
我在这里写了一个关于如何实现运行时类转换的长篇答案:how to retransform a class at runtime
更简单的方法是在https://github.com/nickman/retransformer重新构建项目,它将转换java。*类,包括本机方法。 (我用它来修改Runtime.availableProcessors)