“Добавление поддержки реализаций Java InvocationHandler для методов интерфейса по умолчанию” описывает, как реализовать InvocationHandler
для вызова методов интерфейса по умолчанию
. Этот механизм имеет решающее значение для Fluent Node
реализация, описанная в “Интерфейсные фасады Java” . В первой статье также отмечается, что метод Обрабатывает. Поиск
Метод, используемый с Java 8 не будет работать с Java 9 это означает, что весь API не будет работать на Java 9 и последующих JVM .
В этой статье описывается специфичное для Java 9 решение, рефакторинг InvocationHandler
реализация для разделения и разделения логики решений, специфичных для Java 8 и Java 9, и вводит “JEP 238: файлы JAR с несколькими выпусками” для предоставления Java 8 и решения Java 9 (и более поздних версий) одновременно в одном и том же JAR.
Теория работы
Как описано в JEP 238 , банки с несколькими выпусками предоставляют средства для предоставления альтернативных версий классов, которые могут использовать преимущества определенных функций платформы. Альтернативные классы хранятся в JAR в иерархии, описанной /META-INF/versions/${java.specification.version}/
. (Иерархия реализации подробно показана в конце этой статьи.) Для архиваторов и загрузчиков классов, которые не поддерживают несколько выпусков (например, Java 8 ), эти дополнительные классы игнорируются. Однако для Java 9 и последующих сред эти дополнительные классы загружаются, если версия
меньше или равна JVM/| ${java.specification.version} (причем последнее имеет приоритет).
Класс с кодом, специфичным для Java 8 , будет переработан для реализации суперинтерфейса с одним методом, воплощающим этот код. Эта существующая кодовая база будет скомпилирована как есть для создания JAR, подходящего для Java 8 Jvm. Кроме того, версия суперинтерфейса Java 9 будет скомпилирована для среды Java 9 и сохранена в JAR под /META-INF/versions/9/
иерархия.
Реализация
Defaultinvocationhandler.invoke(Объект,метод,Объект[])
учитывается для реализации DefaultInterfaceMethodInvocationHandler
:
@NoArgsConstructor @ToString public class DefaultInvocationHandler implements DefaultInterfaceMethodInvocationHandler { ... @Override public Object invoke(Object proxy, Method method, Object[] argv) throws Throwable { Object result = null; Class> declarer = method.getDeclaringClass(); if (method.isDefault()) { result = DefaultInterfaceMethodInvocationHandler.super.invoke(proxy, method, argv); } else if (declarer.equals(Object.class)) { result = method.invoke(this, argv); } else { result = invokeMethod(this, true, method.getName(), argv, method.getParameterTypes()); } return result; } ... }
С помощью реализации Java 8 :
public interface DefaultInterfaceMethodInvocationHandler extends InvocationHandler { @Override default Object invoke(Object proxy, Method method, Object[] argv) throws Throwable { Constructorconstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); constructor.setAccessible(true); Class> declarer = method.getDeclaringClass(); Object result = constructor.newInstance(declarer) .in(declarer) .unreflectSpecial(method, declarer) .bindTo(proxy) .invokeWithArguments(argv); return result; } }
В то время как реализация Java 9 является:
public interface DefaultInterfaceMethodInvocationHandler extends InvocationHandler { @Override default Object invoke(Object proxy, Method method, Object[] argv) throws Throwable { Class> declarer = method.getDeclaringClass(); Object result = MethodHandles.lookup() .findSpecial(declarer, method.getName(), methodType(method.getReturnType(), method.getParameterTypes()), declarer) .bindTo(proxy) .invokeWithArguments(argv); return result; } }
Альтернатива Java 9 сохраняется в ${basedir}/src/main/java9/ball/lang/reflect/DefaultInterfaceMethodInvocationHandler.java
в рамках проекта Maven. Компиляция с помощью maven-compiler-plugin
проста, поскольку следующий профиль демонстрирует инкрементную конфигурацию. 1
... ... ...... [+] src/main/java9 ${basedir}/src/main/java9 org.apache.maven.plugins maven-compiler-plugin jdk9 compile 9 ${project.basedir}/src/main/java9 true
Чтобы завершить настройку JAR, в манифесте JAR должен быть установлен флаг Multi-Release
.
Manifest-Version: 1.0 ... Multi-Release: true ...
Результирующая иерархия JAR показана ниже.
ball-util.jar ├── META-INF │ ├── MANIFEST.MF │ ├── ... │ └── versions │ └── 9 │ └── ball │ └── lang │ └── reflect │ └── DefaultInterfaceMethodInvocationHandler.class └── ball ├── ... ├── lang │ ├── ... │ └── reflect │ ├── DefaultInterfaceMethodInvocationHandler.class │ ├── DefaultInvocationHandler.class │ └── ... ├── ... ...
Резюме
Банки с несколькими выпусками предоставляют решение для одного JAR для поддержки различных версий платформы Java. Разработчики должны знать, что решение действительно создает некоторые проблемы: скомпилированные файлы классов version нельзя запускать вне JAR (что затрудняет тестирование), а детали реализации загрузчика классов повлияют на обнаружение и загрузку ресурсов. 2 (и это лишь некоторые из них). Однако, как показано в этом примере использования, это мощный инструмент для создания JAR-файлов, поддерживающих широкий спектр платформ Java.
[1] Родительский POM имеет аналогичные профили для Java с 10 по 14. ↩
[2] Пожалуйста, ознакомьтесь с обсуждением в “JEP 238: Файлы JAR с несколькими выпусками” . ↩
Оригинал: “https://dev.to/allenball/java-multi-release-jars-5c1l”