It was a coincidence that I discovered the upcoming 2.0.0.RELEASE
of Spring Shell yesterday.
I decided to migrate our Planets CLI and write about it.
Since we have a Maven based setup let’s start with the Maven dependency:
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
becomes
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
Tip: You did spot the change from
spring-shell
tospring-shell-starter
, did you?
Bootstrapping the CLI
In version 1.2.0
we used org.springframework.shell.Bootstrap
to bootstrap our shell:
public static void main(String[] args) throws IOException {
Bootstrap.main(args);
}
With version 2.0.0
the shell is a regular Spring Boot application and is started accordingly with org.springframework.boot.SpringApplication.run
:
@SpringBootApplication
public class PlanetsShell {
public static void main(String[] args) {
run(PlanetsShell.class, args);
}
}
Shell commands
Previously our shell commands were annotated with @Component
and implemented CommandMarker
:
@Component
public class PlanetsCommands implements CommandMarker {
…
}
With the new 2.0 release comes a more specific version @ShellComponent
and the marker interface is gone. Nice.
@ShellComponent
public class PlanetsCommands {
…
}
The former @CliCommand
and @CliOption
we used to annotate methods to appear in our CLI:
@CliCommand(value = "planets:login", help = "Login with your username/password")
public void login(@CliOption(key = "username", mandatory = true) String username,
@CliOption(key = "password", mandatory = true) String password) {
…
}
can be replaced with @ShellMethods
and @ShellOption
like this:
@ShellMethod("Login with your username/password")
public String login(@ShellOption String username, @ShellOption String password) {
…
}
For more details please refer to the Writing your own Commands.
Availability indication
The annotation @CliAvailablilityIndicator
…
@CliAvailabilityIndicator({"planets:showLocation"})
public boolean isActive() {
return isAuthenticated();
}
is replaced by a method returning an Availability
:
public Availability isAvailable() {
return isAuthenticated() ? available() : unavailable("you are not logged in");
}
If you want to dive into the details I highly recommend to read the section Dynamic Command Availability
PromptProvider
and BannerProvider
Finally the necessity to create a bean with @Order(1)
to override the default providers is gone.
@Order(1) // Override the Spring Shell default Provider(s)
@Component
public class PlanetsShellTheme implements BannerProvider, PromptProvider {
…
}
Another notably change happened to the method PromptProvider.getPrompt()
.
The return type changed from a simple String
to AttributedString
:
@Bean
public PromptProvider promptProvider() {
return () -> new AttributedString("Planets > ", DEFAULT.foreground(YELLOW));
}
Since we have a regular Spring Boot app the BannerProvider
vanished completely.
Simply name your banner file banner.txt
and put it in src/main/resources
.
That’s it.
Tip: Just in case you want to update your banner when moving to Spring Shell
2.0.0
…we polished our Online Banner Generator lately.
Troubleshooting the migration
The format of the history changed.
You might encounter a NumberFormatException
during startup:
18:14:49.114 WARN - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'history' defined in class path resource [org/springframework/shell/jline/JLineShellAutoConfiguration$HistoryConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.jline.reader.History]: Factory method 'history' threw exception; nested exception is java.lang.NumberFormatException: For input string: "// null 1.2.0.RELEASE log opened at 2017-11-09 09"
Since the shell has an excellent tab completion I don’t care too much about the command history.
so I simply deleted my spring-shell.log
and started over!
A huge thanks to the team behind Spring Shell!
PS: Another small thing that has changed is how scripts are called. In Spring Shell 1.2 we used —script <filename>
. With version 2.0 the only parameter needed is the script name prefixed with an @
like this: @<filename>
.
PPS: Shell methods shouldn’t throw checked exceptions. This generates a shell error no application error. In our case we wrapped an IOException
into an UncheckedIOException
to avoid this misleading exceptions.