一个类可以在运行时为自己添加一个方法(如从static
块),这样,如果有人在这个类上执行反射,他们会看到新的方法,即使它没有在编译时定义?
背景:
我正在使用的框架期望定义具有doAction(...)
方法的Action
类。框架在运行时检查这些类,看看它们的doAction()
方法中有什么类型的参数可用。例如:doAction(String a, Integer b)
我希望每个类都能够以编程方式生成具有各种参数的doAction()
方法,在检查时及时。方法体可以为空。
这并不简单。类装入器装入类后,就无法更改装入类的方法。当一个类被请求时,一个类加载器将加载它并链接它。并且没有办法(使用Java)更改链接的代码或添加/删除方法。
我想到的唯一技巧是使用类加载器。如果我们删除自定义类加载器,那么由该类加载器加载的类也应该被删除或不可访问。我想到的一个主意是
- 实现一个自定义类加载器
- 用自定义类加载器 加载动态类
- 如果我们有这个类的更新版本,
- 删除自定义类加载器和
- 使用自定义类加载器的新实例加载这个类的新版本
我把它留给思考,不能证明,如果这导致一个解决方案,或者如果我们有陷阱。
作为对这个问题的简单回答:不,我们不能像用反射改变字段的内容那样改变一个已加载的类。(我们也不能添加或删除字段)。
Andres_D是对的,我们可以很好地使用自定义类加载,这里是如何做到这一点的详细指南:http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=1
本文解释了如何编写动态Java代码。它讨论了运行时源代码编译、类重新加载以及使用Proxy设计模式对动态类的修改对其调用者透明。
事实上,奥地利的研究人员已经编写了一个JVM,甚至允许重载具有不同类型层次结构的类。他们通过使用现有的线程保存点来生成一个对象的完整的"侧面宇宙",以及所有相关的引用和引用内容,然后在所有需要的更改中完全重新洗牌,简单地交换所有更改的类。[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
我不确定这是不是第一个,但是这个Sun员工2001年的论文似乎是最早提到热点到热插拔功能的建议之一。[2]
参考[1] T. wrthinger, C. Wimmer, L. Stadler,"Java的动态代码进化",发表于第8届Java编程原则与实践国际会议,维也纳,2010。
[2] M. Dmitriev,"面向java语言应用程序运行时演化的灵活安全技术",《面向对象系统工程》,2001。
我自己从来没有尝试过类似的东西,但是您应该看看ASM、cglib和Javassist。
不,这在Java中是不可能的。
听起来你好像在尝试使用Java,好像它是一种动态编程语言。例如,Ruby有开放类:您可以在运行时从Ruby类中添加和删除方法。在Ruby中,您还可以在类中使用"方法缺失"方法,当您尝试调用类中不存在的方法时,将调用该方法。这样的事情在Java中也不存在。
有一个在JVM上运行的Ruby版本,JRuby,它必须做一些非常困难的技巧才能使开放类在JVM上工作。
你可以有一个doAction
方法,它做任何你想生成的方法做的事情。是否存在需要生成它的原因,或者它是否可以是动态的?
看起来没有办法动态添加方法。但是,您可以使用方法列表或散列来准备一个类,例如:
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();
}
}
}
>
我相信你需要一些字节代码修改工具/框架,如asm, cglib或javassist。您可以通过方面/编织实现这一点,就像在Spring中那样,但我认为您仍然需要首先定义方法。
代理可能有帮助。但是每次要添加或删除方法时都必须实例化代理。
我的建议应该适用于你的情况:1. 你有一个现有的类MyClass,它有n个方法2. 在另一个.java源文件
中编译时,要包含(n+1)不在类中的方法我解决这个问题的方法是继承。为扩展第一个类MyClass的类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
}
}
我不确定这是可能的。但是,您可以使用AspectJ、ASM等,并将这些方法编织到适当的类中。
另一种选择是使用组合来包装目标类并提供doAction方法。在这种情况下,您最终将委托给目标类。
这是一个相当古老的问题,但我今天仍然发现自己在看它,所以,以防万一,我将补充我的两分意见。
如果你使用Java 8+,你可以定义"default"接口方法的实现,因此您可以仅用带有空缺省实现的所有额外方法定义接口,并在所需的类中添加implements子句。在某些情况下,这种方法可能是最简单的方法。
如果你不能控制类的定义,或者你需要与旧的Java版本兼容,你仍然可以定义一个包含所有必要的额外方法的接口;但在本例中,实现一个decorator;类的方法接收要"decorate"的对象。作为参数,并返回一个DynamicProxy实例,用这个接口包装传递的对象。
如果你使用的是Spring,那么装饰器可以作为@Component添加到上下文中,这样你就可以在任何需要使用它的地方注入它。如果需要注入的任何对象都是Spring bean,那么可以实现一个使用装饰器返回实例的FactoryBean,这样就不用为它们显式调用装饰器了。