如何将类的String表示形式转换为真正的对象?

时间:2014-12-04 16:06:05

标签: java string object

我正在编写一个需要命令对象的程序。 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表示转换为代码本身;因为我最初的问题。

1 个答案:

答案 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'));

在我看来,这样做的另一个主要缺点是代码不会受益于编译器检查,并且您当然无法通过调试器在该代码中设置断点。总而言之,调试和维护将是一件非常令人头疼的问题。