Изменить только результат метода и загрузчик класса NoSuchMethod

Мы начали с одной банки с одним классом с одним методом, например:

boolean foo( int bar ) { ... }

Однако результат этого метода был бесполезен (на самом деле всегда истинен) и клиенты, которые использовали этот результат для чего-либо, вылетали с ошибкой. По этой причине метод был изменен на:

void foo( int bar ) { ... }

и все элементы перекомпилированы. Таким образом, мы можем предположить, что все пользователи этого jar-файла вызывают метод как:

foo(14);

никто не использует форму (и выходит за рамки этого вопроса, если кто-то есть):

boolean x = foo(14);

Предполагается, что ни один клиент, новый или старый, не использует логический результат.

Проблема заключалась в том, что в целевых системах новый jar загружался без обновления клиентов. Необновленные клиенты завершаются ошибкой с исключением «NoSuchMethod» при поиске метода «foo» с результатом «boolean». То есть:

  • клиент имеет статус типа "foo(14);" без использования метода результат
  • клиент скомпилирован с использованием jar с логическим методом.
  • клиент и библиотека загружаются в целевую систему
  • библиотека обновлена ​​​​новой банкой с методом void
  • клиент падает с "NoSuchMethod"

Источник проблемы, по-видимому, заключается в том, что и Java, и C/C++ не допускают двух методов, которые отличаются только результатом, но только Java «искажение имен» включает тип результата в имя, которое загрузчик класса (компоновщик в C/ С++) ищет.

Вопрос в следующем: можно ли каким-либо образом обмануть банку библиотеки или загрузчик классов, чтобы пропустить исключение «NoSuchMethod» в этом сценарии?


person pasaba por aqui    schedule 20.02.2017    source источник


Ответы (2)


Такое поведение вполне ожидаемо. Это связано с тем, что скомпилированные классы клиента содержат < em>постоянный пул, который включает символическую ссылку на метод.

Предположим, у нас есть класс FooClass с двумя методами:

public boolean foo1(int bar) {
    return true;
}

public void foo2(int bar) {
    // ...
}

И давайте предположим, что у нас есть еще один класс Main, в котором мы вызываем оба метода:

    FooClass fc = new FooClass();
    fc.foo1(1);
    fc.foo2(1);

Если мы разберем Main.class, то увидим, что ссылки на методы различаются не только именами, но и возвращаемым типом (обратите внимание на (I)Z и (I)V):

     7: astore_1
     8: aload_1
     9: iconst_1
    10: invokevirtual #4                  // Method q42340444/FooClass.foo1:(I)Z
    13: pop
    14: aload_1
    15: iconst_1
    16: invokevirtual #5                  // Method q42340444/FooClass.foo2:(I)V

Где постоянный пул содержит:

   #4 = Methodref          #2.#25         // q42340444/FooClass.foo1:(I)Z
   #5 = Methodref          #2.#26         // q42340444/FooClass.foo2:(I)V

Другими словами, он сохраняет тип возврата исходного метода и ищет в связанных библиотеках точно такой же метод, который вызывает исключение NoSuchMethod в вашем случае.

Точнее, скомпилированные пользовательские классы связаны с методом FooClass.foo:(I)Z, а ваша новая библиотека не содержит метода с таким описанием, а только FooClass.foo:(I)V.

Вывод: Вы не можете решить эту проблему без сохранения старой сигнатуры метода (может быть, лучше аннотировать этот метод аннотацией @Deprecated) или путем перекомпиляции кода клиента.

person Andremoniy    schedule 20.02.2017
comment
Да, это так, как вы описываете, но ваше предложение решить текущую реальную проблему? - person pasaba por aqui; 20.02.2017
comment
Вы не можете решить эту проблему, не сохраняя сигнатуру старого метода или не перекомпилируя клиентский код. - person Andremoniy; 20.02.2017
comment
К сожалению, я не могу сохранить старый метод и более новый, потому что Java не позволяет использовать два метода, которые отличаются только типом результата. - person pasaba por aqui; 20.02.2017
comment
Переименуйте его в foo2(int bar) :) Другого пути нет - person Andremoniy; 20.02.2017
comment
Как насчет пользовательского загрузчика классов с @Override findClass? - person pasaba por aqui; 20.02.2017
comment
:) Можно, но это также потребует изменения кода клиента. Нельзя просто так подставить classloader из 3d-party-library - person Andremoniy; 20.02.2017
comment
Возможно ли настроить загрузчик классов для одной банки или класса в банке? - person pasaba por aqui; 20.02.2017
comment
Вы не можете сделать это без изменения кода клиента. - person Andremoniy; 20.02.2017
comment
Или вы можете попробовать написать собственную оболочку, которая будет загружать как клиентскую, так и вашу банку с помощью пользовательского загрузчика классов. - person Andremoniy; 20.02.2017
comment
Но я не уверен, что такой трюк с кастомным загрузчиком классов сработает. Трудно сказать, не попробовав код жизни. У меня есть сомнения по поводу этого решения, и я чувствую, что вам придется создать какой-то поддельный или синтетический метод. - person Andremoniy; 20.02.2017

Просто мои мысли.

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

person Zefick    schedule 20.02.2017