【问题标题】:Unexplainable Singleton / Picocli behaviour无法解释的 Singleton / Picocli 行为
【发布时间】:2020-05-08 15:04:11
【问题描述】:

我正在编写一些代码,但我无法弄清楚我的错误是怎么回事。我希望这里有人可以给我一些答案。这是我的代码(相关部分):

public class AppData implements Callable<Integer> {
    private static AppData appData = new AppData();

    private AppData() {
        System.out.println("AppData-Constructor");
    }

    public static AppData getInstance() {
        return appData;
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        return 0;
    }

    private boolean _validate;

    public boolean validate() {
        return _validate;
    }

    @Option(names = { "--validate" }, description = "", defaultValue = "false", hidden = false, interactive = false, paramLabel = "", required = false, type = boolean.class)
    public void set_validate(boolean validate) {
        System.out.println("Set Validate: " + validate);
        this._validate = validate;

        if(validate)
        {
            System.out.println("\nBeginne Programmvalidierung\n");
            Path tmp = null;
            try {
                // Doing some validation stuff
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

如您所见,我的班级是单身人士。注释来自 picoli,我用它来解析命令行参数。 System.out-calls 用于调试。这是我无法解释的行为:

当我启动我的应用程序时,例如“-h”作为参数,我可以很好地打印帮助。 System.out.println显示单例已创建,set_validate() 使用默认值调用。但是当我使用 --validate 作为参数时,情况会发生变化。

由于某种原因,构造函数和默认集连续调用了两次。之后,set_validate()true 调用(应该如此)。但是,似乎第一次调用设置了静态实例变量,而最后一次调用 true 是在第二个实例上完成的(我的理论)。结果,当我从我的主方法(在另一个类中)检查我的单例实例上的 _validatevalidate() 的状态时,我得到了错误,因为它没有在正确的实例中设置。

我用搜索引擎查了一下:

  1. 除了静态单例实例(正如预期的那样,因为它是私有的)之外,不会在任何地方调用构造函数。
  2. _validate 除了我发布的代码外,其他任何地方都无法访问。
  3. set_validate() 不会在任何地方调用。只有 Picocli 调用它。

我不知道接下来要检查什么。有什么想法吗?

问候

索斯滕

编辑: AppData 是保存数据的多个类之一。它们都被收集在 Picocli 的一个大类中,如下所示:

class Data
{
    @AddGroup(...)
    AppData appData = AppData.getInstance();

    @AddGroup(...)
    FooData fooData = FooData.getInstance();

    @AddGroup(...)
    BarData barData = BarData.getInstance();
}

在我的 main 方法中是这样使用的:

Data data = new Data();
CommandLine cmd = new CommandLine(data);
cmd.parseArgs(args);

【问题讨论】:

  • 如果您让构造函数生成堆栈跟踪并将其写入控制台,那么无论何时调用构造函数,您都应该能够看到调用它的内容。也许这会给你一个提示。
  • 我不熟悉 Picoli,但它似乎必须构造自己的注释类实例,即使它有一个私有构造函数。我想知道如果您使用枚举而不是具有静态字段的类来实现单例模式会发生什么。
  • @NathanHughes 我试过了。据我所知,它表明第二个 instacne 是使用反射创建的,就像 Remko 的回答所说的那样。

标签: java singleton picocli


【解决方案1】:

我怀疑(但只能猜测,因为这部分代码没有显示)AppData 要么是另一个命令的子命令,要么应用程序像这样使用 picocli:

int exitCode = new CommandLine(AppData.class).execute(args);

在这两种情况下,picocli 都会使用反射创建AppData 的实例。 picocli 创建的实例是从命令行值填充的实例。这是与AppData::getInstance 返回的实例不同的实例。

确保只有一个实例的一种方法是将单例实例传递给 picocli。例如:

AppData singleton = AppData.getInstance();
int exitCode = new CommandLine(singleton).execute(args);

System.out.println("validate=" + singleton.validate());

(如果AppData 是子命令,则还有其他方法可以访问 picocli 创建的实例,例如 @Spec annotation 注入 picocli 模型,并在其上调用 CommandSpec::userObject() getter 以获取 AppData 实例。 )

现在,另一个问题是:为什么set_validate 方法会被调用两次?

从 4.2 版本开始,picocli 将首先使用默认值调用 @Option-annotated 方法,然后再解析命令行参数。所以set_validate方法先用默认值调用,然后用命令行指定的值再次调用。

(从4.3版开始(即将发布),命令行默认值为only be set if the value is not specified,所以从4.3版开始,set_validate方法只会被调用一次。)

【讨论】:

  • 感谢您的回答。我按照建议查看了堆栈跟踪,这表明正在使用反射。但我实际上将一个对象传递给 Picocli。我将编辑我的问题以添加此信息。
  • 谢谢。堆栈跟踪也可能有用。一个重现问题的小型可运行程序将是理想的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-22
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多