在Java中是否可以在运行时实现接口?
我正在从事一个项目,其中有许多对象是由库创建的,我无法访问这些对象的创建过程 下面的代码片段是一个很好的例子来说明我的问题 代码:在Java中是否可以在运行时实现接口?,java,bytecode,Java,Bytecode,我正在从事一个项目,其中有许多对象是由库创建的,我无法访问这些对象的创建过程 下面的代码片段是一个很好的例子来说明我的问题 代码: ExampleInterface是Clazz在编译时可能实现也可能不实现的接口 代码: 下面的代码就是我遇到的问题。注意到以下几点: public interface CustomMouseListener { public void mouseClicked(CustomMouseEvent evt); public void mousePress
ExampleInterface
是Clazz在编译时可能实现也可能不实现的接口
代码:
下面的代码就是我遇到的问题。注意到以下几点:
public interface CustomMouseListener {
public void mouseClicked(CustomMouseEvent evt);
public void mousePressed(CustomMouseEvent evt);
public void mouseReleased(CustomMouseEvent evt);
//etc
}
run()
仅在c是ExampleInterface
的实例时调用getRunConditions(Clazz c)
和executeClazz(Clazz c)
都是我无权访问的类中的私有方法Clazz
将不包含名为run()
的方法ExampleInterface
,请将c设置为实现ExampleInterface
,然后提供必须重写的方法
注意到以下几点:
public interface CustomMouseListener {
public void mouseClicked(CustomMouseEvent evt);
public void mousePressed(CustomMouseEvent evt);
public void mouseReleased(CustomMouseEvent evt);
//etc
}
扩展接口(
接口名称
)
由语法组成
为了说明我的目标而创建run()
Clazz
对象必须最终实现ExampleInterface
,我不能使用变通方法。(如果您想知道原因,请参阅)Clazz
中何时调用run()
。如果Clazz
没有实现ExampleInterface
,我不知道什么时候应该调用run()
同时,我还想偶尔添加对默认情况下不支持的run()
的支持。因为我没有权限创建Clazz
对象,所以我无法通过自己实现接口来实现这一点
问题:简单地说,是否可以在运行时实现接口(并提供所需的方法)?
注意:虽然唯一的解决方案可能需要反射(如果需要,请在下面发布),但我使用的库有一个安全管理器,可以阻止所有反射的使用。也就是说,一个反思性的解决方案在将来可能对其他人有用,但对我没有用处
而且,我不是说在我自己的程序中只使用一个库。一个已经运行的主机应用程序(我正在使用的库就是为了这个应用程序而创建的)遵循并运行我为它编写的代码。如果该应用程序不喜欢我提供的任何代码(即,与它的安全管理器冲突),则该代码甚至不会被编译
我为什么需要这样做:
这与我正在使用的图书馆有关。因为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
}
执行建议的唯一方法是使用字节码检测。您可以添加一个代理,在加载之前更改要修改的clazz的字节码 您需要在加载时这样做的原因是,许多JVM不允许您更改字段,有些JVM不允许您在类加载后添加方法 一个更简单的解决方案是对类进行反编译、修改并再次编译。假设类可以反编译,这将为您节省大量时间和精力 我正在使用的库有一个安全管理器,它阻止所有反射的使用
这是一个奇怪的选择,因为您可以在调用库之前设置自己的安全管理器,它不会阻止您做任何事情。我认为您不可能想要什么;有,但它们使用反射,并且看起来您不可能访问类加载器(您可以在其中设置自己的即时字节码操作)。您可以使用java instrumentation API(强制)使类适应接口。APM、AOP框架和分析器通常使用这种技术在运行时将日志记录和度量代码注入目标类。应用程序直接使用这种技术是非常罕见的。如果我在生产代码中看到这一点,至少会是一个巨大的危险信号 尽管如此 鉴于这些分歧:
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;
}
}
将所有物品装入一个罐子中,使其正常工作:
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
根据您的java版本,您可以使用lambda表达式(使用Java8) 代码将相对简单:
Clazz o = .... // Here you obtain your object through third party library
ExampleInterface yourInterface = o::run;
yourInterface.run();
请注意,这仅是w
jar -tf agent.jar
META-INF/MANIFEST.MF
com/sabertiger/instrumentation/ExampleInterfaceAdapter.class
java -javaagent:agent.jar -classpath ..\instrumentation\bin com.sabertiger.example.ExampleExecutor
Hello world
Clazz o = .... // Here you obtain your object through third party library
ExampleInterface yourInterface = o::run;
yourInterface.run();