Trouble creating cutom class loader
I am trying to create a custom class loader to accomplish the following:
I have a class in package com.company.MyClass
When the class loader is asked to load anything in the following format:
com.company.[additionalPart].MyClass
I'd like the class loader to load com.company.MyClass
effectively ignoring the [additionalPart] of the package name.
Here is the code I have:
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;
}
}
And here is my test code:
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 picks up the correct class (ie com.company.MyClass
) and returns it just correctly from loadClass
(I have stepped through the code), however, it throws an exception at some later point (ie after loadClass
is called from the JVM) as follows:
Exception in thread "main" java.lang.ClassNotFoundException: com/company/something/MyClass at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:247) at [my code]
Now, I realize some of you may be thinking "Why would anyone need to do this? There has to be a better way". I am sure that there is, but this is something of education for me, as I'd like to understand how class loaders work, and gain a deeper understanding into the jvm class loading process. So, if you can overlook the inanity of the procedure, and humor me, I'd be very grateful.
So, does anyone have any ideas?
Thanks!
Your Question
This is pure speculation. The class name is stored in the java byte code. Thus the classes you manage to load by this technique will be defective. This is probably confusing the system.
The ClassLoader probably keeps a reference to com.company.something.MyClass, but the class itself probably keeps a reference to com.company.MyClass. (I use probably a lot because I don't really know for sure.) Probably everything works OK until you use the MyClass class for something. Then the inconsistency creates trouble. So when is this exception thrown?
If you are interested in learning how class loaders work, then you can use javap to get at the byte code. This would also allow you to check my hypothesis.
If my hypothesis is correct, then the solution would be to fix the byte code. There are several packages that allow you to engineer byte code. Copy a class, change the name of the copied class, and then load it.
Aside
While not relevant to your question: I find the below to be unnecessarily complicated (and it doesn't work on 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);
}
}
Why not?
//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 ) ) )
I don't think you can really do that via a classloader. Theoretically if some other class is attempting to load a class it assumes is called 'com.mycompany.foo.MyClass' then it's too late, someone already has a class with bytecode referencing 'com.mycompany.foo' and that class is already loaded.
Repackaging is a lot easier at the static level, by using something like ASM to repackage all the code at build time. You of course have to modify both he classes package itself and all the classes that my refer to that package.
If you use Maven, check out the shade plugin. If not I seem to recall a tool called JarJar.
You can of course do that kind of byte code manipulation at runtime via a javaagent and a ClassTransformer. The code for the maven-shade-plugin is actually pretty small -- if you grabbed it and ripped out the maven parts you'd probably have something working in 2-3 days.
链接地址: http://www.djcxy.com/p/58268.html下一篇: 无法创建cutom类加载器