Is it possible to implement an interface at runtime in Java?

前端 未结 4 1368
梦谈多话
梦谈多话 2021-02-01 20:29

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 sn

4条回答
  •  悲哀的现实
    2021-02-01 20:46

    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
    

提交回复
热议问题