我正在尝试创建一个自定义类加载器来完成以下任务:
我有一个包com.company.MyClass
当要求类加载器以下列格式加载任何内容时:
com.company.[additionalPart].MyClass
我希望类加载器加载com.company.MyClass
,有效地忽略包名称的[additionalPart]。
这是我的代码:
public class MyClassLoader extends ClassLoader {
private String packageName = "com.company.";
final private String myClass = "MyClass";
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
String className = name;
// Check if the class name to load is of the format "com.company.[something].MyClass
if (name.startsWith(packageName)) {
String restOfClass = className.substring(packageName.length());
// Check if there is some additional part to the package name
int index = restOfClass.indexOf('.');
if (index != -1) {
restOfClass = restOfClass.substring(index + 1);
//finally, check if the class name equals MyClass
if (restOfClass.equals(myClass)) {
// load com.company.MyClass instead of com.company.[something].MyClass
className = packageName + myClass;
clazz = super.loadClass(className, true);
}
}
}
if (clazz == null) {
// Normal clase: just let the parent class loader load the class as usual
clazz = super.loadClass(name);
}
return clazz;
}
}
这是我的测试代码:
public class TestClassLoader {
public void testClassLoader () throws Exception {
ClassLoader loader = new MyClassLoader(this.getClass().getClassLoader());
clazz = Class.forName("com.company.something.MyClass", true, loader );
}
public static void main (String[] args) throws Exception {
TestClassLoader tcl = new TestClassLoader();
tcl.testClassLoader();
}
}
MyClassLoader选择正确的类(即com.company.MyClass
)并从loadClass
正确地返回它(我已经完成了代码),但是,它会在稍后的某个时间点(即之后)抛出异常从JVM调用loadClass
,如下所示:
线程“main”中的异常 抛出java.lang.ClassNotFoundException: com / company / something / MyClass at java.lang.Class.forName0(母语 方法)
在 java.lang.Class.forName(Class.java:247) 在[我的代码]
现在,我意识到你们中的一些人可能在想“为什么有人需要这样做?必须有更好的方法”。我确信有,但这对我来说是一种教育,因为我想了解类加载器的工作原理,并深入了解jvm类加载过程。所以,如果你能忽视程序的无聊,并且幽默我,我会非常感激。
答案 0 :(得分:1)
您的问题
这是纯粹的猜测。班级名称存储在java byte code中。因此,您通过此技术加载的类将是有缺陷的。这可能会使系统混乱。
ClassLoader可能保留对com.company.something.MyClass的引用,但类本身可能保留对com.company.MyClass的引用。 (我使用的可能很多,因为我确实不确定。)可能一切正常,直到你使用MyClass类为止。然后不一致会造成麻烦。那么什么时候抛出这个异常?
如果您有兴趣了解类加载器的工作原理,那么您可以使用 javap 来获取字节代码。这也可以让你检查我的假设。
如果我的假设是正确的,那么解决方案就是修复字节码。有几个软件包允许您设计字节代码。复制一个类,更改复制的类的名称,然后加载它。
<强>除了强>
虽然与您的问题无关:我发现以下内容不必要复杂(并且它不适用于com.company.something.somethingelse.MyClass)。
// Check if the class name to load is of the format "com.company.[something].MyClass
if (name.startsWith(packageName)) {
String restOfClass = className.substring(packageName.length());
// Check if there is some additional part to the package name
int index = restOfClass.indexOf('.');
if (index != -1) {
restOfClass = restOfClass.substring(index + 1);
//finally, check if the class name equals MyClass
if (restOfClass.equals(myClass)) {
// load com.company.MyClass instead of com.company.[something].MyClass
className = packageName + myClass;
clazz = super.loadClass(className, true);
}
}
为什么不呢?
//Check if the class name to load is of the format "com.com // Check if the class name to load is of the format "com.company.[something].MyClass"
if ( ( name . startsWith ( packageName ) ) && ( name . endsWith ( myClass ) ) )
答案 1 :(得分:1)
我认为你不能通过类加载器真正做到这一点。从理论上讲,如果其他一些类试图加载一个它假定的类叫做'com.mycompany.foo.MyClass'那么为时已晚,有人已经有一个带有字节码引用'com.mycompany.foo'的类,并且该类已经加载
通过使用ASM之类的东西在构建时重新打包所有代码,静态级别的重新打包变得更加容易。你当然必须修改他自己的类包本身以及我引用该包的所有类。
如果你使用Maven,请查看shade插件。如果不是,我似乎回想起一个名为JarJar的工具。
您当然可以通过javaagent和ClassTransformer在运行时进行这种字节码操作。 maven-shade-plugin的代码实际上非常小 - 如果你抓住它并撕掉maven部分你可能会在2-3天内工作。