Skip to content

Commit b5e2f6f

Browse files
authored
Merge branch 'master' into master
2 parents 04d7044 + ffc477b commit b5e2f6f

25 files changed

+1834
-1
lines changed

docs/en/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ customization you need for your project.
1212
- Server
1313
- [Getting Started](server/getting-started.md)
1414
- [Configuration](server/configuration.md)
15+
- [Exception Handling](server/exception-handling.md)
1516
- [Contextual Data / Scoped Beans](server/contextual-data.md)
1617
- [Testing the Service](server/testing.md)
1718
- [Security](server/security.md)

docs/en/server/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This section describes how you can configure your grpc-spring-boot-starter appli
1717

1818
- [Getting Started](getting-started.md)
1919
- *Configuration*
20+
- [Exception Handling](exception-handling.md)
2021
- [Contextual Data / Scoped Beans](contextual-data.md)
2122
- [Testing the Service](testing.md)
2223
- [Security](security.md)
@@ -121,6 +122,7 @@ public GrpcServerConfigurer keepAliveServerConfigurer() {
121122

122123
- [Getting Started](getting-started.md)
123124
- *Configuration*
125+
- [Exception Handling](exception-handling.md)
124126
- [Contextual Data / Scoped Beans](contextual-data.md)
125127
- [Testing the Service](testing.md)
126128
- [Security](security.md)

docs/en/server/contextual-data.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This section describes how you can store contextual / per request data.
1313

1414
- [Getting Started](getting-started.md)
1515
- [Configuration](configuration.md)
16+
- [Exception Handling](exception-handling.md)
1617
- *Contextual Data / Scoped Beans*
1718
- [Testing the Service](testing.md)
1819
- [Security](security.md)
@@ -61,6 +62,7 @@ public void grpcMethod(Request request, StreamObserver<Response> responseObserve
6162

6263
- [Getting Started](getting-started.md)
6364
- [Configuration](configuration.md)
65+
- [Exception Handling](exception-handling.md)
6466
- *Contextual Data / Scoped Beans*
6567
- [Testing the Service](testing.md)
6668
- [Security](security.md)

docs/en/server/exception-handling.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Exception Handling inside GrpcService
2+
3+
[<- Back to Index](../index.md)
4+
5+
This section describes how you can handle exceptions inside GrpcService layer without cluttering up your code.
6+
7+
## Table of Contents <!-- omit in toc -->
8+
9+
- [Proper exception handling](#proper-exception-handling)
10+
- [Detailed explanation](#detailed-explanation)
11+
- [Priority of mapped exceptions](#priority-of-mapped-exceptions)
12+
- [Sending Metadata in response](#sending-metadata-in-response)
13+
- [Overview of returnable types](#overview-of-returnable-types)
14+
15+
## Additional Topics <!-- omit in toc -->
16+
17+
- [Getting Started](getting-started.md)
18+
- [Configuration](configuration.md)
19+
- [Contextual Data](contextual-data.md)
20+
- *Exception Handling*
21+
- [Testing the Service](testing.md)
22+
- [Security](security.md)
23+
24+
## Proper exception handling
25+
26+
If you are already familiar with spring's [error handling](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling),
27+
you should see some similarities with the exception handling for gRPC.
28+
29+
_An explanation for the following class:_
30+
31+
```java
32+
@GrpcAdvice
33+
public class GrpcExceptionAdvice {
34+
35+
36+
@GrpcExceptionHandler
37+
public Status handleInvalidArgument(IllegalArgumentException e) {
38+
return Status.INVALID_ARGUMENT.withDescription("Your description").withCause(e);
39+
}
40+
41+
@GrpcExceptionHandler(ResourceNotFoundException.class)
42+
public StatusException handleResourceNotFoundException(ResourceNotFoundException e) {
43+
Status status = Status.NOT_FOUND.withDescription("Your description").withCause(e);
44+
Metadata metadata = ...
45+
return status.asException(metadata);
46+
}
47+
48+
}
49+
```
50+
51+
- `@GrpcAdvice` marks a class to be checked up for exception handling methods
52+
- `@GrpcExceptionHandler` marks the annotated method to be executed, in case of the _specified_ exception being thrown
53+
- f.e. if your application throws `IllegalArgumentException`,
54+
then the `handleInvalidArgument(IllegalArgumentException e)` method will be executed
55+
- The method must either return a `io.grpc.Status`, `StatusException`, or `StatusRuntimeException`
56+
- If you handle server errors, you might want to log the exception/stacktrace inside the exception handler
57+
58+
> **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-)
59+
> So we recommend adding it to the `Status`/`StatusException` to avoid the loss of information on the server side.
60+
61+
## Detailed explanation
62+
63+
### Priority of mapped exceptions
64+
65+
Given this method with specified exception in the annotation *and* as a method argument
66+
67+
```java
68+
@GrpcExceptionHandler(ResourceNotFoundException.class)
69+
public StatusException handleResourceNotFoundException(ResourceNotFoundException e) {
70+
// your exception handling
71+
}
72+
```
73+
74+
If the `GrpcExceptionHandler` annotation contains at least one exception type, then only those will be
75+
considered for exception handling for that method. The method parameters must be "compatible" with the specified
76+
exception types. If the annotation does not specify any handled exception types, then all method parameters are being
77+
used instead.
78+
79+
_("Compatible" means that the exception type in annotation is either the same class or a superclass of one of the
80+
listed method parameters)_
81+
82+
### Sending Metadata in response
83+
84+
In case you want to send metadata in your exception response, let's have a look at the following example.
85+
86+
```java
87+
@GrpcExceptionHandler
88+
public StatusRuntimeException handleResourceNotFoundException(IllegalArgumentException e) {
89+
Status status = Status.INVALID_ARGUMENT.withDescription("Your description");
90+
Metadata metadata = ...
91+
return status.asRuntimeException(metadata);
92+
}
93+
```
94+
95+
If you do not need `Metadata` in your response, just return your specified `Status`.
96+
97+
### Overview of returnable types
98+
99+
Here is a small overview of possible mapped return types with `@GrpcExceptionHandler` and if custom `Metadata` can be
100+
returned:
101+
102+
| Return Type | Supports Custom Metadata |
103+
| ----------- | --------------- |
104+
| `Status` | &cross; |
105+
| `StatusException` | &#10004; |
106+
| `StatusRuntimeException` | &#10004; |
107+
108+
## Additional Topics <!-- omit in toc -->
109+
110+
- [Getting Started](getting-started.md)
111+
- [Configuration](configuration.md)
112+
- [Contextual Data](contextual-data.md)
113+
- *Exception Handling*
114+
- [Testing the Service](testing.md)
115+
- [Security](security.md)
116+
117+
----------
118+
119+
[<- Back to Index](../index.md)

docs/en/server/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This section describes the steps necessary to convert your application into a gr
1818

1919
- *Getting started*
2020
- [Configuration](configuration.md)
21+
- [Exception Handling](exception-handling.md)
2122
- [Contextual Data / Scoped Beans](contextual-data.md)
2223
- [Testing the Service](testing.md)
2324
- [Security](security.md)
@@ -316,6 +317,7 @@ See [here](testing.md#grpcurl) for `gRPCurl` example command output and addition
316317

317318
- *Getting Started*
318319
- [Configuration](configuration.md)
320+
- [Exception Handling](exception-handling.md)
319321
- [Contextual Data / Scoped Beans](contextual-data.md)
320322
- [Testing the Service](testing.md)
321323
- [Security](security.md)

docs/en/server/security.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ We strongly recommend enabling at least transport layer security.
1919

2020
- [Getting Started](getting-started.md)
2121
- [Configuration](configuration.md)
22+
- [Exception Handling](exception-handling.md)
2223
- [Contextual Data / Scoped Beans](contextual-data.md)
2324
- [Testing the Service](testing.md)
2425
- *Security*
@@ -279,6 +280,7 @@ public void methodX(Request request, StreamObserver<Response> responseObserver)
279280

280281
- [Getting Started](getting-started.md)
281282
- [Configuration](configuration.md)
283+
- [Exception Handling](exception-handling.md)
282284
- [Contextual Data / Scoped Beans](contextual-data.md)
283285
- [Testing the Service](testing.md)
284286
- *Security*

docs/en/server/testing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Please refer to [Tests with Grpc-Stubs](../client/testing.md).
2222

2323
- [Getting Started](getting-started.md)
2424
- [Configuration](configuration.md)
25+
- [Exception Handling](exception-handling.md)
2526
- [Contextual Data / Scoped Beans](contextual-data.md)
2627
- *Testing the Service*
2728
- [Security](security.md)
@@ -368,6 +369,7 @@ For more information regarding `gRPCurl` please refer to their [official documen
368369
369370
- [Getting Started](getting-started.md)
370371
- [Configuration](configuration.md)
372+
- [Exception Handling](exception-handling.md)
371373
- [Contextual Data / Scoped Beans](contextual-data.md)
372374
- *Testing the Service*
373375
- [Security](security.md)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2016-2021 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.advice;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.springframework.stereotype.Component;
27+
28+
/**
29+
* Special {@link Component @Component} to declare global gRPC exception handling.
30+
*
31+
* Every class annotated with {@link GrpcAdvice @GrpcAdvice} is marked to be scanned for
32+
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations.
33+
* <p>
34+
*
35+
* @author Andjelko Perisic ([email protected])
36+
* @see GrpcExceptionHandler
37+
*/
38+
@Target({ElementType.TYPE, ElementType.METHOD})
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Documented
41+
@Component
42+
public @interface GrpcAdvice {
43+
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2016-2021 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.advice;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.Collection;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
25+
26+
import org.springframework.beans.factory.InitializingBean;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.ApplicationContextAware;
29+
import org.springframework.core.MethodIntrospector;
30+
import org.springframework.core.annotation.AnnotatedElementUtils;
31+
import org.springframework.util.Assert;
32+
import org.springframework.util.ReflectionUtils.MethodFilter;
33+
34+
import lombok.extern.slf4j.Slf4j;
35+
36+
/**
37+
* A discovery class to find all Beans annotated with {@link GrpcAdvice @GrpcAdvice} and for all found beans a second
38+
* search is performed looking for methods with {@link GrpcExceptionHandler @GrpcExceptionHandler}.
39+
*
40+
* @author Andjelko Perisic ([email protected])
41+
* @see GrpcAdvice
42+
* @see GrpcExceptionHandler
43+
*/
44+
@Slf4j
45+
public class GrpcAdviceDiscoverer implements InitializingBean, ApplicationContextAware {
46+
47+
/**
48+
* A filter for selecting {@code @GrpcExceptionHandler} methods.
49+
*/
50+
public static final MethodFilter EXCEPTION_HANDLER_METHODS =
51+
method -> AnnotatedElementUtils.hasAnnotation(method, GrpcExceptionHandler.class);
52+
53+
private ApplicationContext applicationContext;
54+
private Map<String, Object> annotatedBeans;
55+
private Set<Method> annotatedMethods;
56+
57+
@Override
58+
public void setApplicationContext(final ApplicationContext applicationContext) {
59+
this.applicationContext = applicationContext;
60+
}
61+
62+
@Override
63+
public void afterPropertiesSet() {
64+
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcAdvice.class);
65+
annotatedBeans.forEach(
66+
(key, value) -> log.debug("Found gRPC advice: " + key + ", class: " + value.getClass().getName()));
67+
68+
annotatedMethods = findAnnotatedMethods();
69+
}
70+
71+
private Set<Method> findAnnotatedMethods() {
72+
return this.annotatedBeans.values().stream()
73+
.map(Object::getClass)
74+
.map(this::findAnnotatedMethods)
75+
.flatMap(Collection::stream)
76+
.collect(Collectors.toSet());
77+
}
78+
79+
private Set<Method> findAnnotatedMethods(final Class<?> clazz) {
80+
return MethodIntrospector.selectMethods(clazz, EXCEPTION_HANDLER_METHODS);
81+
}
82+
83+
public Map<String, Object> getAnnotatedBeans() {
84+
Assert.state(annotatedBeans != null, "@GrpcAdvice annotation scanning failed.");
85+
return annotatedBeans;
86+
}
87+
88+
public Set<Method> getAnnotatedMethods() {
89+
Assert.state(annotatedMethods != null, "@GrpcExceptionHandler annotation scanning failed.");
90+
return annotatedMethods;
91+
}
92+
93+
}

0 commit comments

Comments
 (0)