63

一个类可以在运行时向自己添加一个方法(比如从一个static块中),这样如果有人在这个类上执行反射,他们会看到新方法,即使它没有在编译时定义?

背景:

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

我希望每个类都能够以编程方式生成doAction()具有各种参数的方法,并在检查时即时生成。方法的主体可以为空。

4

11 回答 11

59

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

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

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

我将其作为思考的食物,无法证明这是否会导致解决方案或我们是否有陷阱。

作为对这个问题的简单回答:不,我们不能像通过反射改变字段的内容那样改变加载的类。(我们也不能添加或删除字段)。

于 2011-07-13T14:53:32.233 回答
24

Andres_D 是对的,我们可以使用自定义类加载来很好地做到这一点,这里有一个关于如何做到这一点的详细指南:http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic。 html?page=1

本文解释了如何编写动态 Java 代码。它讨论了运行时源代码编译、类重载,以及使用代理设计模式对调用者透明的动态类进行修改。

事实上,奥地利的研究人员已经编写了一个 JVM,它甚至允许重新加载具有不同类型层次结构的类。他们通过使用现有线程保存点来生成对象的完整“侧面宇宙”及其所有相关引用和引用内容来实现这一点,然后一旦完全重新洗牌并进行所有必需的更改,只需交换所有更改的类。[1] 这里是他们项目的链接http://ssw.jku.at/dcevm/甲骨文赞助肯定会引起对未来计划的有趣猜测。

使用 Java 1.4 中引入的 JPDA 的热交换功能,在标准 Java VM 中已经可以对方法主体和字段进行较少侵入性的更改:
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 和 L. Stadler,“Java 的动态代码演进”,发表在第 8 届 Java 编程原理与实践国际会议上,维也纳,2010 年。

[2] M. Dmitriev,“面向 Java 语言应用程序运行时演化的灵活和安全技术”,OOPSLA 工程复杂面向对象系统演化研讨会,2001 年。

于 2012-09-14T23:26:57.537 回答
10

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

于 2011-07-13T14:41:44.670 回答
5

不,这在 Java 中(很容易)是不可能的。

听起来您正在尝试使用 Java,就好像它是一种动态编程语言一样。例如,Ruby 具有开放类:您可以在运行时从 Ruby 类中添加和删除方法。在 Ruby 中,您的类中还可以有一个“缺少方法”的方法,当您尝试调用该类中不存在的方法时,该方法将被调用。Java中也不存在这样的东西。

有一个运行在 JVM 上的 Ruby 版本,JRuby,它必须做一些非常困难的技巧才能使开放类在 JVM 上运行。

于 2011-07-13T14:42:47.940 回答
3

您可以拥有一个doAction方法来执行您希望生成的方法执行的任何操作。是否有需要生成它的原因或者它可以是动态的?

于 2011-07-13T14:42:56.797 回答
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();
        }
   }
}

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

于 2016-05-15T22:45:30.743 回答
2

我相信您需要一些字节码更改工具/框架,例如 asm、cglib 或 javassist。您可以像 Spring 一样通过方面/编织来实现这一点,但我相信您仍然需要首先定义该方法。

于 2011-07-13T14:45:31.960 回答
2

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

于 2013-12-27T15:19:43.953 回答
2

我的建议应该适用于您的情况: 1.您有一个现有的类 MyClass 和 n 个方法 2.您希望在编译另一个 .java 源文件时包含不在类中的 (n+1) 个方法

我解决它的方法是继承。为扩展第一个类 MyClass 的 Class MyClassPlusOne 创建一个新的 .java 源文件。编译此类并使用该对象。如何在运行时编译和部署 java 类?

class MyClassPlusOne extends MyClass
{
     void doAction(String a, Integer b)
     {
         int myNPlus1 = a+b;
         //add whatever you want before compiling this code
      }
}
于 2018-04-30T15:13:07.203 回答
1

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

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

于 2011-07-13T14:43:03.090 回答
0

这是一个相当老的问题,但我今天仍然发现自己在看它,所以以防万一,我会加两分钱。

如果您使用的是 Java 8+,则可以定义接口方法的“默认”实现,因此您只需定义具有空默认实现的所有额外方法的接口,并在所需的类中添加 implements 子句。在某些情况下,这种方法可能是最简单的方法。

如果您无法控制类的定义,或者您需要与较旧的 Java 版本兼容,您仍然可以定义一个包含所有必需的额外方法的接口;但在这种情况下,实现一个“装饰器”类,其方法接收要“装饰”的对象作为参数,并返回一个 DynamicProxy 实例,用此接口包装传递的对象。

如果您使用的是 Spring,则可以将装饰器作为 @Component 添加到上下文中,因此您可以在需要使用它的任何地方注入它。如果您需要注入的任何对象是 Spring Bean,您可以实现一个使用装饰器返回实例的 FactoryBean,这样您就可以忘记为它们显式调用装饰器。

于 2020-07-28T10:15:38.333 回答