Picocli: Отдельная ответственность за заполнение класса и выполнение приложения
Перед использованием cli у меня был бы начальный класс, который вызывает мой класс ApplicationPropertiesProvider (который читает мой файл свойств), а затем запускает бизнес-логику. Таким образом, произошло разделение, у ApplicationPropertiesProvider была только одна работа.
Теперь с picocli, руководство / документация заявляет, что я должен использовать CommandLine.run (objectToPopulate, args) или CommandLine.call (objectToPopulate, args). Поэтому класс, заполняемый параметрами cli (ApplicationPropertiesProvider), должен реализовывать Runnable или Callable. Теперь я могу просто вставить свой стартовый код класса Starter в метод run () или call () и отказаться от класса Starter. Но мне это не нравится, я хочу разделить класс, просто содержащий свойства, и мой класс Starter.
Вид грязного обходного пути, о котором я подумал и который показал в моем примере ниже, будет состоять в том, чтобы передать аргументы из метода main конструктору моего класса Starter, заполнить ApplicationPropertiesProvider с помощью CommandLine.run (), но реализовать только пустой run () или call (), так что он сразу же вернется в мой начальный класс, где я тогда начну бизнес-логику. Это был бы результат, о котором я прошу (разделение), но в таком случае это кажется действительно глупым.
Еще один вопрос, который только что возник: если у меня есть стандартный случай наличия нескольких классов, содержащих бизнес-код, а также их собственные свойства (вместо одного класса, предоставляющего свойство): возможно ли заполнить несколько разных классов одним вызовом cli, т.е. Вызов "test.jar command --a --b", где параметр "a" идет прямо к экземпляру класса "X", а "b" - к экземпляру "Y"?
public class Starter {
public static void main(String[] args) {
new Starter(args);
}
public Starter(String[] args) {
app = ApplicationPropertiesProvider.getInstance();
CommandLine.run(app, args);
//then kick off the business logic of the application
}
}
@Command(...)
public class ApplicationPropertiesProvider implements Runnable {
//annotated properties
@Option(...)
private String x;
@Override
public void run() { }
1 ответ
run
а также call
методы - это удобные методы, позволяющие приложениям сокращать свой стандартный код. Вам не нужно их использовать. Вместо этого вы можете использовать parse
или же parseArgs
метод. Это выглядит примерно так:
1 @Command(mixinStandardHelpOptions = true)
2 public class ApplicationPropertiesProvider { // not Runnable
3 //annotated properties
4 @Option(...)
5 private String x;
6 // ...
7 }
8
9 public class Starter {
10 public static void main(String[] args) {
11 ApplicationPropertiesProvider app = ApplicationPropertiesProvider.getInstance();
12 try {
13 ParseResult result = new CommandLine(app).parseArgs(args);
14 if (parseResult.isUsageHelpRequested()) {
15 cmd.usage(System.out);
16 } else if (parseResult.isVersionHelpRequested()) {
17 cmd.printVersionHelp(System.out);
18 } else {
19 new Starter(app); // run the business logic
20 }
21 } catch (ParameterException ex) {
22 System.err.println(ex.getMessage());
23 ex.getCommandLine().usage(out, ansi);
24 }
25 }
26
27 public Starter(ApplicationPropertiesProvider app) {
28 // kick off the business logic of the application
29 }
30 }
Это нормально, просто строки 11-24 представляют собой шаблонный код. Вы можете опустить это и позволить picocli сделать эту работу за вас, позволив аннотированному объекту реализовать Runnable или Callable.
Я понимаю вашу точку зрения о разделении интересов и имею различные классы для бизнес-логики и класса, который обладает свойствами. У меня есть предложение, но сначала позвольте мне ответить на ваш секундный вопрос:
Можно ли заполнить несколько разных классов одним вызовом cli?
Picocli поддерживает "Mixins", которые позволяют вам это делать. Например:
class A {
@Option(names = "-a") int aValue;
}
class B {
@Option(names = "-b") int bValue;
}
class C {
@Mixin A a;
@Mixin B b;
@Option(names = "-c") int cValue;
}
// populate C's properties as well as the nested mixins
C c = CommandLine.populate(new C(), "-a=11", "-b=22", "-c=33");
assert c.a.aValue == 11;
assert c.b.bValue == 22;
assert c.cValue == 33;
Теперь давайте соберем все это вместе:
class A {
@Option(names = "-a") int aValue;
@Option(names = "-b") int bValue;
@Option(names = "-c") int cValue;
}
class B {
@Option(names = "-x") int xValue;
@Option(names = "-y") int yValue;
@Option(names = "-z") int zValue;
}
class ApplicationPropertiesProvider {
@Mixin A a;
@Mixin B b;
}
class Starter implements Callable<Void> {
@Mixin ApplicationPropertiesProvider properties = ApplicationPropertiesProvider.getInstance();
public Void call() throws Exception {
// business logic here
}
public static void main(String... args) {
CommandLine.call(new Starter(), args);
}
}
Это дает вам разделение интересов: свойства расположены в ApplicationPropertiesProvider
бизнес-логика в Starter
учебный класс. Это также позволяет группировать свойства, которые логически принадлежат друг к другу, в отдельные классы, вместо того, чтобы иметь единый полигон в ApplicationPropertiesProvider
,
Starter
реализует класс Callable
; это позволяет вам опустить шаблонную логику выше и запустить ваше приложение в одной строке кода в main
,