-
Notifications
You must be signed in to change notification settings - Fork 806
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
1,834 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# Exception Handling inside GrpcService | ||
|
||
[<- Back to Index](../index.md) | ||
|
||
This section describes how you can handle exceptions inside GrpcService layer without cluttering up your code. | ||
|
||
## Table of Contents <!-- omit in toc --> | ||
|
||
- [Proper exception handling](#proper-exception-handling) | ||
- [Detailed explanation](#detailed-explanation) | ||
- [Priority of mapped exceptions](#priority-of-mapped-exceptions) | ||
- [Sending Metadata in response](#sending-metadata-in-response) | ||
- [Overview of returnable types](#overview-of-returnable-types) | ||
|
||
## Additional Topics <!-- omit in toc --> | ||
|
||
- [Getting Started](getting-started.md) | ||
- [Configuration](configuration.md) | ||
- [Contextual Data](contextual-data.md) | ||
- *Exception Handling* | ||
- [Testing the Service](testing.md) | ||
- [Security](security.md) | ||
|
||
## Proper exception handling | ||
|
||
If you are already familiar with spring's [error handling](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling), | ||
you should see some similarities with the exception handling for gRPC. | ||
|
||
_An explanation for the following class:_ | ||
|
||
```java | ||
@GrpcAdvice | ||
public class GrpcExceptionAdvice { | ||
|
||
|
||
@GrpcExceptionHandler | ||
public Status handleInvalidArgument(IllegalArgumentException e) { | ||
return Status.INVALID_ARGUMENT.withDescription("Your description").withCause(e); | ||
} | ||
|
||
@GrpcExceptionHandler(ResourceNotFoundException.class) | ||
public StatusException handleResourceNotFoundException(ResourceNotFoundException e) { | ||
Status status = Status.NOT_FOUND.withDescription("Your description").withCause(e); | ||
Metadata metadata = ... | ||
return status.asException(metadata); | ||
} | ||
|
||
} | ||
``` | ||
|
||
- `@GrpcAdvice` marks a class to be checked up for exception handling methods | ||
- `@GrpcExceptionHandler` marks the annotated method to be executed, in case of the _specified_ exception being thrown | ||
- f.e. if your application throws `IllegalArgumentException`, | ||
then the `handleInvalidArgument(IllegalArgumentException e)` method will be executed | ||
- The method must either return a `io.grpc.Status`, `StatusException`, or `StatusRuntimeException` | ||
- If you handle server errors, you might want to log the exception/stacktrace inside the exception handler | ||
|
||
> **Note:** Cause is not transmitted from server to client - as stated in [official docs](https://grpc.github.io/grpc-java/javadoc/io/grpc/Status.html#withCause-java.lang.Throwable-) | ||
> So we recommend adding it to the `Status`/`StatusException` to avoid the loss of information on the server side. | ||
## Detailed explanation | ||
|
||
### Priority of mapped exceptions | ||
|
||
Given this method with specified exception in the annotation *and* as a method argument | ||
|
||
```java | ||
@GrpcExceptionHandler(ResourceNotFoundException.class) | ||
public StatusException handleResourceNotFoundException(ResourceNotFoundException e) { | ||
// your exception handling | ||
} | ||
``` | ||
|
||
If the `GrpcExceptionHandler` annotation contains at least one exception type, then only those will be | ||
considered for exception handling for that method. The method parameters must be "compatible" with the specified | ||
exception types. If the annotation does not specify any handled exception types, then all method parameters are being | ||
used instead. | ||
|
||
_("Compatible" means that the exception type in annotation is either the same class or a superclass of one of the | ||
listed method parameters)_ | ||
|
||
### Sending Metadata in response | ||
|
||
In case you want to send metadata in your exception response, let's have a look at the following example. | ||
|
||
```java | ||
@GrpcExceptionHandler | ||
public StatusRuntimeException handleResourceNotFoundException(IllegalArgumentException e) { | ||
Status status = Status.INVALID_ARGUMENT.withDescription("Your description"); | ||
Metadata metadata = ... | ||
return status.asRuntimeException(metadata); | ||
} | ||
``` | ||
|
||
If you do not need `Metadata` in your response, just return your specified `Status`. | ||
|
||
### Overview of returnable types | ||
|
||
Here is a small overview of possible mapped return types with `@GrpcExceptionHandler` and if custom `Metadata` can be | ||
returned: | ||
|
||
| Return Type | Supports Custom Metadata | | ||
| ----------- | --------------- | | ||
| `Status` | ✗ | | ||
| `StatusException` | ✔ | | ||
| `StatusRuntimeException` | ✔ | | ||
|
||
## Additional Topics <!-- omit in toc --> | ||
|
||
- [Getting Started](getting-started.md) | ||
- [Configuration](configuration.md) | ||
- [Contextual Data](contextual-data.md) | ||
- *Exception Handling* | ||
- [Testing the Service](testing.md) | ||
- [Security](security.md) | ||
|
||
---------- | ||
|
||
[<- Back to Index](../index.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
...-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright (c) 2016-2021 Michael Zhang <yidongnan@gmail.com> | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | ||
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the | ||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the | ||
* Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | ||
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
|
||
package net.devh.boot.grpc.server.advice; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
* Special {@link Component @Component} to declare global gRPC exception handling. | ||
* | ||
* Every class annotated with {@link GrpcAdvice @GrpcAdvice} is marked to be scanned for | ||
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations. | ||
* <p> | ||
* | ||
* @author Andjelko Perisic (andjelko.perisic@gmail.com) | ||
* @see GrpcExceptionHandler | ||
*/ | ||
@Target({ElementType.TYPE, ElementType.METHOD}) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
@Component | ||
public @interface GrpcAdvice { | ||
|
||
} |
93 changes: 93 additions & 0 deletions
93
...ot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscoverer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Copyright (c) 2016-2021 Michael Zhang <yidongnan@gmail.com> | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | ||
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the | ||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the | ||
* Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | ||
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
|
||
package net.devh.boot.grpc.server.advice; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.Collection; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.springframework.beans.factory.InitializingBean; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.context.ApplicationContextAware; | ||
import org.springframework.core.MethodIntrospector; | ||
import org.springframework.core.annotation.AnnotatedElementUtils; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.ReflectionUtils.MethodFilter; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
|
||
/** | ||
* A discovery class to find all Beans annotated with {@link GrpcAdvice @GrpcAdvice} and for all found beans a second | ||
* search is performed looking for methods with {@link GrpcExceptionHandler @GrpcExceptionHandler}. | ||
* | ||
* @author Andjelko Perisic (andjelko.perisic@gmail.com) | ||
* @see GrpcAdvice | ||
* @see GrpcExceptionHandler | ||
*/ | ||
@Slf4j | ||
public class GrpcAdviceDiscoverer implements InitializingBean, ApplicationContextAware { | ||
|
||
/** | ||
* A filter for selecting {@code @GrpcExceptionHandler} methods. | ||
*/ | ||
public static final MethodFilter EXCEPTION_HANDLER_METHODS = | ||
method -> AnnotatedElementUtils.hasAnnotation(method, GrpcExceptionHandler.class); | ||
|
||
private ApplicationContext applicationContext; | ||
private Map<String, Object> annotatedBeans; | ||
private Set<Method> annotatedMethods; | ||
|
||
@Override | ||
public void setApplicationContext(final ApplicationContext applicationContext) { | ||
this.applicationContext = applicationContext; | ||
} | ||
|
||
@Override | ||
public void afterPropertiesSet() { | ||
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcAdvice.class); | ||
annotatedBeans.forEach( | ||
(key, value) -> log.debug("Found gRPC advice: " + key + ", class: " + value.getClass().getName())); | ||
|
||
annotatedMethods = findAnnotatedMethods(); | ||
} | ||
|
||
private Set<Method> findAnnotatedMethods() { | ||
return this.annotatedBeans.values().stream() | ||
.map(Object::getClass) | ||
.map(this::findAnnotatedMethods) | ||
.flatMap(Collection::stream) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
private Set<Method> findAnnotatedMethods(final Class<?> clazz) { | ||
return MethodIntrospector.selectMethods(clazz, EXCEPTION_HANDLER_METHODS); | ||
} | ||
|
||
public Map<String, Object> getAnnotatedBeans() { | ||
Assert.state(annotatedBeans != null, "@GrpcAdvice annotation scanning failed."); | ||
return annotatedBeans; | ||
} | ||
|
||
public Set<Method> getAnnotatedMethods() { | ||
Assert.state(annotatedMethods != null, "@GrpcExceptionHandler annotation scanning failed."); | ||
return annotatedMethods; | ||
} | ||
|
||
} |
Oops, something went wrong.