-
비동기 Timelimiter 를 동기로 호출 가능하도록 변경Spring/CircuitBreaker 2023. 10. 10. 10:50
목차
1. 배경 지식
dependencies
dependencies { implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'io.github.resilience4j:resilience4j-spring6:2.1.0' }
2. TimeLimiterAspectExt 상속
package io.github.resilience4j.spring6.timelimiter.configure; public interface TimeLimiterAspectExt { boolean canHandleReturnType(Class<?> returnType); Object handle(ProceedingJoinPoint proceedingJoinPoint, TimeLimiter timeLimiter, String methodName) throws Throwable; }
3. canHandleReturnType 구현
@Override public boolean canHandleReturnType(Class<?> returnType) { return !(CompletionStage.class.isAssignableFrom(returnType)) && timeLimiterAspectExtList.stream().noneMatch(ext -> ext.canHandleReturnType(returnType)); }
: 비동기이면 returnType 바꾸지 않기위해 false, 동기이면 returntype 바꾸기 위해 true 리턴 합니다
1) CompletionStage.class.isAssignableFrom(returnType) : 주어진 클래스가 CompletionStage 클래스의 자손인지 여부를 확인합니다
2) timeLimiterAspectExtList.stream().noneMatch(ext -> ext.canHandleReturnType(returnType) : canHandleReturnType 에서 지원되는 타입인지를 검사합니다
- 지원되는 타입 : void,String,Number,Boolean,Iterable,Map,Object
4. handle 구현
배경지식
ProceedingJoinPoint class
org.aspectj.lang.ProceedingJoinPoint는 AspectJ에서 제공하는 인터페이스입니다. 이 인터페이스는 현재 실행 중인 메서드 또는 필드에 대한 정보를 제공하며, 메서드의 실행을 제어할 수 있는 기능을 제공합니다.
ProceedingJoinPoint 인터페이스에는 다음과 같은 메서드가 정의되어 있습니다.
- proceed(): 메서드 실행을 계속 진행합니다.
- getThis(): 메서드가 실행 중인 객체를 반환합니다.
- getTarget(): 메서드가 실행 중인 객체의 클래스 객체를 반환합니다.
- getArgs(): 메서드에 전달된 인수를 반환합니다.
- getSignature(): 메서드의 시그니처를 반환합니다.
- getKind(): JoinPoint의 종류(메서드 실행 전/후, 예외 발생 시 등)를 반환합니다.
Advice에서는 ProceedingJoinPoint를 이용하여 메서드 실행을 제어하거나, 메서드 실행 중에 발생한 예외를 처리할 수 있습니다. 예를 들어, 로깅 기능을 구현하는 Advice에서는 ProceedingJoinPoint를 이용하여 메서드 실행 전/후에 로그를 기록할 수 있습니다.
ExposeInvocationInterceptor.currentInvocation()
ExposeInvocationInterceptor.currentInvocation() 메서드는 현재 실행 중인 AOP Alliance MethodInvocation 객체를 반환합니다.
이 객체는 AOP 프레임워크에서 제공하는 인터페이스이며, 현재 실행 중인 메서드에 대한 정보를 제공하고 메서드의 실행을 제어할 수 있는 기능을 제공합니다.
ExposeInvocationInterceptor는 특수한 AOP 인터셉터로서, 현재 실행 중인 MethodInvocation 객체를 스레드 로컬 객체에 저장합니다.
따라서 ExposeInvocationInterceptor.currentInvocation() 메서드는 언제 어디서든 현재 실행 중인 MethodInvocation 객체에 액세스할 수 있습니다.
다음은 ExposeInvocationInterceptor.currentInvocation() 메서드를 사용하는 예제 코드입니다.
public class MyAdvice { @AroundAdvice public Object handle(ProceedingJoinPoint joinPoint) throws Throwable { // 현재 실행 중인 MethodInvocation 객체 MethodInvocation invocation = ExposeInvocationInterceptor.currentInvocation(); // MethodInvocation 객체의 타겟 객체 Object target = invocation.getThis(); // MethodInvocation 객체의 메서드 Method method = invocation.getMethod(); // ... return joinPoint.proceed(); } }
다른 쓰레드에서 실행될 때, methodInvocation 을 복사해 주는 용도로 사용합니다
구현
@Override public Object handle(ProceedingJoinPoint proceedingJoinPoint, TimeLimiter timeLimiter, String methodName) throws Throwable { final MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); CompletionStage<Object> completionStage = timeLimiter.executeCompletionStage(scheduledExecutorService, () -> CompletableFuture.supplyAsync(() -> { try { // thread 간 methodInvocation 복사 @SuppressWarnings("unchecked") final var threadLocal = (ThreadLocal<MethodInvocation>)invocationField.get(null); threadLocal.set(mi); return proceedingJoinPoint.proceed(); } catch (CompletionException e) { throw e; } catch (Throwable e) { throw new CompletionException(e); } }, scheduledExecutorService)); // 예외 처리 try { return completionStage.toCompletableFuture().join(); } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause instanceof TimeoutException) { throw e; } else { throw cause; } } }
1) 쓰레드 간 methodInvocation 을 복사해 줍니다
2) CompletionException Exception 을 잡아서 전달해줍니다
'Spring > CircuitBreaker' 카테고리의 다른 글
Resilience4j 관련 (0) 2023.09.24 Micrometer 관련 (0) 2023.09.24