I am working on a project where there are a lot of objects that are created by a library, and I have no access to the creation process of these objects.
The following snippets serve as a good example to illustrate my problem.
Code:
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
is an interface that Clazz may or may not implement at compile time.
Code:
public interface ExampleInterface {
public void run();
}
The following code is the problem that I am running into. Take note of the following:
run()
is only called when c is an instance of ExampleInterface
.getRunConditions(Clazz c)
and executeClazz(Clazz c)
are both private methods in a class that I do not have access to.Clazz
will not contain a method named run()
.Code:
public class ExampleExecutor {
public void executeClazz(Clazz c) {
if ((c instanceof ExampleInterface) && getRunConditions(c)) {
ExampleInterface ex = (ExampleInterface) c;
ex.run();
}
}
}
Obviously the following method isn't syntactically possible, but it is what I am trying to achieve. Basically, if c doesn't already implement ExampleInterface
, set c to implement ExampleInterface
, and then provide the methods that must be overridden.
Take note of the following:
extendInterface(
Name of Interface
)
is made-up syntax I
created in an attempt to illustrate my goal.run()
must be defined here (at runtime).Clazz
object must wind up implementing ExampleInterface
, and I can't use a workaround. (refer to this link if you want to know why).Code:
public void implementInterface(Clazz c) {
if (!(c instanceof ExampleInterface)) {
c.extendInterface(ExampleInterface {
@Override
public void run() {
//code
}
});
}
}
To clarify, the issue that I am running into is that I need to always know when run()
is called in Clazz
. If Clazz
ever doesn't implement ExampleInterface
, I can't know when run()
should be called.
At the same time, I would also like to occasionally add support for run()
when it by default isn't supported. Because I have no access to the creation of Clazz
objects, I can't do this by implementing the interface myself.
Question: Put simply, is it possible to implement an interface (and provide the required methods) at runtime?
NOTE: While the only solution may require reflection (and if so please post it below), the library I am using has a security manager that blocks the use of all reflection. IE, a reflective solution may be of use to others in the future, but will be of no use to me.
Also, I don't mean just using a library in my own program. An already running host application (which is what the library I am using was made for) complies and then runs code that I write for it. If that application doesn't like any of the code that I provide (IE, conflicts with its security manager), the code is never even compiled.
Why I need to do this:
It has to do with the library that I am using. Because ExampleExecutor
is a method that I don't have access to, and I can't control the creation of Clazz, I can't determine when run()
is executed.
The reason why I need to know when run()
is executed is because in actuality, run()
is an event handler that is part of the library I am using.
For example: mouseClicked(CustomMouseEvent evt)
might be a method that is part of the interface CustomMouseListener
. Sometimes the instance of Clazz
I am working with cares when the mouse is clicked (and therefore inherits CustomMouseListener
), while other times it doesn't.
Unlike the Clazz
instance, I always care if the mouse is clicked, and always need the event to be triggered.
In reality, the ExampleInterface
would actually be the following:
public interface CustomMouseListener {
public void mouseClicked(CustomMouseEvent evt);
public void mousePressed(CustomMouseEvent evt);
public void mouseReleased(CustomMouseEvent evt);
//etc
}
An interface cannot implement another interface in Java. An interface in Java is essentially a special kind of class. Like classes, the interface contains methods and variables. Unlike classes, interfaces are always completely abstract.
An interface can extend other interfaces, just as a class subclass or extend another class. However, whereas a class can extend only one other class, an interface can extend any number of interfaces.
Yes. you can happily do it.
You can use the java instrumentation API to (forcefully) adapt the class to the interface. This technique is usually used by APM, AOP frameworks, and profilers to inject logging and metrics measurement code into target classes at runtime. It is very unusual for applications to directly use this technique. It would be a big red flag in the least if I see this in production code.
Nonetheless,
Given these Clazz:
package com.sabertiger.example;
public class Clazz {
public void purr(){
System.out.println("Hello world");
}
}
Interface
package com.sabertiger.example;
public interface ExampleInterface {
void run();
}
Executor
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();
}
}
A Normal run produces this error:
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)
You can make it work by supplying the missing interface and implementation by transforming the class:
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;
}
}
and the required MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: com.sabertiger.instrumentation.ExampleInterfaceAdapter
Boot-Class-Path: javassist.jar
Pack everything into a jar to make it work:
jar -tf agent.jar
META-INF/MANIFEST.MF
com/sabertiger/instrumentation/ExampleInterfaceAdapter.class
Now we are able to pass Clazz to ExampleExecutor
java -javaagent:agent.jar -classpath ..\instrumentation\bin com.sabertiger.example.ExampleExecutor
Hello world
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With