Можно ли подделать отсутствующие классы с помощью ClassLoader? - PullRequest
0 голосов
/ 15 апреля 2020

Я загружаю в классы из JAR, которые реализуют интерфейс из API publi c. Сам интерфейс останется постоянным, но другие классы, связанные с API, могут со временем меняться. Очевидно, что после изменения API мы больше не сможем поддерживать реализации интерфейса, которые были написаны в старой версии. Однако некоторые методы интерфейса предоставляют простые метаданные типа String, которые, как мы можем предположить, никогда не изменятся и никогда не будут полагаться на другие части API, которые могут измениться. Я хотел бы иметь возможность извлекать эти метаданные, даже когда API изменился.

Например, рассмотрим следующую реализацию, которая может быть загружена, где Foo - это интерфейс, а Bar - это другой класс в API. Я хочу вызвать метод name, даже если класс Bar больше не существует.

class MyFoo implements Foo {
   Bar bar = null;

   @Override public String name() {
      return "MyFoo"
   }
}

Насколько я вижу, очевидный подход заключается в переопределении loadClass(String name) в моем пользовательском ClassLoader и верните некоторый "поддельный" класс для Bar. Можно предположить, что методы метаданных никогда не создают и не используют объект Bar. Вопрос в том, как сгенерировать этот «фальшивый» класс при запросе на загрузку Bar. Я думал о следующих подходах:

  1. Просто верните любой старый существующий класс. Я попытался вернуть Object.class, но это все равно приводит к NoClassDefFoundError для Bar, когда я пытаюсь создать экземпляр Foo.
  2. . Использовать ASM для генерации байтового кода для нового класса с нуля.
  3. Используйте ASM, чтобы переименовать какой-то пустой класс шаблона, чтобы он соответствовал Bar, и загрузите его.

И 2., и 3. кажется довольно сложным, так что я был интересно, был ли более простой способ достичь моей цели?

1 Ответ

1 голос
/ 15 апреля 2020

Вот загрузчик классов, который создаст фиктивный класс для каждого класса, который не найден в пути поиска, очень простым способом:

public class DummyGeneratorLoader extends URLClassLoader {

    public DummyGeneratorLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public DummyGeneratorLoader(URL[] urls) {
        super(urls);
    }

    public DummyGeneratorLoader(
        URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(urls, parent, factory);
    }

    static final byte[] template = ("Êþº¾\0\0\0002\0\n\1\7\0\1\1\0\20java/lang/Object"
        + "\7\0\3\1\0\6<init>\1\0\3()V\14\0\5\0\6\n\0\4\0\7\1\0\4Code\0\1\0\2\0\4\0"
        + "\0\0\0\0\1\0\1\0\5\0\6\0\1\0\t\0\0\0\21\0\1\0\1\0\0\0\5*·\0\b±\0\0\0\0\0\0")
        .getBytes(StandardCharsets.ISO_8859_1);

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            return super.findClass(name);
        }
        catch(ClassNotFoundException ex) { }
        return new ByteArrayOutputStream(template.length + name.length() + 10) { {
            write(template, 0, 11);
            try { new DataOutputStream(this).writeUTF(name.replace('.', '/')); }
            catch (IOException ex) { throw new AssertionError(); }
            write(template, 11, template.length - 11);
        }
        Class<?> toClass(String name) {
            return defineClass(name, buf, 0, count); } }.toClass(name);
    }
}

Однако, может быть много ожидания или структурные ограничения, накладываемые использованием кода, который не может выполнить фиктивный класс. В конце концов, прежде чем вы сможете вызвать метод интерфейса, вы должны создать экземпляр класса, чтобы он прошел проверку и успешное выполнение своего конструктора.

Если методы действительно имеют предполагаемую структуру, такую ​​как public String name() { return "MyFoo"; } Использование ASM может быть более простым выбором, но не для генерации произвольно сложной фальшивой среды, а для анализа этих методов и прогнозирования постоянного значения, которое они вернут. Такой метод будет состоять только из двух инструкций, ldc value и areturn. Вам нужно только проверить, что это так, и извлечь значение из первой инструкции.

...