Проблема создания пользовательского загрузчика классов - PullRequest
2 голосов
/ 03 августа 2010

Я пытаюсь создать собственный загрузчик классов, чтобы выполнить следующее:

У меня есть класс в пакете 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 (я прошел через код), однако, он выдает исключение в какой-то более поздний момент (т. Е. После loadClass вызывается из JVM) следующим образом:

Исключение в теме "главная" java.lang.ClassNotFoundException: com / компания / что-то / MyClass в java.lang.Class.forName0 (Native Метод)

в java.lang.Class.forName (Class.java:247) в [мой код]

Теперь я понимаю, что некоторые из вас могут подумать: «Зачем кому-то это нужно делать? Должен быть лучший путь». Я уверен, что это так, но для меня это что-то полезное, поскольку я хотел бы понять, как работают загрузчики классов, и глубже понять процесс загрузки классов jvm. Так что, если вы можете забыть о бессмысленности процедуры и пошутить со мной, я был бы очень благодарен.

Ответы [ 2 ]

1 голос
/ 03 августа 2010

Я не думаю, что вы действительно можете сделать это через загрузчик классов.Теоретически, если какой-то другой класс пытается загрузить класс, который, как он предполагает, называется com.mycompany.foo.MyClass, тогда уже слишком поздно, у кого-то уже есть класс с байт-кодом, ссылающийся на com.mycompany.foo, и этот класс уже загружен..

Переупаковка намного проще на статическом уровне, используя что-то вроде ASM для перепаковки всего кода во время сборки.Вы, конечно, должны изменить как сам пакет классов, так и все классы, которые могут относиться к этому пакету.

Если вы используете Maven, проверьте плагин shade.Если нет, то я помню инструмент под названием JarJar.

Конечно, вы можете выполнять подобные манипуляции с байтовым кодом во время выполнения с помощью javaagent и ClassTransformer.Код для maven-shade-plugin на самом деле довольно маленький - если вы схватите его и извлечете maven-части, у вас, вероятно, будет что-то работающее через 2-3 дня.

1 голос
/ 03 августа 2010

Ваш вопрос

Это чистое предположение. Имя класса хранится в байт-коде Java . Таким образом, классы, которые вам удастся загрузить с помощью этой техники, будут дефектными. Вероятно, это сбивает с толку систему.

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 ) ) )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...