views:

56

answers:

2

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 (i.e. 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 (i.e. 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!

+1  A: 

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 ) ) )
emory
Thanks so much for your answer. I suspect your answer is at least on the right track.I decided to go the byte code editor route. I have started playing with asm, and I think I have a solution that will work. I'll still need a class loader, and create a new class on the fly.
yspotts
Oh, and about the Aside you mentioned -- I was trying to isolate specifically where there was only one additional item in the package name (so that com.company.something.somethingelse.MyClass would not match by design). Don't ask :)
yspotts
A: 

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.

David Blevins
Thanks for the response. javaagent would be cool to try -- in my case, it won't help much, but it seems like a neat thing to investigate.I actually found jarjar very recently, and looked at the source code. I got some good ideas for how to proceed with project using asm.
yspotts