Skip to content

Commit 80c6a75

Browse files
committed
refactor from AOP to interceptor & change javadoc
1 parent 05b3e13 commit 80c6a75

14 files changed

+446
-290
lines changed

grpc-server-spring-boot-autoconfigure/build.gradle

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@ dependencies {
1616
api project(':grpc-common-spring-boot')
1717
api 'org.springframework.boot:spring-boot-starter'
1818
optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-actuator'
19-
optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-aop'
2019
optionalSupportImplementation 'org.springframework.security:spring-security-core'
2120
optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-sleuth'
2221
optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
2322
optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
2423
optionalSupportImplementation "com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:${springCloudAlibabaNacosVersion}"
25-
// Only needed to avoid some warnings during compilation (for eureka)
26-
optionalSupportImplementation 'com.google.inject:guice:4.2.3'
27-
optionalSupportImplementation 'io.zipkin.brave:brave-instrumentation-grpc'
28-
optionalSupportApi 'io.grpc:grpc-netty'
24+
optionalSupportImplementation 'com.google.inject:guice:4.2.3' // Only needed to avoid some warnings during compilation (for eureka)
2925
api 'io.grpc:grpc-netty-shaded'
3026
api 'io.grpc:grpc-protobuf'
3127
api 'io.grpc:grpc-stub'
@@ -39,6 +35,5 @@ dependencies {
3935
exclude module: 'junit'
4036
}
4137

42-
runtimeOnly 'org.aspectj:aspectjweaver'
4338
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
4439
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1616
*/
1717

18-
package net.devh.boot.grpc.server.service.exceptionhandling;
18+
package net.devh.boot.grpc.server.advice;
1919

2020
import java.lang.annotation.Documented;
2121
import java.lang.annotation.ElementType;
@@ -26,19 +26,19 @@
2626
import org.springframework.stereotype.Component;
2727

2828
/**
29-
* Special {@link Component @Component} to declare {@link GrpcExceptionHandler GrpcException Handling}.
29+
* Special {@link Component @Component} to declare global gRPC exception handling.
3030
*
31-
* Every class annotated with {@link GrpcServiceAdvice @GrpcServiceAdvice} is marked to be scanned for
31+
* Every class annotated with {@link GrpcAdvice @GrpcAdvice} is marked to be scanned for
3232
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations.
33+
* <p>
3334
*
3435
* @author Andjelko Perisic ([email protected])
35-
*
3636
* @see GrpcExceptionHandler
3737
*/
3838
@Target(ElementType.TYPE)
3939
@Retention(RetentionPolicy.RUNTIME)
4040
@Documented
4141
@Component
42-
public @interface GrpcServiceAdvice {
42+
public @interface GrpcAdvice {
4343

4444
}
Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1616
*/
1717

18-
package net.devh.boot.grpc.server.service.exceptionhandling;
18+
package net.devh.boot.grpc.server.advice;
1919

2020
import java.lang.reflect.Method;
2121
import java.util.Arrays;
@@ -28,66 +28,70 @@
2828
import org.springframework.beans.factory.InitializingBean;
2929
import org.springframework.context.ApplicationContext;
3030
import org.springframework.context.ApplicationContextAware;
31+
import org.springframework.util.Assert;
3132

3233
import lombok.extern.slf4j.Slf4j;
3334

3435
/**
35-
* A discovery class to find all Beans annotated with {@link GrpcServiceAdvice @GrpcServiceAdvice} and for all found
36-
* beans a second search is performed looking for methods with {@link GrpcExceptionHandler @GrpcExceptionHandler}.<br>
37-
* <br>
36+
* A discovery class to find all Beans annotated with {@link GrpcAdvice @GrpcAdvice} and for all found beans a second
37+
* search is performed looking for methods with {@link GrpcExceptionHandler @GrpcExceptionHandler}.<br>
38+
* <p>
3839
*
3940
* @author Andjelko Perisic ([email protected])
40-
* @see GrpcServiceAdvice
41+
* @see GrpcAdvice
4142
* @see GrpcExceptionHandler
4243
*/
4344
@Slf4j
44-
public class GrpcServiceAdviceDiscoverer implements InitializingBean, ApplicationContextAware {
45+
public class GrpcAdviceDiscoverer implements InitializingBean, ApplicationContextAware {
4546

4647
private ApplicationContext applicationContext;
4748
private Map<String, Object> annotatedBeans;
4849
private Set<Class<?>> annotatedClasses;
4950
private Set<Method> annotatedMethods;
5051

5152

52-
Map<String, Object> getAnnotatedBeans() {
53-
return annotatedBeans;
54-
}
55-
56-
Set<Method> getAnnotatedMethods() {
57-
return annotatedMethods;
58-
}
59-
60-
6153
@Override
6254
public void afterPropertiesSet() throws Exception {
6355

64-
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcServiceAdvice.class);
65-
annotatedClasses = findAllAnnotatedClasses();
66-
annotatedMethods = getAnnotatedMethods(annotatedClasses);
56+
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcAdvice.class);
57+
annotatedClasses = extractClassType();
58+
annotatedMethods = findAnnotatedMethods();
6759
}
6860

69-
private Set<Class<?>> findAllAnnotatedClasses() {
61+
private Set<Class<?>> extractClassType() {
7062
return annotatedBeans.values()
7163
.stream()
7264
.map(Object::getClass)
7365
.collect(Collectors.toSet());
7466
}
7567

76-
private Set<Method> getAnnotatedMethods(Set<Class<?>> annotatedClasses) {
68+
private Set<Method> findAnnotatedMethods() {
7769
Function<Class<?>, Stream<Method>> extractMethodsFromClass = clazz -> Arrays.stream(clazz.getDeclaredMethods());
7870
return annotatedClasses.stream()
7971
.flatMap(extractMethodsFromClass)
8072
.filter(method -> method.isAnnotationPresent(GrpcExceptionHandler.class))
8173
.collect(Collectors.toSet());
8274
}
8375

76+
@Override
77+
public void setApplicationContext(final ApplicationContext applicationContext) {
78+
this.applicationContext = applicationContext;
79+
}
80+
8481
boolean isAnnotationPresent() {
8582

8683
return !annotatedClasses.isEmpty() && !annotatedMethods.isEmpty();
8784
}
8885

89-
@Override
90-
public void setApplicationContext(final ApplicationContext applicationContext) {
91-
this.applicationContext = applicationContext;
86+
Map<String, Object> getAnnotatedBeans() {
87+
Assert.state(annotatedBeans != null, "@GrpcAdvice annotation scanning failed.");
88+
return annotatedBeans;
9289
}
90+
91+
Set<Method> getAnnotatedMethods() {
92+
Assert.state(annotatedMethods != null, "@GrpcExceptionHandler annotation scanning failed.");
93+
return annotatedMethods;
94+
}
95+
96+
9397
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) 2016-2020 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.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.lang.reflect.Parameter;
23+
import java.lang.reflect.Type;
24+
import java.util.Map.Entry;
25+
26+
import org.aspectj.lang.annotation.Aspect;
27+
import org.springframework.lang.Nullable;
28+
29+
import lombok.extern.slf4j.Slf4j;
30+
31+
/**
32+
* As part of {@link GrpcAdvice @GrpcAdvice}, when a thrown exception is caught during gRPC calls (via global
33+
* interceptor {@link GrpcAdviceExceptionInterceptor}, then this thrown exception is being handled. By
34+
* {@link GrpcExceptionHandlerMethodResolver} is a mapping between exception and the in case to be executed method
35+
* provided. <br>
36+
* Returned object is declared in {@link GrpcAdvice @GrpcAdvice} classes with annotated methods
37+
* {@link GrpcExceptionHandler @GrpcExceptionHandler}.
38+
* <p>
39+
*
40+
* @author Andjelko Perisic ([email protected])
41+
* @see GrpcExceptionHandlerMethodResolver
42+
* @see GrpcAdviceExceptionInterceptor
43+
*/
44+
@Slf4j
45+
@Aspect
46+
public class GrpcAdviceExceptionHandler {
47+
48+
private final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver;
49+
50+
public GrpcAdviceExceptionHandler(
51+
final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver) {
52+
this.grpcExceptionHandlerMethodResolver = grpcExceptionHandlerMethodResolver;
53+
}
54+
55+
/**
56+
* Given an exception, a lookup is performed to retrieve mapped method. <br>
57+
* In case of successful returned method, and matching exception parameter type for given exception, the exception
58+
* is handed over to retrieved method. Retrieved method is then being invoked.
59+
*
60+
* @param exception exception to search for
61+
* @param <E> type of exception
62+
* @return result of invoked mapped method to given exception
63+
* @throws Throwable rethrows exception if no mapping existent or exceptions raised by implementation
64+
*/
65+
@Nullable
66+
public <E extends Throwable> Object handleThrownException(E exception) throws Throwable {
67+
68+
final Class<? extends Throwable> exceptionClass = exception.getClass();
69+
boolean exceptionIsMapped =
70+
grpcExceptionHandlerMethodResolver.isMethodMappedForException(exceptionClass);
71+
if (!exceptionIsMapped) {
72+
throw exception;
73+
}
74+
75+
Entry<Object, Method> methodWithInstance =
76+
grpcExceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
77+
Method mappedMethod = methodWithInstance.getValue();
78+
Object instanceOfMappedMethod = methodWithInstance.getKey();
79+
Object[] instancedParams = determineInstancedParameters(mappedMethod, exception);
80+
81+
return invokeMappedMethodSafely(mappedMethod, instanceOfMappedMethod, instancedParams);
82+
}
83+
84+
private <E extends Throwable> Object[] determineInstancedParameters(Method mappedMethod, E exception) {
85+
86+
Parameter[] parameters = mappedMethod.getParameters();
87+
Object[] instancedParams = new Object[parameters.length];
88+
89+
for (int i = 0; i < parameters.length; i++) {
90+
Class<?> parameterClass = convertToClass(parameters[i]);
91+
if (parameterClass.isAssignableFrom(exception.getClass())) {
92+
instancedParams[i] = exception;
93+
break;
94+
}
95+
}
96+
return instancedParams;
97+
}
98+
99+
private Class<?> convertToClass(Parameter parameter) {
100+
Type paramType = parameter.getParameterizedType();
101+
if (paramType instanceof Class) {
102+
return (Class<?>) paramType;
103+
}
104+
throw new IllegalStateException("Parameter type of method has to be from Class, it was: " + paramType);
105+
}
106+
107+
private Object invokeMappedMethodSafely(
108+
Method mappedMethod,
109+
Object instanceOfMappedMethod,
110+
Object[] instancedParams) throws Throwable {
111+
Object statusThrowable = null;
112+
try {
113+
statusThrowable = mappedMethod.invoke(instanceOfMappedMethod, instancedParams);
114+
} catch (InvocationTargetException | IllegalAccessException e) {
115+
throw e.getCause(); // throw the exception thrown by implementation
116+
}
117+
return statusThrowable;
118+
}
119+
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2016-2020 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 io.grpc.Metadata;
21+
import io.grpc.ServerCall;
22+
import io.grpc.ServerCall.Listener;
23+
import io.grpc.ServerCallHandler;
24+
import io.grpc.ServerInterceptor;
25+
26+
/**
27+
* Interceptor to use for global exception handling. Every raised {@link Throwable} is caught and being processed.
28+
* Actual processing of exception is in {@link GrpcAdviceExceptionListener}.
29+
* <p>
30+
*
31+
* @author Andjelko Perisic ([email protected])
32+
* @see GrpcAdviceExceptionHandler
33+
* @see GrpcAdviceExceptionListener
34+
*/
35+
public class GrpcAdviceExceptionInterceptor implements ServerInterceptor {
36+
37+
private final GrpcAdviceExceptionHandler grpcAdviceExceptionHandler;
38+
39+
public GrpcAdviceExceptionInterceptor(final GrpcAdviceExceptionHandler grpcAdviceExceptionHandler) {
40+
this.grpcAdviceExceptionHandler = grpcAdviceExceptionHandler;
41+
}
42+
43+
@Override
44+
public <ReqT, RespT> Listener<ReqT> interceptCall(
45+
ServerCall<ReqT, RespT> call,
46+
Metadata headers,
47+
ServerCallHandler<ReqT, RespT> next) {
48+
Listener<ReqT> delegate = next.startCall(call, headers);
49+
return new GrpcAdviceExceptionListener<>(delegate, call, grpcAdviceExceptionHandler);
50+
}
51+
52+
}

0 commit comments

Comments
 (0)