是否可以在Java中在运行时实现接口?

时间:2014-05-20 20:22:51

标签: java bytecode

我正在开发一个项目,其中有很多由库创建的对象,我无法访问这些对象的创建过程。

以下片段是举例说明我的问题。

代码:

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();
}

以下代码是我遇到的问题。请注意以下事项:

    仅当c是run()的实例时才会调用
  1. ExampleInterface
  2. getRunConditions(Clazz c)executeClazz(Clazz c)都是我无法访问的类中的私有方法。
  3. 在编译时,Clazz 将包含名为run()的方法。
  4. ExampleExecutor 我的班级。我无法访问它 方式(我甚至无法获得该类的实例)。
  5. 代码:

    public class ExampleExecutor {
        public void executeClazz(Clazz c) {
            if ((c instanceof ExampleInterface) && getRunConditions(c)) {
                ExampleInterface ex = (ExampleInterface) c;
                ex.run();
            }
        }
    }
    

    显然以下方法在语法上不可能,但这正是我想要实现的目标。基本上,如果c尚未实现ExampleInterface,请设置c以实现ExampleInterface,然后提供必须覆盖的方法。

    请注意以下事项:

    1. extendInterface( Name of Interface )虚构的语法 I 旨在说明我的目标。
    2. run() 必须在这里定义(在运行时)。
    3. 我不能使用包装器或代理类作为解决方案。 IE,Clazz对象必须最终实现ExampleInterface,我不能使用变通方法。 (如果您想知道原因,请参阅 this link
    4. 代码:

      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),而有时则不会。{/ p>

      Clazz实例不同,我总是关心鼠标是否被点击,并且总是需要触发事件。

      实际上,ExampleInterface实际上是以下内容:

      public interface CustomMouseListener {
          public void mouseClicked(CustomMouseEvent evt);
          public void mousePressed(CustomMouseEvent evt);
          public void mouseReleased(CustomMouseEvent evt);
          //etc
      }
      

4 个答案:

答案 0 :(得分:5)

您可以使用java instrumentation API(强制)使类适应接口。 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中以使其正常工作:

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

答案 1 :(得分:4)

执行建议的唯一方法是使用字节码Instrumentation。您可以添加一个代理,在加载之前更改要修改的clazz的字节代码。

您需要在加载时执行此操作的原因是许多JVM不允许您更改字段,有些JVM不允许您在加载类之后添加方法。

更简单的解决方案是反编译该类,修改它并再次编译它。假设该类可以被反编译,这将为您节省大量的时间和精力。

  

我正在使用的库有一个安全管理器阻止使用所有反射

这是一个奇怪的选择,因为你可以在调用库之前安装自己的SecurityManager,它不能阻止你做任何事情。

答案 2 :(得分:0)

我不认为你想要什么是可能的;有Dynamic Proxies,但它们使用反射,并且看起来您可能无法访问类加载器(您可以在其中设置您自己的实时字节码操作)。

答案 3 :(得分:-1)

根据您的java版本,您可以使用lambda表达式(使用java 8)。

代码相对简单:

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

请注意,这仅适用于具有一种方法的界面。两个签名(接口和Clazz)必须匹配。