Support exception handling with annotated methods
- New annotations ExceptionResolver and ExitCode - New needed functionality is in classes ExceptionResolverMethodResolver and MethodCommandExceptionResolver. - Hook these annotations with StandardMethodTargetRegistrar and Shell classes - Fixes #597
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
[[dynamic-command-exitcode-annotation]]
|
||||
==== @ExceptionResolver
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
`@ShellComponent` classes can have `@ExceptionResolver` methods to handle exceptions from component
|
||||
methods. These are meant for annotated methods.
|
||||
|
||||
The exception may match against a top-level exception being propagated (e.g. a direct IOException
|
||||
being thrown) or against a nested cause within a wrapper exception (e.g. an IOException wrapped
|
||||
inside an IllegalStateException). This can match at arbitrary cause levels.
|
||||
|
||||
For matching exception types, preferably declare the target exception as a method argument, as
|
||||
the preceding example(s) shows. When multiple exception methods match, a root exception match is
|
||||
generally preferred to a cause exception match. More specifically, the ExceptionDepthComparator
|
||||
is used to sort exceptions based on their depth from the thrown exception type.
|
||||
|
||||
Alternatively, the annotation declaration may narrow the exception types to match, as the
|
||||
following example shows:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=exception-resolver-with-type-in-annotation]
|
||||
----
|
||||
====
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=exception-resolver-with-type-in-method]
|
||||
----
|
||||
====
|
||||
|
||||
`@ExceptionResolver` can also return `String` which is used as an output to console. You can
|
||||
use `@ExitCode` annotation to define return code.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=exception-resolver-with-exitcode-annotation]
|
||||
----
|
||||
====
|
||||
|
||||
`@ExceptionResolver` with `void` return type is automatically handled as handled exception.
|
||||
You can then also define `@ExitCode` and use `Terminal` if you need to write something
|
||||
into console.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=exception-resolver-with-void]
|
||||
----
|
||||
====
|
||||
|
||||
===== Method Arguments
|
||||
`@ExceptionResolver` methods support the following arguments:
|
||||
|
||||
[Attributes]
|
||||
|===
|
||||
|Method argument |Description
|
||||
|
||||
|Exception type
|
||||
|For access to the raised exception. This is any type of `Exception` or `Throwable`.
|
||||
|
||||
|Terminal
|
||||
|For access to underlying `JLine` terminal to i.e. get its terminal writer.
|
||||
|
||||
|===
|
||||
|
||||
===== Return Values
|
||||
`@ExceptionResolver` methods support the following return values:
|
||||
|
||||
[Attributes]
|
||||
|===
|
||||
|Return value |Description
|
||||
|
||||
|String
|
||||
|Plain text to return to a shell. Exit code 1 is used in this case.
|
||||
|
||||
|CommandHandlingResult
|
||||
|Plain `CommandHandlingResult` having message and exit code.
|
||||
|
||||
|void
|
||||
|A method with a void return type is considered to have fully handled the exception. Usually
|
||||
you would define `Terminal` as a method argument and write response using _terminal writer_
|
||||
from it. As exception is fully handled, Exit code 0 is used in this case.
|
||||
|===
|
||||
@@ -0,0 +1,34 @@
|
||||
[[dynamic-command-exitcode-mappings]]
|
||||
==== Exit Code Mappings
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
Default behaviour of an exit codes is as:
|
||||
|
||||
- Errors from a command option parsing will result code of `2`
|
||||
- Any generic error will result result code of `1`
|
||||
- Obviously in any other case result code is `0`
|
||||
|
||||
Every `CommandRegistration` can define its own mappings between _Exception_ and _exit code_.
|
||||
Essentially we're bound to functionality in `Spring Boot` regarding _exit code_ and simply
|
||||
integrate into that.
|
||||
|
||||
Assuming there is an exception show below which would be thrown from a command:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ExitCodeSnippets.java[tag=my-exception-class]
|
||||
----
|
||||
====
|
||||
|
||||
It is possible to define a mapping function between `Throwable` and exit code. You can also
|
||||
just configure a _class_ to _exit code_ which is just a syntactic sugar within configurations.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ExitCodeSnippets.java[tag=example1]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Exit codes cannot be customized with annotation based configuration
|
||||
@@ -0,0 +1,47 @@
|
||||
[[dynamic-command-exitcode-resolving]]
|
||||
==== Exception Resolving
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
Unhandled exceptions will bubble up into shell's `ResultHandlerService` and then eventually
|
||||
handled by some instance of `ResultHandler`. Chain of `ExceptionResolver` implementations
|
||||
can be used to resolve exceptions and gives you flexibility to return message to get written
|
||||
into console together with exit code which are wrapped within `CommandHandlingResult`.
|
||||
`CommandHandlingResult` may contain a _message_ and/or _exit code_.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-resolver-class]
|
||||
----
|
||||
====
|
||||
|
||||
`CommandExceptionResolver` implementations can be defined globally as bean.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-resolver-class-as-bean]
|
||||
----
|
||||
====
|
||||
|
||||
or defined per `CommandRegistration` if it's applicable only for a particular command itself.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=example1]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Resolvers defined with a command are handled before global resolvers.
|
||||
|
||||
|
||||
Use you own exception types which can also be an instance of boot's `ExitCodeGenerator` if
|
||||
you want to define exit code there.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-class]
|
||||
----
|
||||
====
|
||||
@@ -2,77 +2,18 @@
|
||||
=== Exception Handling
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
Exceptions happen from a user code wether it is intentional or not. This section describes
|
||||
how `spring-shell` handles exceptions and gives instructions and best practices how to
|
||||
work with it.
|
||||
|
||||
Many command line applications when applicable return an _exit code_ which running environment
|
||||
can use to differentiate if command has been executed successfully or not. In a `spring-shell`
|
||||
this mostly relates when a command is run on a non-interactive mode meaning one command
|
||||
is always executed once with an instance of a `spring-shell`.
|
||||
is always executed once with an instance of a `spring-shell`. Take a note that _exit code_
|
||||
always relates to non-interactive shell.
|
||||
|
||||
==== Exception Resolving
|
||||
include::using-shell-commands-exceptionhandling-resolving.adoc[]
|
||||
|
||||
Unhandled exceptions will bubble up into shell's `ResultHandlerService` and then eventually
|
||||
handled by some instance of `ResultHandler`. Chain of `ExceptionResolver` implementations
|
||||
can be used to resolve exceptions and gives you flexibility to return message to get written
|
||||
into console together with exit code which are wrapped within `CommandHandlingResult`.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-resolver-class]
|
||||
----
|
||||
====
|
||||
|
||||
`CommandExceptionResolver` implementations can be defined globally as beans or defined
|
||||
per `CommandRegistration` if it's applicable only for a particular command itself.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=example1]
|
||||
----
|
||||
====
|
||||
|
||||
Use you own exception types which can also be an instance of boot's `ExitCodeGenerator` if
|
||||
you want to define exit code there.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-class]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: With annotation based configuration exception resolving can only be customised globally
|
||||
|
||||
==== Exit Code Mappings
|
||||
|
||||
Default behaviour of an exit codes is as:
|
||||
|
||||
- Errors from a command option parsing will result code of `2`
|
||||
- Any generic error will result result code of `1`
|
||||
- Obviously in any other case result code is `0`
|
||||
|
||||
Every `CommandRegistration` can define its own mappings between _Exception_ and _exit code_.
|
||||
Essentially we're bound to functionality in `Spring Boot` regarding _exit code_ and simply
|
||||
integrate into that.
|
||||
|
||||
Assuming there is an exception show below which would be thrown from a command:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ExitCodeSnippets.java[tag=my-exception-class]
|
||||
----
|
||||
====
|
||||
|
||||
It is possible to define a mapping function between `Throwable` and exit code. You can also
|
||||
just configure a _class_ to _exit code_ which is just a syntactic sugar within configurations.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/ExitCodeSnippets.java[tag=example1]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Exit codes cannot be customized with annotation based configuration
|
||||
include::using-shell-commands-exceptionhandling-mappings.adoc[]
|
||||
|
||||
include::using-shell-commands-exceptionhandling-annotation.adoc[]
|
||||
|
||||
@@ -15,14 +15,28 @@
|
||||
*/
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
import org.springframework.boot.ExitCodeGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.shell.command.CommandExceptionResolver;
|
||||
import org.springframework.shell.command.CommandHandlingResult;
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
import org.springframework.shell.command.annotation.ExceptionResolver;
|
||||
import org.springframework.shell.command.annotation.ExitCode;
|
||||
|
||||
class ErrorHandlingSnippets {
|
||||
|
||||
// tag::my-exception-class[]
|
||||
static class CustomException extends RuntimeException {}
|
||||
static class CustomException extends RuntimeException implements ExitCodeGenerator {
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// end::my-exception-class[]
|
||||
|
||||
// tag::my-exception-resolver-class[]
|
||||
@@ -48,4 +62,60 @@ class ErrorHandlingSnippets {
|
||||
// end::example1[]
|
||||
}
|
||||
|
||||
static class Dump1 {
|
||||
|
||||
// tag::exception-resolver-with-type-in-annotation[]
|
||||
@ExceptionResolver({ RuntimeException.class })
|
||||
CommandHandlingResult errorHandler(Exception e) {
|
||||
// Exception would be type of RuntimeException,
|
||||
// optionally do something with it
|
||||
return CommandHandlingResult.of("Hi, handled exception\n", 42);
|
||||
}
|
||||
// end::exception-resolver-with-type-in-annotation[]
|
||||
}
|
||||
|
||||
static class Dump2 {
|
||||
|
||||
// tag::exception-resolver-with-type-in-method[]
|
||||
@ExceptionResolver
|
||||
CommandHandlingResult errorHandler(RuntimeException e) {
|
||||
return CommandHandlingResult.of("Hi, handled custom exception\n", 42);
|
||||
}
|
||||
// end::exception-resolver-with-type-in-method[]
|
||||
}
|
||||
|
||||
static class Dump3 {
|
||||
|
||||
// tag::my-exception-resolver-class-as-bean[]
|
||||
@Bean
|
||||
CustomExceptionResolver customExceptionResolver() {
|
||||
return new CustomExceptionResolver();
|
||||
}
|
||||
// end::my-exception-resolver-class-as-bean[]
|
||||
}
|
||||
|
||||
static class Dump4 {
|
||||
|
||||
// tag::exception-resolver-with-exitcode-annotation[]
|
||||
@ExceptionResolver
|
||||
@ExitCode(code = 5)
|
||||
String errorHandler(Exception e) {
|
||||
return "Hi, handled exception";
|
||||
}
|
||||
// end::exception-resolver-with-exitcode-annotation[]
|
||||
}
|
||||
|
||||
static class Dump5 {
|
||||
|
||||
// tag::exception-resolver-with-void[]
|
||||
@ExceptionResolver
|
||||
@ExitCode(code = 5)
|
||||
void errorHandler(Exception e, Terminal terminal) {
|
||||
PrintWriter writer = terminal.writer();
|
||||
String msg = "Hi, handled exception " + e.toString();
|
||||
writer.println(msg);
|
||||
writer.flush();
|
||||
}
|
||||
// end::exception-resolver-with-void[]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user