Java类能否在运行时添加方法?

65

一个类能否在运行时添加方法(例如从static块),以便如果有人对该类执行反射操作,他们将看到新的方法,即使它在编译时未定义?

背景:

我正在使用的框架期望定义一个doAction(...)方法的Action类,并按约定使用。框架在运行时检查这些类,以查看其doAction()方法中可用的参数类型。例如:doAction(String a, Integer b)。

我希望每个类都能够在运行时生成其带有各种参数的doAction()方法,仅在检查时生成。该方法的主体可以为空。


2
但是如果可以的话,那就太棒了! :) - Nicholas
是的,这是可能的。请查看此问题:https://dev59.com/hXPYa4cB1Zd3GeqPnMY8 - Mehdi
1
我了解到AOP可以用于诸如日志记录之类的事情,但OP要求框架根据方法的存在来识别某种类型的处理类。换句话说,AOP能否用于添加方法签名? - Quaternion
11个回答

62

这并不简单。一旦一个类被类加载器加载,就没有办法更改已加载类的方法。当请求一个类时,类加载器会将其加载并链接它。而且(使用Java)无法更改链接的代码或添加/删除方法。

唯一的技巧是玩弄类加载器。如果我们删除自定义类加载器,那么由该类加载器加载的类也应该被删除或无法访问。我想到的想法是:

  1. 实现一个自定义类加载器
  2. 用该自定义类加载器加载动态类
  3. 如果我们有此类的更新版本
  4. 删除自定义类加载器和
  5. 使用新的自定义类加载器实例加载此类的新版本

我将其留作思考的食物,无法证明,它是否导致解决方案或是否存在陷阱。

简短地回答问题:不能像使用反射更改字段内容一样更改已加载的类。(我们也无法添加或删除字段)。


2
有人尝试过并在实践中使其工作了吗? - Norswap
1
当然你可以这样做,但是你还需要重新加载所有编译依赖于此类的类。 - Stefan Reich
2
这是一个很好的方法。对于初学者来说可能有些复杂。 - Prateek Shankar

25
安德烈斯·D是正确的,我们可以使用自定义类加载器来实现这一点,以下是如何实现的详细指南:http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=1 该文章介绍了如何编写动态Java代码,讨论了运行时源代码编译、类重新加载以及使用代理设计模式使对动态类的修改对其调用者透明的方法。
实际上,奥地利的研究人员已经编写了一个JVM,甚至允许重新加载具有不同类型层次结构的类。他们通过使用现有线程安全点生成对象及其相关引用和引用内容的完整“侧向宇宙”,一旦所有所需更改都被完全重组,就可以交换所有更改后的类。 [1] 这里是链接到他们项目的网址:http://ssw.jku.at/dcevm/,甲骨文的赞助确实为未来计划带来了有趣的推测。
在标准的Java虚拟机中,使用JPDA的热插拔功能对方法体和字段进行的更少侵入式的更改已经是可能的,这是在Java 1.4中引入的:
docs.oracle.com/javase/1.4.2/docs/guide/jpda/enhancements.html#hotswap 我不确定它是否是第一个,但这位Sun员工在2001年发表的论文似乎是最早提出HotSpot到Hot Swap功能的提案之一。[2] 参考资料 [1] T. Würthinger, C. Wimmer, and L. Stadler, “Dynamic Code Evolution for Java,” presented at the 8th International Conference on the Principles and Practice of Programming in Java, Vienna, 2010.
[2] M. Dmitriev, “Towards flexible and safe technology for runtime evolution of java language applications,” in OOPSLA Workshop on Engineering Complex Object-Oriented Systems for Evolution, 2001.

http://www.javaworld.com:页面未找到。 - Sam Ginrich

10

我自己从未尝试过类似的东西,但你应该看看 ASMcglibJavassist


2
正如其他答案中提到的那样,还需要一个自定义类加载器。 - Bodey Baker
@Bodey Baker 我很确定,他们使用了一个自定义类加载器,就像这个 https://github.com/Sanjay007/CustomLoader/blob/master/src/test/main/MyClassLoader.java - Sam Ginrich

5
不,Java不支持(轻松地)进行此操作。听起来你正在尝试将Java用作动态编程语言。例如,Ruby有开放类:可以在运行时向Ruby类添加和删除方法。在Ruby中,您还可以在类中拥有一个“方法丢失”方法,当您尝试调用不存在于类中的方法时,该方法将被调用。这样的功能在Java中也不存在。有一个在JVM上运行的Ruby版本,JRuby,它必须使用非常困难的技巧才能使开放类在JVM上工作。

可以使用字节码操作库(例如ASM)轻松实现。 - Angsuman Chakraborty
1
@AngsumanChakraborty 我不会说那很“容易”。你必须理解什么是字节码以及它是如何工作的。关键是Java没有像Ruby的开放类这样的标准功能。 - Jesper
使用与Java紧密集成的Groovy也同样容易实现。 - Angsuman Chakraborty

3

看起来似乎没有办法动态添加方法。但是你可以准备一个带有方法列表或哈希的类,例如:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;

public class GenericClass {
    private HashMap<String, Method> methodMap = new HashMap<String, Method>();

    public Object call(String methodName,Object ...args) 
               throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Method method = methodMap.get(methodName);
        return method.invoke(null, args);
    }

    public void add(String name,Method method){
        if(Modifier.isStatic(method.getModifiers()))
            methodMap.put(name, method);
    }

    public static void main(String[] args) {
        try   {
            GenericClass task = new GenericClass();
            task.add("Name",Object.class.getMethod("Name", new Class<?>[0]));
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
   }
}

然后,使用反射可以设置或取消属性。

3

你可以有一个doAction方法,该方法执行您希望生成的方法执行的任何操作。它需要被生成吗,或者可以是动态的?


2
我建议的解决方案适用于您的情况: 1. 您有一个已存在的类MyClass,其中包含n个方法 2. 您想要在编译另一个.java源文件时包含第(n+1)个方法,但该方法不在该类中
我的解决方法是继承。创建一个新的.java源文件,命名为MyClassPlusOne,并将其扩展到第一个类MyClass。编译此类并使用对象。 如何在运行时编译和部署Java类?
class MyClassPlusOne extends MyClass
{
     void doAction(String a, Integer b)
     {
         int myNPlus1 = a+b;
         //add whatever you want before compiling this code
      }
}

2

我相信你需要一些字节码修改工具/框架,比如asm、cglib或javassist。你可以通过像Spring那样的方面/编织来实现这一点,但我认为你仍然需要先定义方法。


2
代理可能会有所帮助。但每次想要添加或删除方法时都必须实例化一个代理。

1

我不确定这是否可能。但是,您可以使用AspectJ、ASM等工具,并将这些方法编织到适当的类中。

另一种选择是使用组合来包装目标类并提供doAction方法。在这种情况下,您最终会委托给目标类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接