我正在编写一个需要命令对象的程序。 Command包含一个String作为其名称,一个AbstractAction表示Command实际执行的操作。此外,Command有一个方法init(),在程序的层次结构中使用更高的位置,用于实例化Command的使用变量(提供对GUI,网络等的访问),以及方法execute(),在特殊的Thread上执行AbstractAction。以下是创建和使用Command的示例:
Command c = new Command("Test",
new AbstractAction() {
public void actionPerformed(ActionEvent a) {
System.out.println("Hello world!");
}
});
此时,调用“c.execute();”将按预期打印出“Hello world!”。
我的目标是拥有一对带有值对的文本文件,可以对其进行解析以生成String名称和AbstractAction操作。完成后,另一个类将遍历找到的名称和操作,为每个类创建命令对象,并将它们添加到程序中的命令列表中,然后可以正常使用它们。
现在,我的问题是我读取了一个String,它表示上面私有的AbstractAction的主体 - 但是没有一种简单的方法可以将String实际转换为实际的AbstractAction对象。
一个潜在的想法是使用AbstractAction String表示创建一个临时java文件,编译它,从中创建一个新的AbstractAction,然后使用反射获得该引用,但这似乎有点过分。另一种方法是直接修改通过文件解析的文件的源代码,这样就可以写出AbstractAction的代码,但同样,这有点疯狂。
我已经尝试了一些其他实现,包括强制用户创建Command的子类,将他们的源放入一个特殊的程序文件夹,然后在初始化时创建命令,但这最终需要做很多工作。用户(许多冗余代码)。
请告诉我是否有更好的方法来实现我想要做的事情 - 或者如果有更简单的方法将源的字符串转换为内部对象,如上所述。
编辑1: 以下是文本文件的示例:
//Anything outside of quotes is a comment
"Foo", "System.out.println("Hello world!");"
"Bar", "network.sendOverAFile(new File("test.txt"));"
从这里开始,解析器(在启动时)将读取文件并提取“Foo”作为String名称,并且“System ...;”作为String动作。我需要将操作转换为AbstractAction主体中的代码,如上所述,在创建Command时。 Bar也是如此; Bar使用init()传递的变量之一。
至于我尝试的子类实现,用户必须创建自己的Command子类,并将其放入源文件夹中。子类看起来像这样:
public class TestCommand extends Command {
public TestCommand() {
super("Test", new AbstractAction() {
public void actionPerformed(ActionEvent a) {
System.out.println("Hello!");
}
});
}
}
然后将其放入源目录中,在所有其他子类Command中,并进行编译。解析器将遍历已编译的代码段,并将相关信息添加到数组中。每次执行Command时,解析器都会扫描所有名称的列表,如果匹配,则执行相关的AbstractAction。这可行,但涉及大量对外部类的引用(可能会使用几十个命令减慢程序速度),并且是制作插件的用户的两到三倍的工作量。结果,我觉得使用上面的文本文件技术要容易得多,但我不知道如何将代码的String表示转换为代码本身;因为我最初的问题。
答案 0 :(得分:0)
这听起来像是过度工程的情况。你真的在运行时需要这么大的灵活性吗,或者你只是有很多命令而你想要一个简单的方法在文件中引用它们?
如果是后者,则您的文本文件不需要包含代码;它只需要包含与该代码对应的符号标识符。这些标识符应作为枚举常量存在于代码中:
public enum Command { FOO, BAR }
您应该在代码中创建所有操作,并使用枚举常量作为键将这些操作放在Map中。然后,您的文件可以引用这些枚举常量的操作:
public List<Action> parseActions(Path file)
throws IOException {
List<Action> actions = new ArrayList<>();
try (BufferedReader reader =
Files.newBufferedReader(file, Charset.defaultCharset())) {
String line;
while ((line = reader.readLine()) != null) {
Command command = Command.valueOf(line);
Action action = getAction(command);
actions.add(action);
}
}
return actions;
}
private Map<Command, Action> allActions;
private Action getAction(Command command) {
Objects.requireNonNull(command, "Command cannot be null");
if (allActions == null) {
allActions = new EnumMap<>(Command.class);
allActions.put(Command.FOO, new AbstractAction() {
public void actionPerformed(ActionEvent event) {
System.out.println("Hello world!");
}
};
allActions.put(Command.BAR, new AbstractAction() {
public void actionPerformed(ActionEvent event) {
network.sendOverAFile(new File("test.txt"));
}
};
// Safety check
if (!allActions.keySet().containsAll(
EnumSet.allOf(Command.class))) {
throw new RuntimeException(
"Not every Command constant has an associated Action");
}
}
return allActions.get(command);
}
为了符合上述要求,您的文本文件将只包含:
FOO BAR
如果您确实需要可以从文本文件中读取的完全动态代码,请记住这是一个巨大的安全漏洞。实际上,它是代码注入的定义:任何人都可以在文件中放置任意代码(包括Runtime.getRuntime().exec("rd /s/q C:\\Windows\\System32")
或Runtime.getRuntime().exec("rm -rf ~")
之类的东西),程序很乐意运行它。
如果您仍然确定要这样做,那么您可能希望使用每个Java运行时附带的JavaScript引擎:
public List<Action> parseActions(Path file)
throws IOException {
List<Action> actions = new ArrayList<>();
final ScriptEngine engine =
new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("network", myNetwork);
try (BufferedReader reader =
Files.newBufferedReader(file, Charset.defaultCharset())) {
String line;
while ((line = reader.readLine()) != null) {
String[] nameAndCode = line.split("\\s+", 2);
String name = nameAndCode[0];
final String code = nameAndCode[1];
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent event) {
engine.eval(code);
}
};
actions.add(action);
}
}
return actions;
}
文件中的每一行都包含一个命令名,后跟JavaScript代码。所以它可能看起来像这样:
Foo importClass(java.lang.System); System.out.println('Hello world!'); Bar importClass(java.io.File); network.sendOverAFile(new File('test.txt'));
在我看来,这样做的另一个主要缺点是代码不会受益于编译器检查,并且您当然无法通过调试器在该代码中设置断点。总而言之,调试和维护将是一件非常令人头疼的问题。