2014-05-20 107 views
14

我正在研究一个项目,其中有很多由库创建的对象,并且我无法访问这些对象的创建过程。是否有可能在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. 
} 

ExampleInterface是clazz中可能会或可能不会在编译时实现一个接口。

代码:

public interface ExampleInterface { 
    public void run(); 
} 

下面的代码是,我遇到了这个问题。看看下面的注意事项:

  1. run()当c是ExampleInterface一个实例只调用。
  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,并且我无法使用解决方法。 (参考this link如果你想知道为什么)。

代码:

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),我正在小心处理,而其他时间则不会。

Clazz实例不同,我总是在意鼠标是否被点击,并始终需要触发该事件。

在现实中,ExampleInterface实际上是以下几点:

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

要说清楚,你是不是在问匿名课程? http://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html –

+0

你可以用'new SomeInterface(){...}'在运行时创建一个实现接口的实例,但它赢得了' t是其他任何东西的实例(当然除了Object和超接口)。这听起来像你正在获取某个类的实例,只需要检查它是否实现了一个特定的接口,如果是的话,就用它做些什么。是对的吗? –

+0

不,不。参考我已经链接的其他问题,这可能会对我正在努力完成的事情有所了解。 代码'if(c extends ExampleInterface){'已被内部库调用。我需要确保如果Clazz在编译时没有实现'ExampleInterface',我可以强制它在运行时执行此操作。 – user3144349

回答

4

做你所说的其实是使用字节代码装备的唯一途径。您可以添加一个代理程序,它在加载之前更改要修改的clazz的字节代码。

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

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

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

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

+0

嗯,也许图书馆不是最好的词。我正在编写的代码是从另一个基于我正在使用的库的应用程序中调用的。如果宿主应用程序检测到代码中的某些内容,我提供它不喜欢(与安全管理器冲突),它不会编译和运行我的任何代码。 – user3144349

+0

@ user3144349如果您可以更改命令行,则可以执行任何操作。如果你不能改变命令行,你只限于你的框架允许你做的事情。这是拥有SecurityManager的关键。 ;) –

+0

我没有访问命令行:( – user3144349

0

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

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

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

的代码会相对简单:

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

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

相关问题