Возможно ли реализовать интерфейс во время выполнения на Java?
Я работаю над проектом, в котором есть много объектов, которые создаются библиотекой, и у меня нет доступа к процессу создания этих объектов.
Следующие примеры являются хорошим примером для иллюстрации моей проблемы.
код:
public class Clazz {
//The contents of Clazz are irrelevant
//Clazz is NOT my class. I have no access to its internal structure.
//However, I do have access to Clazz objects that were created elsewhere.
}
ExampleInterface
- это интерфейс, который Clazz может реализовать или не реализовать во время компиляции.
код:
public interface ExampleInterface {
public void run();
}
Следующий код - проблема, с которой я сталкиваюсь. Обратите внимание на следующее:
-
run()
вызывается только тогда, когда c является экземпляром ExampleInterface
.
-
getRunConditions(Clazz c)
и executeClazz(Clazz c)
являются частными методами в классе, к которому у меня нет доступа.
- Во время компиляции
Clazz
не будет содержать метод с именем run()
.
- ExampleExecutor не мой класс. У меня нет доступа к нему ни в одном
(я даже не могу получить экземпляр класса).
код:
public class ExampleExecutor {
public void executeClazz(Clazz c) {
if ((c instanceof ExampleInterface) && getRunConditions(c)) {
ExampleInterface ex = (ExampleInterface) c;
ex.run();
}
}
}
Очевидно, что следующий метод не является синтаксически возможным, но это то, чего я пытаюсь достичь. В принципе, если c не реализует ExampleInterface
, установите c для реализации ExampleInterface
, а затем предоставите методы, которые должны быть переопределены.
Обратите внимание на следующее:
-
extendInterface(
Name of Interface
)
является сильным синтаксисом . I
созданный в попытке проиллюстрировать мою цель.
-
run()
должен быть определен здесь (во время выполнения).
- Я не могу использовать оболочку или прокси-класс в качестве решения. IE, объект
Clazz
должен завершить реализацию ExampleInterface
, и я не могу использовать обходной путь. (обратитесь к этой ссылке, если вы хотите узнать почему).
код:
public void implementInterface(Clazz c) {
if (!(c instanceof ExampleInterface)) {
c.extendInterface(ExampleInterface {
@Override
public void run() {
//code
}
});
}
}
Чтобы прояснить, проблема, с которой я сталкиваюсь, заключается в том, что мне всегда нужно знать, когда run()
вызывается в Clazz
. Если Clazz
никогда не реализует ExampleInterface
, я не могу знать, когда следует вызывать run()
.
В то же время я также хотел бы иногда добавлять поддержку run()
, когда она по умолчанию не поддерживается. Поскольку у меня нет доступа к созданию объектов Clazz
, я не могу этого сделать, реализуя сам интерфейс.
Вопрос: Проще говоря, возможно ли реализовать интерфейс (и предоставить необходимые методы) во время выполнения?
ПРИМЕЧАНИЕ. Хотя единственное решение может потребовать отражения (и если да, то, пожалуйста, разместите его ниже), в библиотеке, которую я использую, есть менеджер безопасности, который блокирует использование всех отражений. IE, рефлексивное решение может быть полезным для других в будущем, но для меня это бесполезно.
Кроме того, я не имею в виду использование библиотеки в моей собственной программе. Выполнено уже запущенное хост-приложение (которое я использую для библиотеки), а затем запускает код, который я пишу для него. Если этому приложению не нравится какой-либо код, который я предоставляю (IE, конфликтует с его менеджером безопасности), код никогда не компилируется.
Зачем мне это нужно:
Это связано с библиотекой, которую я использую. Поскольку ExampleExecutor
- это метод, к которому у меня нет доступа, и я не могу контролировать создание Clazz, я не могу определить, когда выполняется run()
.
Причина, по которой мне нужно знать, когда выполняется run()
, является то, что в действительности run()
является обработчиком событий, который является частью библиотеки, которую я использую.
Например: mouseClicked(CustomMouseEvent evt)
может быть методом, который является частью интерфейса CustomMouseListener
. Иногда экземпляр Clazz
Я работаю с заботами, когда мышь нажата (и, следовательно, наследует CustomMouseListener
), а в других случаях это не так.
В отличие от экземпляра Clazz
, мне всегда все равно, если щелкнуть мышью, и всегда нужно, чтобы событие было запущено.
В действительности, ExampleInterface
будет фактически следующим:
public interface CustomMouseListener {
public void mouseClicked(CustomMouseEvent evt);
public void mousePressed(CustomMouseEvent evt);
public void mouseReleased(CustomMouseEvent evt);
//etc
}
Ответы
Ответ 1
Единственный способ сделать то, что вы предлагаете, - использовать байтовый код Instrumentation. Вы можете добавить агента, который изменяет байт-код clazz, который вы хотите изменить до его загрузки.
Причина, по которой вам нужно сделать это во время загрузки, заключается в том, что многие JVM не позволят вам изменять поля, а некоторые не позволяют добавлять методы после загрузки класса.
Более простым решением является декомпиляция класса, его изменение и компиляция. Предполагая, что класс может быть декомпилирован, это сэкономит вам много времени и усилий.
библиотека, которую я использую, имеет диспетчера безопасности, который блокирует использование всего отражения
Это странный выбор, потому что вы можете создать собственный SecurityManager перед вызовом библиотеки, и это не помешает вам что-либо сделать.
Ответ 2
Вы можете использовать API-интерфейс Java-инструментария для (принудительного) адаптации класса к интерфейсу. Этот метод обычно используется структурами APM, AOP и профайлерами для ввода кода измерения каротажа и показателей в целевые классы во время выполнения. Для приложений очень необычно использовать эту технику. Это был бы большой красный флаг, если я увижу это в производственном коде.
Тем не менее,
Учитывая эти Clazz:
package com.sabertiger.example;
public class Clazz {
public void purr(){
System.out.println("Hello world");
}
}
Интерфейс
package com.sabertiger.example;
public interface ExampleInterface {
void run();
}
Исполнитель
package com.sabertiger.example;
public class ExampleExecutor {
public static void main(String[] args) {
Clazz c=new Clazz();
// Normally a ClassCastException
ExampleInterface i=(ExampleInterface)(Object)(Clazz) c;
i.run();
}
}
Обычный запуск вызывает эту ошибку:
Exception in thread "main" java.lang.ClassCastException:
com.sabertiger.example.Clazz cannot be cast to
com.sabertiger.example.ExampleInterface
at com.sabertiger.example.ExampleExecutor.main(ExampleExecutor.java:7)
Вы можете заставить его работать, предоставив недостающий интерфейс и реализацию, изменив класс:
package com.sabertiger.instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class ExampleInterfaceAdapter implements ClassFileTransformer {
public static void premain(String agentArgument, Instrumentation instrumentation) {
// Add self to list of runtime transformations
instrumentation.addTransformer(new ExampleInterfaceAdapter());
}
@Override
// Modify only com.sabertiger.example.Clazz, return all other unmodified
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.matches("com/sabertiger/example/Clazz")) {
return addExampleInterface(className, classfileBuffer );
} else {
return classfileBuffer;
}
}
// Uses javassist framework to add interface and new methods to target class
protected byte[] addExampleInterface(String className, byte[] classBytecode) {
CtClass clazz= null;
try {
ClassPool pool = ClassPool.getDefault();
clazz = pool.makeClass(new java.io.ByteArrayInputStream(classBytecode));
String src=
"{ "+
" purr(); "+
"} ";
//Add interface
CtClass anInterface = pool.getCtClass("com.sabertiger.example.ExampleInterface");
clazz.addInterface(anInterface);
//Add implementation for run method
CtMethod implementation = CtNewMethod.make(
CtClass.voidType,
"run",
new CtClass[0],
new CtClass[0],
src,
clazz);
clazz.addMethod(implementation);
classBytecode=clazz.toBytecode();
} catch(Throwable e) {
throw new Error("Failed to instrument class " + className, e);
}
return classBytecode;
}
}
и требуемый файл MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: com.sabertiger.instrumentation.ExampleInterfaceAdapter
Boot-Class-Path: javassist.jar
Упакуйте все в банку, чтобы она работала:
jar -tf agent.jar
META-INF/MANIFEST.MF
com/sabertiger/instrumentation/ExampleInterfaceAdapter.class
Теперь мы можем передать Clazz в ExampleExecutor
java -javaagent:agent.jar -classpath ..\instrumentation\bin com.sabertiger.example.ExampleExecutor
Hello world
Ответ 3
Я не думаю, что вы хотите, есть Динамические прокси-серверы, но они используют отражение, и не похоже, что у вас есть доступ к загрузчику классов (где вы можете установить свой собственный который выполнял манипулирование байт-кодом "на лету" ).
Ответ 4
В зависимости от вашей java-версии вы можете использовать выражение лямбда (с java 8).
Код был бы относительно простым:
Clazz o = .... // Here you obtain your object through third party library
ExampleInterface yourInterface = o::run;
yourInterface.run();
Обратите внимание, что это работает только для интерфейса с одним методом. Обе подписи (интерфейс и Clazz) должны совпадать.