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

拜拜、爱过 提交于 2019-11-28 21:23:18

The only way to do what you suggest is to use byte code Instrumentation. You can add an agent which changes the byte code of the clazz you want to modify before it is loaded.

The reason you need to do this at load time is that many JVMs will not allow you to change fields and some don't allow you to add methods after the class is loaded.

A simpler solution is to decompile the class, modify it and compile it again. Assuming the class can be decompiled this will save you a lot of time and effort.

the library I am using has a security manager that blocks the use of all reflection

This is an odd choice because you can put in place your own SecurityManager before calling the library and it can't prevent you from doing anything.

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

I don't think what you want is possible; there are Dynamic Proxies, but they use reflection, and it doesn't look likely that you have access to the classloader (where you could set your own that did on-the-fly bytecode manipulation).

Depending on your java version, you could use the lambda expression (with java 8).

The code would be relatively simple:

Clazz o = .... // Here you obtain your object through third party library
ExampleInterface yourInterface = o::run;
yourInterface.run();

Note that this only works for interface with one method. Both signatures (interface and Clazz) must match.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!