Замените класс в библиотеке классов Java пользовательской версией

На класс BasicLabelUI в javax/swing/plaf/basic влияет ошибка подтвержденная ошибка. В моем приложении мне нужна функциональность, предоставляемая исправленная версия (подана для v9). Как по юридическим, так и по техническим причинам я все еще привязан к затронутой версии JDK.

Мой подход состоял в том, чтобы создать пакет javax/swing/plaf/basic внутри моего проекта, содержащий исправленную версию.

Как я могу заставить мой проект отдавать предпочтение моей включенной версии класса по сравнению с дефектным классом в установленном JDK?

Это должно быть несколько переносимым, поскольку фиксированный класс также должен работать на стороне клиента, а дефектный класс в установке JDK следует игнорировать. Поэтому я не хочу модифицировать JDK, а скорее обхожу этот конкретный класс.


person bogus    schedule 10.11.2015    source источник
comment
Как ни странно, я не могу воспроизвести эту ошибку в Java 1.7.0_75 или 1.8.0_65 в Windows 7, используя код из сообщения об ошибке. Я попытался изменить его, чтобы использовать внешний вид по умолчанию; Я попытался добавить использование EventQueue.invokeLater в основной метод. (Я надеялся поэкспериментировать с обходным решением на основе InputMap.)   -  person VGR    schedule 10.11.2015
comment
Я могу воспроизвести ошибочное поведение, используя код из bugs.java.com/bugdatabase/ view_bug.do?bug_id=7172652. Я использую 1.8.0_45   -  person bogus    schedule 10.11.2015


Ответы (2)


Как упоминалось в других ответах, теоретически вы могли разархивировать файл rt.jar вашей JVM и заменить файл совместимой версией с исправленными ошибками.

Любые классы библиотеки классов Java, такие как классы Swing, загружаются загрузчиком классов начальной загрузки, который ищет свои классы в этом rt.jar. Как правило, вы не можете добавлять классы в этот путь к классам, не добавляя их в этот файл. Есть (нестандартный) вариант ВМ

-Xbootclasspath/jarWithPatchedClass.jar:path

где вы должны добавить файл jar, который включает исправленную версию, но это не обязательно работает на любой виртуальной машине Java. Кроме того, незаконно развертывать приложение, которое изменяет это поведение! Как указано в официальной документации:

Не развертывайте приложения, использующие этот параметр для переопределения класса в rt.jar, так как это нарушает лицензию на двоичный код Java Runtime Environment.

Однако если вы добавили класс в загрузчик классов начальной загрузки (что возможно без использования нестандартных API с помощью инструментального API), среда выполнения все равно загрузит исходный класс, поскольку в этом случае загрузчик классов начальной загрузки ищет rt .jar сначала. Поэтому невозможно «затенить» сломанный класс без изменения этого файла.

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

[...] распространять [среду выполнения Java] полностью и без изменений и только в составе ваших апплетов и приложений

Поэтому менять виртуальную машину, которую вы распространяете, не рекомендуется, так как вы можете столкнуться с юридическими последствиями, когда это будет раскрыто.

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

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

java -javaagent:bugFixAgent.jar -jar myApp.jar

Агент Java способен заменить двоичное представление класса при запуске приложения и, следовательно, может изменить реализацию ошибочного метода.

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

public static class BugFixAgent {
  public static void premain(String args, Instrumentation inst) {
    inst.addClassFileTransformer(new ClassFileTransformer() {
      @Override
      public byte[] transform(ClassLoader loader, 
                              String className, 
                              Class<?> classBeingRedefined, 
                              ProtectionDomain protectionDomain, 
                              byte[] classfileBuffer) {
        if (className.equals("javax/swing/plaf/basic/BasicLabelUI")) {
          return patchedClassFile; // as found in the repository
          // Consider removing the transformer for future class loading
        } else {
          return null; // skips instrumentation for other classes
        }
      }
    });
  }
}

Пакет javadoc java.lang.instrumentation предлагает подробное описание того, как создать и реализовать агент Java. При таком подходе вы можете использовать фиксированную версию рассматриваемого класса, не нарушая лицензионного соглашения.

По опыту, агенты Java — отличный способ исправить временные ошибки в сторонних библиотеках и в библиотеке классов Java без необходимости развертывания изменений в вашем коде или даже развертывания новой версии для клиента. По сути, это типичный вариант использования агента Java.

person Rafael Winterhalter    schedule 10.11.2015
comment
Если вы собираетесь изменить поведение JVM, есть более простой способ. Но я думаю, что это нарушает (я думаю) правила клиента. - person Stephen C; 10.11.2015
comment
Я не думаю, что у клиента возникнут проблемы при инструментировании классов процесса, который запускает приложение Swing (то есть, скорее всего, выделенную виртуальную машину). В конце концов, большинство клиентов хотят версию без ошибок. Однако то, что вы предлагаете, является юридическим нарушением лицензионного соглашения Java. - person Rafael Winterhalter; 10.11.2015
comment
Вы не полностью прочитали мой ответ. См. 1-й абзац после строки. - person Stephen C; 10.11.2015
comment
Я предложил ТРИ вещи. И порекомендуйте один из них... что точно не является незаконным. Кроме того, мое предложение № 1 является нарушением бинарной лицензии Oracle только в том случае, если применяется бинарная лицензия Oracle. Это не так, если вы строите из исходников OpenJDK (GPLv2) и следуете правилам GPL. Прочтите эти... openjdk.java.net/legal - person Stephen C; 10.11.2015
comment
OP заявил, что миграция версии виртуальной машины не является вариантом, что исключает ваши предложения либо о создании собственной виртуальной машины, либо о настройке существующей виртуальной машины (не обращая внимания на тот факт, что вам по закону не разрешено изменять двоичные файлы). Если вы имели в виду вариант -X, который я также предложил, вы должны быть более конкретными в своем ответе. Ничто из того, что указано в ОП, не предполагает, что использование агента Java неприменимо для этого варианта использования. Наконец, в вашем первом предложении говорится, что то, чего хочет ОП, невозможно, что не соответствует действительности и не противоречит вашему собственному ответу, поэтому я не согласен. - person Rafael Winterhalter; 10.11.2015
comment
@RafaelWinterhalter Итак, этот BugFixAgent отменяет плохо обусловленный класс в JDK? - person bogus; 10.11.2015
comment
@bogus Да, оба подхода будут работать. Агент работает на любой стандартной виртуальной машине, решение с добавлением пути к классу работает только с текущими версиями HotSpot. - person Rafael Winterhalter; 10.11.2015
comment
Подход prepend to classpath ТАКЖЕ работает в более старых версиях Hotspot и, вероятно, продолжит работать в будущих версиях .... если у вас нет доказательств того, что они собираются отозвать эту функцию. - person Stephen C; 12.11.2015
comment
Кроме того, -Xbootstrapclasspath доступен (по крайней мере) в java-команде IBM и Oracle JRockit. Это не только для Hotspot, как вы подразумеваете. - person Stephen C; 12.11.2015
comment
Я имею в виду, что опции с префиксом X не поддерживаются. sun.misc.Unsafe существует на всех платформах, но исчезает. - person Rafael Winterhalter; 12.11.2015

Как я могу заставить мой проект отдавать предпочтение моей включенной версии класса по сравнению с дефектным классом в установленном JDK?

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

Предполагая, что вы можете определить подходящую версию в исходных репозиториях OpenJDK, можно было бы создать свой собственный вариант библиотек Java с исправленной ошибкой. Однако это будет не настоящая Java. Конечно, она не будет считаться затронутой версией Java, которую вы ограничены в использовании. (И кроме того, вы берете на себя бесконечный цикл повторного применения вашего патча к каждому новому выпуску патчей текущей версии Java...)

Также теоретически возможно поместить модифицированную версию некоторого класса стандартной библиотеки Java в JAR и добавить ее в путь к классам начальной загрузки JVM с помощью параметра командной строки -Xbootclasspath. Но это также равносильно изменению затронутой версии Java.

Выполнение этого с помощью агента Java для использования исправленной версии класса также нарушает правила. И это сложнее. (Если вы собираетесь нарушить свои правила, сделайте это простым способом...)


Если вы и ваши клиенты решите, что настройка JVM является приемлемым решением, то сделать это с помощью пути к классам начальной загрузки, вероятно, будет самым простым и чистым подходом. И это ОПРЕДЕЛЕННО законно1.

Тем не менее, я бы порекомендовал вам найти обходной путь для этой ошибки, пока не будет выпущена версия Java 9 с вашим исправлением.


1. На самом деле, даже сборка из модифицированного исходного кода является законной, поскольку лицензия Oracle Binary на нее не распространяется. Бинарная лицензия предназначена для распространения модифицированной версии бинарного файла Oracle. Другая возможная проблема заключается в том, что вы можете нарушить условия использования товарных знаков Java, если распространяете версию, несовместимую с настоящей Java, и называете свой дистрибутив Java. Решение этой проблемы... не называйте это Java!

Однако не следуйте моим советам. Спросите юриста. А еще лучше не делайте этого вообще. Это излишне сложно.

person Stephen C    schedule 10.11.2015
comment
Вы говорите, что [d]использование агента Java для использования исправленной версии класса также нарушает правила, что не соответствует действительности. Вы можете настроить любой класс начальной загрузки, какой захотите. - person Rafael Winterhalter; 10.11.2015
comment
Это не приборка. Это преднамеренно меняет поведение JVM. Если клиент не согласен, вы нарушаете их правила; то есть тот, который говорит, что должна поддерживаться Java версии X. Почему это плохо? Предположим, они собираются смешивать ваш код с другим кодом, который требует, чтобы тот же класс/метод вел себя стандартным образом? - person Stephen C; 10.11.2015
comment
Эквивалентный мой вопрос: если есть два класса с одинаковыми именами, одинаковыми интерфейсами и одним и тем же именем пакета - как вы можете установить, какой из них распознается, а какой игнорируется? - person bogus; 10.11.2015
comment
@bogus Загрузчик классов начальной загрузки, который отвечает за загрузку классов Swing, всегда сначала запрашивается для класса и затеняет любой другой класс того же существующего. Если два класса имеют одно и то же имя (недопустимо при жестком кодировании в дистрибутив виртуальной машины), загружается первый класс, найденный в этом пути к классам. API инструментария позволяет вам добавлять классы в путь к классу начальной загрузки, но тогда эти классы будут обнаружены последними, поэтому класс с ошибками будет загружен. - person Rafael Winterhalter; 10.11.2015
comment
@StephenC Как бы изменился ваш подход в этом отношении? Инструментирование влияет только на инструментированное приложение, изменение виртуальной машины изменяет глобальное поведение виртуальной машины Java, поэтому оно не разрешено Oracle. Агент всегда менее навязчив по сравнению с глобальным патчем. - person Rafael Winterhalter; 10.11.2015
comment
@RafaelWinterhalter Чем отличается ваш подход в этом отношении? – Требуется написать меньше кода, а заказчику будет легче проводить аудит. Агент всегда менее навязчив по сравнению с глобальным патчем. - Согласен. Я не рекомендую глобальный патч. Я рекомендую использовать настройку bootclasspath. (Это параметр командной строки JVM...) - person Stephen C; 10.11.2015
comment
@RafaelWinterhalter - Вы не можете узнать, чего хочет клиент, не спросив его. (И это не ››ваш‹‹ клиент.) Например, заказчику, возможно, придется получить это приложение от проверки безопасности сети или операций, для которых сторонние исправления для JVM являются анафемой. - person Stephen C; 10.11.2015
comment
Дополнительно: Лицензия на это программное обеспечение не позволяет распространять бета-версии и другие предварительные версии. Патч Oracle будет выпущен в v9. - person bogus; 11.11.2015
comment
@bogus Пока вы не называете это Java. - person Rafael Winterhalter; 12.11.2015