Рубрики
Без рубрики

Создание командной программы Java с picocli

Узнайте, как использовать Picocli для создания программ командной строки на Java.

Автор оригинала: François Dupire.

Создание командной программы Java с picocli

1. Введение

В этом учебнике мы подойдем к пикокли библиотечный , что позволяет нам легко создавать программы командной строки на Java.

Сначала мы начат с создания команды Hello World. Затем мы глубоко погрузимся в ключевые особенности библиотеки, частично воспроизвет git команда.

2. Привет Мировому Командованию

Начнем с чего-то простого: команда Hello World!

Во-первых, мы должны добавить зависимости от пикокли проект :


    info.picocli
    picocli
    3.9.6

Как мы видим, мы будем использовать 3.9.6 версия библиотеки, хотя и 4.0.0 версия находится в стадии строительства (в настоящее время доступна в альфа-тесте).

Теперь, когда зависимость настроена, давайте создадим нашу команду Hello World. Для этого Мы будем использовать @Command аннотация из библиотечного :

@Command(
  name = "hello",
  description = "Says hello"
)
public class HelloWorldCommand {
}

Как видим, аннотация может принимать параметры. Мы используем только два из них здесь. Их цель заключается в предоставлении информации о текущей команде и тексте для автоматического сообщения о помощи.

На данный момент, мы мало что можем сделать с этой командой. Чтобы заставить его сделать что-то, мы должны добавить основные метод вызова удобство CommandLine.run (Runnable, String) метод . Это требует двух параметров: экземпляр нашей команды, которая, таким образом, должна Бег интерфейс и Струнные массив, представляющий аргументы команды (варианты, параметры и подкоманды):

public class HelloWorldCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new HelloWorldCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}

Теперь, когда мы запускаем основные метод, мы увидим, что консоль выводит “Здравствуйте, мир!”

При упаковке в банку , мы можем запустить нашу команду Hello World, используя Ява команда:

java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand

Без удивления, что также выводит “Здравствуйте, мир!” строка к консоли.

3. Конкретный случай использования

Теперь, когда мы увидели основы, мы глубоко погружаемся в пикокли библиотека. Для того, чтобы сделать это, мы собираемся воспроизвести, частично, популярная команда: git .

Конечно, цель не будет в том, чтобы реализовать git командное поведение, но воспроизвести возможности git команда – какие подкоманды существуют и какие варианты доступны для своеобразного подкоманды.

Во-первых, мы должны создать GitCommand класс, как мы сделали для нашей команды Hello World:

@Command
public class GitCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new GitCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("The popular git command");
    }
}

4. Добавление подкоманд

git команда предлагает много подкомандыдобавить, совершить, удаленный , и многое другое. Мы сосредоточимся здесь на добавить и совершить .

Итак, нашей целью здесь будет объявить эти два подкоманды главному командованию. Пикокли предлагает три способа достижения этой цели.

4.1. Использование @Command аннотации к классам

@Command аннотация дает возможность регистрировать подкоманды через подкоманды параметр :

@Command(
  subcommands = {
      GitAddCommand.class,
      GitCommitCommand.class
  }
)

В нашем случае мы добавляем два новых класса: GitAddCommand и GitCommitCommand . Оба аннотированы @Command и осуществлять Бег . Важно дать им имя, так как имена будут использоваться пикокли признать, какой подкоманд (ы) для выполнения:

@Command(
  name = "add"
)
public class GitAddCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Adding some files to the staging area");
    }
}

@Command(
  name = "commit"
)
public class GitCommitCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Committing files in the staging area, how wonderful?");
    }
}

Таким образом, если мы запускаем нашу главную команду с добавить в качестве аргумента консоль будет “Добавление некоторых файлов в постановочную область” .

4.2. Использование @Command аннотации о методах

Еще один способ объявить подкоманды заключается в том, чтобы создать @Command аннотированные методы, представляющие эти команды в GitCommand класс :

@Command(name = "add")
public void addCommand() {
    System.out.println("Adding some files to the staging area");
}

@Command(name = "commit")
public void commitCommand() {
    System.out.println("Committing files in the staging area, how wonderful?");
}

Таким образом, мы можем непосредственно реализовать нашу бизнес-логику в методах, а не создавать отдельные классы для ее обработки.

4.3. Добавление подкоманд программно

Наконец, пикокли предлагает нам возможность зарегистрировать наши подкоманды программно. Это немного сложнее, так как мы должны создать Командная линия объект обертывания нашей команды, а затем добавить подкоманды к нему:

CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.addSubcommand("add", new GitAddCommand());
commandLine.addSubcommand("commit", new GitCommitCommand());

После этого, мы все еще должны запустить нашу команду, но мы не можем воспользоваться этой CommandLine.run () метод больше . Теперь мы должны позвонить в parseWithHandler () метод на нашем недавно созданном C ommandLine объект:

commandLine.parseWithHandler(new RunLast(), args);

Следует отметить использование Бег последний класс, который рассказывает пикокли для запуска наиболее конкретного подкоманды. Есть два других обработчика команд, предоставленных пикокли : RunFirst и RunAll . Первый выполняет верхнюю команду, в то время как последний выполняет все из них.

При использовании метода удобства CommandLine.run () , Бег последний обработчик используется по умолчанию.

5. Управление опциями с использованием @Option аннотации

5.1. Вариант без аргументов

Давайте теперь посмотрим, как добавить некоторые варианты в наши команды. Более того, мы хотели бы рассказать о своей добавить команда, что он должен добавить все измененные файлы. Для достижения этой цели, Мы добавим поле, аннотированное с @Option аннотация к нашему GitAddCommand класс:

@Option(names = {"-A", "--all"})
private boolean allFiles;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    } else {
        System.out.println("Adding some files to the staging area");
    }
}

Как мы видим, аннотация занимает некоторое имена параметр, который дает различные названия опции. Поэтому, называя добавить команды с любой или – все установит allFiles от поля к истинное . Таким образом, если мы забудем команду с опцией, консоль покажет “Добавление всех файлов в постановочную область” .

5.2. Вариант с аргументом

Как мы только что видели, для вариантов без аргументов их присутствие или отсутствие всегда оценивается булеан ценность.

Тем не менее, можно зарегистрировать варианты, которые принимают аргументы. Мы можем сделать это, просто объявив наше поле другого типа. Давайте добавим сообщение вариант для нашего совершить команда:

@Option(names = {"-m", "--message"})
private String message;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (message != null) {
        System.out.println("The commit message is " + message);
    }
}

Неудивительно, что при этом сообщение опция, команда покажет сообщение коммит на консоли. Позже в статье мы покроем, какие типы обрабатываются библиотекой и как обращаться с другими типами.

5.3. Вариант с несколькими аргументами

Но теперь, что, если мы хотим, чтобы наша команда принимать несколько сообщений, как это делается с реальными git коммит команда? Не беспокойтесь, Давайте будем делать наше поле массив или Коллекционая , и мы в значительной степени сделали:

@Option(names = {"-m", "--message"})
private String[] messages;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (messages != null) {
        System.out.println("The commit message is");
        for (String message : messages) {
            System.out.println(message);
        }
    }
}

Теперь мы можем использовать сообщение опция несколько раз:

commit -m "My commit is great" -m "My commit is beautiful"

Тем не менее, мы могли бы также дать возможность только один раз и отделить различные параметры regex делимитатора. Таким образом, мы можем использовать разделенные параметр @Option аннотация:

@Option(names = {"-m", "--message"}, split = ",")
private String[] messages;

Теперь мы можем пройти -м “Мой коммит велик”,”Мой коммит прекрасен” для достижения того же результата, что и выше.

5.4. Необходимый вариант

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

@Option(names = {"-m", "--message"}, required = true)
private String[] messages;

Теперь это невозможно назвать совершить команды без указания сообщение выбор. Если мы попытаемся это сделать, пикокли напечатает ошибку:

Missing required option '--message='
Usage: git commit -m= [-m=]...
  -m, --message=

6. Управление позиционными параметрами

6.1. Параметры захвата позиций

Теперь давайте сосредоточимся на наших добавить команды, потому что это не очень мощный еще. Мы можем только решить добавить все файлы, но что, если мы хотим добавить конкретные файлы?

Мы могли бы использовать другой вариант для этого, но лучшим выбором здесь было бы использование позиционных параметров. Действительно, позиционные параметры предназначены для захвата командных аргументов, которые занимают определенные позиции и не являются ни подкомандами, ни вариантами.

В нашем примере это позволило бы нам сделать что-то вроде:

add file1 file2

Для захвата позиционных параметров Мы будем использовать @Parameters аннотация :

@Parameters
private List files;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    }

    if (files != null) {
        files.forEach(path -> System.out.println("Adding " + path + " to the staging area"));
    }
}

Теперь, наша команда из ранее будет печатать:

Adding file1 to the staging area
Adding file2 to the staging area

6.2. Захват подмножества позиционных параметров

Можно быть более мелкозернистой о том, какие позиционные параметры для захвата, благодаря индекс параметр аннотации. Индекс основан на нуле. Таким образом, если мы определим:

@Parameters(index="2..*")

Это позволит захватить аргументы, которые не соответствуют варианты или подкоманды, от третьего до конца.

Индекс может быть либо диапазоном, либо единым числом, представляющим одну позицию.

7. Слово о преобразовании типа

Как мы видели ранее в этом учебнике, пикокли обрабатывает некоторое преобразование типа само по себе. Например, он отображает несколько значений для массивы или Коллекции , но он также может сопоставить аргументы с конкретными типами, как, когда мы используем Путь класс для добавить команда.

По сути пикокли поставляется с куча предварительно обработанных типов . Это означает, что мы можем использовать эти типы непосредственно без необходимости думать о преобразовании их сами.

Тем не менее, нам может потребоваться сопоставить наши аргументы команды с типами, не такими, как те, которые уже обработаны. К счастью для нас, это возможно благодаря ITypeConverter интерфейс и CommandLine-registerКонвертер метод, который связывает тип с конвертером .

Давайте представим, что мы хотим добавить конфиг подкоманда нашей git команды, но мы не хотим, чтобы пользователи изменили элемент конфигурации, которого не существует. Итак, мы решили сопоставить эти элементы с энумом:

public enum ConfigElement {
    USERNAME("user.name"),
    EMAIL("user.email");

    private final String value;

    ConfigElement(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }

    public static ConfigElement from(String value) {
        return Arrays.stream(values())
          .filter(element -> element.value.equals(value))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("The argument " 
          + value + " doesn't match any ConfigElement"));
    }
}

Плюс, в нашей недавно созданной GitConfigCommand класса, добавим два позиционных параметра:

@Parameters(index = "0")
private ConfigElement element;

@Parameters(index = "1")
private String value;

@Override
public void run() {
    System.out.println("Setting " + element.value() + " to " + value);
}

Таким образом, мы убедитесь, что пользователи не смогут изменять несуществующие элементы конфигурации.

Наконец, мы должны зарегистрировать наш преобразователь. Прекрасно то, что при использовании Java 8 или выше нам даже не нужно создавать класс, реализуя ITypeConverter интерфейс. Мы можем просто передать lambda или метод ссылки на регистрацияКонвертер () метод:

CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.registerConverter(ConfigElement.class, ConfigElement::from);

commandLine.parseWithHandler(new RunLast(), args);

Это происходит в GitCommand основной () метод. Обратите внимание, что мы должны были отпустить удобство CommandLine.run () метод.

При использовании с неохожей конфигурацией команда показывает сообщение справки плюс часть информации, сообщая нам, что невозможно преобразовать параметр в КонфигЭлемент :

Invalid value for positional parameter at index 0 (): 
cannot convert 'user.phone' to ConfigElement 
(java.lang.IllegalArgumentException: The argument user.phone doesn't match any ConfigElement)
Usage: git config  
      
      

8. Интеграция с весенней загрузкой

Наконец, давайте посмотрим, как Springify все это!

Действительно, мы можем работать в среде Spring Boot и хотим извлечь из этого пользу в нашей командной программе. Для этого мы должны создать СпрингБутАппликация реализации CommandLineRunner интерфейс :

@SpringBootApplication
public class Application implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
    }
}

К тому же, Давайте аннотировать все наши команды и подкоманды с весенней @Component аннотация и автопровод все, что в нашем Применение :

private GitCommand gitCommand;
private GitAddCommand addCommand;
private GitCommitCommand commitCommand;
private GitConfigCommand configCommand;

public Application(GitCommand gitCommand, GitAddCommand addCommand, 
  GitCommitCommand commitCommand, GitConfigCommand configCommand) {
    this.gitCommand = gitCommand;
    this.addCommand = addCommand;
    this.commitCommand = commitCommand;
    this.configCommand = configCommand;
}

Обратите внимание, что мы должны были автоматически перемонтировать каждый подкоманд. К сожалению, это потому, что на данный момент, пикокли еще не в состоянии получить подкоманды из контекста весны, когда объявлено декларативно (с аннотациями). Таким образом, мы должны сделать, что проводка себя, в программным образом:

@Override
public void run(String... args) {
    CommandLine commandLine = new CommandLine(gitCommand);
    commandLine.addSubcommand("add", addCommand);
    commandLine.addSubcommand("commit", commitCommand);
    commandLine.addSubcommand("config", configCommand);

    commandLine.parseWithHandler(new CommandLine.RunLast(), args);
}

И теперь наша программа командной строки работает как шарм с компонентами Spring. Таким образом, мы могли бы создать некоторые классы обслуживания и использовать их в наших командах, и пусть Spring заботиться о инъекциях зависимости.

9. Заключение

В этой статье мы видели некоторые ключевые особенности пикокли библиотека. Мы научились создавать новую команду и добавлять в нее некоторые подкоманды. Мы видели много способов решения вариантов и позиционных параметров. Кроме того, мы научились реализовывать наши собственные преобразователи типа, чтобы сделать наши команды сильно найме. Наконец, мы видели, как принести Весенняя загрузка в наши команды.

Конечно, Есть много вещей, чтобы узнать об этом. Библиотека предоставляет полная документация .

Что касается полного кода этой статьи, то его можно найти на наш GitHub .