Необъяснимое поведение Singleton/Picocli

Я пишу код и не могу понять, что происходит с моей ошибкой. Я надеюсь, что кто-то здесь может дать мне некоторые ответы. Вот мой код (соответствующая часть):

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-вызовы предназначены для отладки. Это поведение, которое я не могу объяснить:

Когда я запускаю свое приложение, например. "-h" в качестве аргумента, я отлично печатаю справку. System.out.println показывает, что синглтон создан и что set_validate() вызывается со значением по умолчанию. Но это меняется, когда я использую --validate в качестве аргумента.

По какой-то причине конструктор и набор по умолчанию вызываются два раза подряд. После этого set_validate() вызывается с true (как и должно быть). Однако кажется, что первый вызов задает статическую переменную экземпляра, а последний вызов с истинным значением выполняется во втором экземпляре (моя теория). Как следствие, когда я проверяю состояние _validate с помощью validate() в моем экземпляре singleton из моего основного метода (в другом классе), я получаю false, поскольку оно не было установлено в правильном экземпляре.

Я использовал поисковую систему, чтобы проверить:

  1. Конструктор не вызывается нигде, кроме экземпляра статического синглтона (как и ожидалось, поскольку он приватный).
  2. _validate нигде не доступен, кроме кода, который я разместил.
  3. set_validate() нигде не вызывается. Только Пикокли называет это.

Я не знаю, что проверить дальше. Любые идеи?

С уважением

Торстен

РЕДАКТИРОВАТЬ: AppData является одним из нескольких классов, содержащих данные. Все они собраны в один большой класс для Picocli вот так:

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

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

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

Он используется так в моем основном методе:

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

person Thorsten Schmitz    schedule 08.05.2020    source источник
comment
если ваш конструктор генерирует трассировку стека и записывает ее в консоль, вы должны видеть, что вызывает его всякий раз, когда вызывается конструктор. может быть, это даст вам подсказку.   -  person Nathan Hughes    schedule 08.05.2020
comment
Я не знаком с Picoli, но кажется, что он должен создать свой собственный экземпляр аннотированного класса, даже если у него есть частный конструктор. Интересно, что произойдет, если вы реализуете одноэлементный шаблон, используя перечисление вместо класса со статическим полем.   -  person David Conrad    schedule 08.05.2020
comment
@NathanHughes Я пробовал. Насколько я могу судить, это показывает, что второй instacne создается с использованием отражения, как говорит ответ Ремко.   -  person Thorsten Schmitz    schedule 13.05.2020


Ответы (1)


Я подозреваю (но могу только догадываться, так как эта часть кода не показана), что AppData либо является подкомандой другой команды, либо приложение использует пикокли вот так:

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 аннотация для внедрения модели picocli и вызова для нее CommandSpec::userObject() getter для получения экземпляра AppData.)

Теперь другой вопрос: почему метод set_validate вызывается дважды?

Начиная с версии 4.2, picocli сначала вызывает @Option-аннотированные методы со значением по умолчанию, прежде чем анализировать параметры командной строки. Таким образом, метод set_validate сначала вызывается со значением по умолчанию, а затем снова вызывается со значением, указанным в командной строке.

(Начиная с версии 4.3 (которая скоро будет выпущена) значение по умолчанию устанавливается только в том случае, если значение не указан в командной строке, поэтому, начиная с версии 4.3, метод set_validate будет вызываться только один раз.)

person Remko Popma    schedule 09.05.2020
comment
Спасибо за Ваш ответ. Я посмотрел на трассировку стека, как было предложено, и это показывает, что используется отражение. Но на самом деле я передаю объект в Picocli. Я отредактирую свой вопрос, чтобы добавить эту информацию. - person Thorsten Schmitz; 13.05.2020
comment
Спасибо. Stacktrace также может быть полезен. Небольшая работающая программа, которая воспроизводит проблему, была бы идеальной. - person Remko Popma; 13.05.2020