1. Обзор
Проще говоря, проект Spring Shell предоставляет интерактивную оболочку для обработки команд и создания полнофункционального интерфейса командной строки с использованием модели программирования Spring.
В этой статье мы рассмотрим его функции, ключевые классы и аннотации, а также реализуем несколько пользовательских команд и настроек.
2. Зависимость от Maven
Во- первых, нам нужно добавить зависимость spring-shell
к нашему pom.xml
:
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
Последнюю версию этого артефакта можно найти здесь .
3. Доступ к оболочке
Есть два основных способа доступа к оболочке в наших приложениях.
Первый — загрузить оболочку в точке входа нашего приложения и позволить пользователю вводить команды:
public static void main(String[] args) throws IOException {
Bootstrap.main(args);
}
Второй — получить JLineShellComponent
и выполнить команды программно:
Bootstrap bootstrap = new Bootstrap();
JLineShellComponent shell = bootstrap.getJLineShellComponent();
shell.executeCommand("help");
Мы собираемся использовать первый подход, так как он лучше всего подходит для примеров в этой статье, однако в исходном коде вы можете найти тестовые примеры, использующие вторую форму.
4. Команды
В оболочке уже есть несколько встроенных команд, таких как clear
, help
, exit
и т. д., которые обеспечивают стандартную функциональность каждого CLI.
Пользовательские команды могут быть представлены путем добавления методов, помеченных аннотацией @CliCommand
, внутри компонента Spring, реализующего интерфейс CommandMarker
.
Каждый аргумент этого метода должен быть помечен аннотацией @CliOption
, если мы этого не сделаем, мы столкнемся с несколькими ошибками при попытке выполнить команду.
4.1. Добавление команд в оболочку
Во-первых, нам нужно сообщить оболочке, где находятся наши команды. Для этого требуется, чтобы в нашем проекте присутствовал файл META-INF/spring/spring-shell-plugin.xml
, там мы можем использовать функцию сканирования компонентов Spring:
<beans ... >
<context:component-scan base-package="org.foreach.shell.simple" />
</beans>
Как только компоненты зарегистрированы и созданы Spring, они регистрируются синтаксическим анализатором оболочки, и их аннотации обрабатываются.
Давайте создадим две простые команды, одну для захвата содержимого URL-адреса и его отображения, а другую для сохранения этого содержимого в файл:
@Component
public class SimpleCLI implements CommandMarker {
@CliCommand(value = { "web-get", "wg" })
public String webGet(
@CliOption(key = "url") String url) {
return getContentsOfUrlAsString(url);
}
@CliCommand(value = { "web-save", "ws" })
public String webSave(
@CliOption(key = "url") String url,
@CliOption(key = { "out", "file" }) String file) {
String contents = getContentsOfUrlAsString(url);
try (PrintWriter out = new PrintWriter(file)) {
out.write(contents);
}
return "Done.";
}
}
Обратите внимание, что мы можем передать более одной строки атрибутам значения
и ключа
@CliCommand
и @CliOption
соответственно, это позволяет нам отображать несколько команд и аргументов, которые ведут себя одинаково.
Теперь давайте проверим, все ли работает так, как ожидалось:
spring-shell>web-get --url https://www.google.com
<!doctype html ...
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.
4.2. Доступность команд
Мы можем использовать аннотацию @CliAvailabilityIndicator
для метода, возвращающего логическое значение
для изменения во время выполнения, если команда должна быть представлена оболочке.
Во-первых, давайте создадим метод для изменения доступности команды веб-сохранения
:
private boolean adminEnableExecuted = false;
@CliAvailabilityIndicator(value = "web-save")
public boolean isAdminEnabled() {
return adminEnableExecuted;
}
Теперь давайте создадим команду для изменения переменной adminEnableExecuted
:
@CliCommand(value = "admin-enable")
public String adminEnable() {
adminEnableExecuted = true;
return "Admin commands enabled.";
}
Наконец, давайте проверим это:
spring-shell>web-save --url https://www.google.com --out contents.txt
Command 'web-save --url https://www.google.com --out contents.txt'
was found but is not currently available
(type 'help' then ENTER to learn about this command)
spring-shell>admin-enable
Admin commands enabled.
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.
4.3. Требуемые аргументы
По умолчанию все аргументы команды являются необязательными. Однако мы можем сделать их обязательными с помощью обязательного
атрибута аннотации @CliOption
:
@CliOption(key = { "out", "file" }, mandatory = true)
Теперь мы можем проверить, что если мы его не введем, это приведет к ошибке:
spring-shell>web-save --url https://www.google.com
You should specify option (--out) for this command
4.4. Аргументы по умолчанию
Пустое значение ключа
для @CliOption
делает этот аргумент значением по умолчанию. Там мы получим введенные в оболочке значения, которые не являются частью какого-либо именованного аргумента:
@CliOption(key = { "", "url" })
Теперь давайте проверим, что он работает так, как ожидалось:
spring-shell>web-get https://www.google.com
<!doctype html ...
4.5. Помощь пользователям
Аннотации @CliCommand
и @CliOption
предоставляют атрибут справки
, который позволяет нам направлять наших пользователей при использовании встроенной команды справки
или при переходе на вкладку для автоматического завершения.
Давайте изменим наш web-get
, чтобы добавить собственные справочные сообщения:
@CliCommand(
// ...
help = "Displays the contents of an URL")
public String webGet(
@CliOption(
// ...
help = "URL whose contents will be displayed."
) String url) {
// ...
}
Теперь пользователь может точно знать, что делает наша команда:
spring-shell>help web-get
Keyword: web-get
Keyword: wg
Description: Displays the contents of a URL.
Keyword: ** default **
Keyword: url
Help: URL whose contents will be displayed.
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'
* web-get - Displays the contents of a URL.
* wg - Displays the contents of a URL.
5. Настройка
Существует три способа настроить оболочку путем реализации интерфейсов BannerProvider
, PromptProvider
и HistoryFileNameProvider
, все они с уже предоставленными реализациями по умолчанию.
Кроме того, нам нужно использовать аннотацию @Order
, чтобы позволить нашим провайдерам иметь приоритет над этими реализациями.
Давайте создадим новый баннер, чтобы начать настройку:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleBannerProvider extends DefaultBannerProvider {
public String getBanner() {
StringBuffer buf = new StringBuffer();
buf.append("=======================================")
.append(OsUtils.LINE_SEPARATOR);
buf.append("* ForEach Shell *")
.append(OsUtils.LINE_SEPARATOR);
buf.append("=======================================")
.append(OsUtils.LINE_SEPARATOR);
buf.append("Version:")
.append(this.getVersion());
return buf.toString();
}
public String getVersion() {
return "1.0.1";
}
public String getWelcomeMessage() {
return "Welcome to ForEach CLI";
}
public String getProviderName() {
return "ForEach Banner";
}
}
Обратите внимание, что мы также можем изменить номер версии и приветственное сообщение.
Теперь давайте изменим подсказку:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimplePromptProvider extends DefaultPromptProvider {
public String getPrompt() {
return "foreach-shell";
}
public String getProviderName() {
return "ForEach Prompt";
}
}
Наконец, давайте изменим имя файла истории:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleHistoryFileNameProvider
extends DefaultHistoryFileNameProvider {
public String getHistoryFileName() {
return "foreach-shell.log";
}
public String getProviderName() {
return "ForEach History";
}
}
Файл истории будет записывать все команды, выполненные в оболочке, и будет помещен вместе с нашим приложением.
Когда все на месте, мы можем вызвать нашу оболочку и увидеть ее в действии:
=======================================
* ForEach Shell *
=======================================
Version:1.0.1
Welcome to ForEach CLI
foreach-shell>
6. Преобразователи
До сих пор мы использовали только простые типы в качестве аргументов для наших команд. Общие типы, такие как Integer
, Date
, Enum
, File
и т. д ., имеют уже зарегистрированный преобразователь по умолчанию.
Реализуя интерфейс конвертера
, мы также можем добавить наши конвертеры для получения пользовательских объектов.
Давайте создадим преобразователь, который может преобразовать строку
в URL
:
@Component
public class SimpleURLConverter implements Converter<URL> {
public URL convertFromText(
String value, Class<?> requiredType, String optionContext) {
return new URL(value);
}
public boolean getAllPossibleValues(
List<Completion> completions,
Class<?> requiredType,
String existingData,
String optionContext,
MethodTarget target) {
return false;
}
public boolean supports(Class<?> requiredType, String optionContext) {
return URL.class.isAssignableFrom(requiredType);
}
}
Наконец, давайте изменим наши команды web-get
и web-save
:
public String webSave(... URL url) {
// ...
}
public String webSave(... URL url) {
// ...
}
Как вы уже догадались, команды ведут себя одинаково.
7. Заключение
В этой статье мы кратко рассмотрели основные функции проекта Spring Shell. Мы смогли внести свои команды и настроить оболочку с помощью наших провайдеров, мы изменили доступность команд в соответствии с различными условиями выполнения и создали простой преобразователь типов.
Полный исходный код этой статьи можно найти на GitHub .