Java类可以在运行时向自己添加方法吗?

时间:2011-07-13 14:38:11

标签: java reflection code-generation javassist

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

背景

我正在使用的框架需要按照惯例定义具有Action方法的doAction(...)类。框架在运行时检查这些类,以查看其doAction()方法中可用的参数类型。例如:doAction(字符串 a,整数 b)

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

11 个答案:

答案 0 :(得分:52)

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

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

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

    我把它留作思考的食物,无法证明,如果这会导致解决方案或我们有陷阱。

    作为问题的简单答案:不,我们无法更改已加载的类,因为我们可以使用反射更改字段的内容。 (我们也不能添加或删除字段。)

答案 1 :(得分:21)

Andres_D是对的,我们可以使用自定义类加载来完成,下面是有关如何执行此操作的详细指南:http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=1

  

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

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

使用Java 1.4中引入的JPDA热插拔功能,标准Java VM中已经可以对方法体和字段进行较少的干扰性更改:
docs.oracle.com/javase/1.4.2/docs/guide/jpda/enhancements.html#hotswap

我不确定它是否是第一个,但是这个2001年Sun员工的论文似乎是提到HotSpot到Hot Swap功能的早期提案之一。 [2]

<强>参考

[1]T.Würthinger,C。Wimmer和L. Stadler,“Java的动态代码演化”,在2010年维也纳举行的第八届Java编程原理与实践国际会议上发表。

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

答案 2 :(得分:8)

我从未尝试过与我自己相似的事情,但你应该看看ASMcglibJavassist

答案 3 :(得分:4)

不,这在Java中是不可能的。

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

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

答案 4 :(得分:2)

您可以使用doAction方法执行您希望生成的方法执行的任何操作。是否需要生成它或者它是否是动态的?

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

我解决它的方法是继承。为扩展第一个类MyClass的类MyClassPlusOne创建一个新的.java源文件。编译此类并使用该对象。 How can I compile and deploy a java class at runtime?

class MyClassPlusOne extends MyClass
{
     void doAction(String a, Integer b)
     {
         int myNPlus1 = a+b;
         //add whatever you want before compiling this code
      }
}

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

看起来似乎无法动态添加方法。但是,您可以使用方法列表或类似哈希值来准备一个类:

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

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

答案 10 :(得分:0)

这是一个比较老的问题,但是我今天仍然发现自己在看这个,所以,以防万一,我加两美分。

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

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

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