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

2024-01-08

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

以下片段是说明我的问题的一个很好的例子。

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是 Clazz 在编译时可能实现也可能不实现的接口。

Code:

public interface ExampleInterface {
    public void run();
}

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

  1. run()仅当 c 是以下实例时才被调用ExampleInterface.
  2. getRunConditions(Clazz c) and executeClazz(Clazz c)都是我无权访问的类中的私有方法。
  3. 在编译时,Clazz will not包含一个名为run().
  4. 示例执行者是not我的课。我无法以任何方式访问它 方式(我什至无法获得该类的实例)。

Code:

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) is 编造的语法我 创建是为了说明我的目标。
  2. run() must在这里定义(在运行时)。
  3. 我无法使用包装器或代理类作为解决方案。 IE,Clazz对象必须最终实现ExampleInterface,而且我无法使用解决方法。 (参考这个链接 https://stackoverflow.com/questions/23767885/how-to-check-when-a-method-is-run-in-another-class-in-java-method-call-listener如果你想知道为什么)。

Code:

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

澄清一下,我遇到的问题是我需要always知道什么时候run()被叫进Clazz. If Clazz从来没有实施ExampleInterface,我不知道什么时候run()应该被称为。

同时,我也想偶尔添加对run()当默认情况下不支持时。因为我无权访问创建Clazz对象,我无法通过自己实现接口来做到这一点。

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

NOTE:虽然唯一的解决方案可能需要反射(如果是这样,请将其发布在下面),但我正在使用的库有一个安全管理器,可以阻止所有反射的使用。 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 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();
}

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

正常运行会产生此错误:

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

现在我们可以将类传递给示例执行器

java -javaagent:agent.jar -classpath ..\instrumentation\bin com.sabertiger.example.ExampleExecutor
Hello world
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 是否可以在运行时实现接口? 的相关文章