繁体   English   中英

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

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

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

以下代码片段很好地说明了我的问题。

码:

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.
}

ExampleInterfaceExampleInterface在编译时可能会或可能不会实现的接口。

码:

public interface ExampleInterface {
    public void run();
}

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

  1. 仅当c是ExampleInterface的实例时才调用run()
  2. getRunConditions(Clazz c)executeClazz(Clazz c)都是我无法访问的类中的私有方法。
  3. 在编译时, Clazz包含名为run()的方法。
  4. ExampleExecutor 不是我的课程。 我无法以任何方式访问它(我什至无法获得该类的实例)。

码:

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 )是我为了说明我的目标而创建的组合语法
  2. 必须在此处(在运行时)定义run() )。
  3. 我不能使用包装器或代理类作为解决方案。 IE浏览器,在Clazz的对象必须拉闸实施ExampleInterface ,我不能使用的解决方法。 (如果您想知道原因,请参考此链接 )。

码:

public void implementInterface(Clazz c) {
    if (!(c instanceof ExampleInterface)) {
        c.extendInterface(ExampleInterface {
            @Override
            public void run() {
                //code
            }
        });
    }
}

为了澄清,我遇到的问题是我需要始终知道何时在Clazz调用run() 如果Clazz从来没有实现ExampleInterface ,我不知道何时应该调用run()

同时,当默认情况下不支持run()时,我偶尔也会添加对它的支持。 由于无法访问Clazz对象的创建,因此无法通过自己实现接口来实现。

问题:简单地说,是否可以在运行时实现接口(并提供所需的方法)?

注意:虽然唯一的解决方案可能需要反射(如果需要,请在下面发布),但是我正在使用的库具有一个安全管理器,该管理器阻止所有反射的使用。 IE,一种反射性解决方案将来可能对其他人有用,但对我而言将毫无用处。

另外,我并不是说只在自己的程序中使用一个库。 一个已经在运行的主机应用程序(这就是我正在使用的库所针对的)符合并运行我为其编写的代码。 如果该应用程序不喜欢我提供的任何代码(IE,与其安全管理器冲突),则该代码甚至都不会被编译。

为什么我需要这样做:

它与我正在使用的库有关。 因为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
}

您可以使用Java工具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 -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

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

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

一个更简单的解决方案是反编译该类,对其进行修改然后再次对其进行编译。 假设可以反编译该类,这将节省您大量的时间和精力。

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

这是一个奇怪的选择,因为您可以在调用库之前放置自己的SecurityManager,并且不能阻止您执行任何操作。

我认为您想要的是不可能的; 虽然有Dynamic Proxies ,但是它们使用反射,并且您似乎不太可能访问类加载器(可以在其中设置自己的字节字节操作)。

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

该代码将相对简单:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM