@Aspect
@Component
@RequiredArgsConstructor
public class SlackNotificationAspect {
private final SlackApi slackApi;
@Around("@annotation(com.example.demo.global.annotation.SlackNotification)")
public Object slackNotification(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpServletRequest originalRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
ContentCachingRequestWrapper request = originalRequest instanceof ContentCachingRequestWrapper
? (ContentCachingRequestWrapper) originalRequest
: new ContentCachingRequestWrapper(originalRequest);
String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
SlackAttachment slackAttachment = new SlackAttachment();
slackAttachment.setFallback("Error");
slackAttachment.setColor("danger");
slackAttachment.setTitle("Error Detected");
slackAttachment.setTitleLink(request.getContextPath());
slackAttachment.setFields(Arrays.asList(
new SlackField().setTitle("Request URL").setValue(request.getRequestURL().toString()),
new SlackField().setTitle("Request Method").setValue(request.getMethod()),
new SlackField().setTitle("Request Time").setValue(new Date().toString()),
new SlackField().setTitle("Headers").setValue("Authorization: " + request.getHeader("Authorization")),
new SlackField().setTitle("Request Body").setValue(requestBody),
new SlackField().setTitle("Request IP").setValue(request.getRemoteAddr()),
new SlackField().setTitle("Request User-Agent").setValue(request.getHeader("User-Agent"))
));
SlackMessage slackMessage = new SlackMessage();
slackMessage.setAttachments(Collections.singletonList(slackAttachment));
slackMessage.setIcon(":ghost:");
slackMessage.setText("Error Detected");
slackMessage.setUsername("DutyPark");
slackApi.call(slackMessage);
return proceedingJoinPoint.proceed();
}
}
ContentCachingRequestWrapper
와 같은 래퍼를 사용하여 요청 본문을 내부 버퍼에 저장, 요청 본문에 대한 읽기를 가능하게 하였다.((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
→ RequestContextHolder
클래스를 이용하여 서블릿 필터나 인터셉터에서 HTTP 요청이 시작될 때 HTTP 요청과 관련된 정보(HttpServletRequest
, HttpServletResponse
)를 ServletRequestAttributes
에 저장한다.ServletRequestAttributes
은 Spring Context 안에 들어왔을 때 관리되는 객체, Spring Context 에 들어오기 전이라면 HttpServletRequest
, HttpServletResponse
로 관리가 된다.@Component
public class CachingRequestBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request instanceof ContentCachingRequestWrapper) {
filterChain.doFilter(request, response);
} else {
ContentCachingRequestWrapper cachingWrapper = new ContentCachingRequestWrapper(request);
filterChain.doFilter(cachingWrapper, response);
}
}
}
OncePerRequestFilter
를 상속 받아 필터가 각 요청에 대해 한번만 실행되도록 보장한다.filterChain.doFilter
를 사용하여 다음 필터나 디스패치 서블릿으로 넘겨준다.@Aspect
@Component
@RequiredArgsConstructor
public class SlackNotificationAspect {
private final SlackApi slackApi;
private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Around("@annotation(com.example.demo.global.annotation.SlackNotification)")
public Object slackNotification(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpServletRequest originalRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
ContentCachingRequestWrapper request = originalRequest instanceof ContentCachingRequestWrapper
? (ContentCachingRequestWrapper) originalRequest
: new ContentCachingRequestWrapper(originalRequest);
String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
SlackAttachment slackAttachment = new SlackAttachment();
slackAttachment.setFallback("Error");
slackAttachment.setColor("danger");
slackAttachment.setTitle("Error Detected");
slackAttachment.setTitleLink(request.getContextPath());
slackAttachment.setFields(Arrays.asList(
new SlackField().setTitle("Request URL").setValue(request.getRequestURL().toString()),
new SlackField().setTitle("Request Method").setValue(request.getMethod()),
new SlackField().setTitle("Request Time").setValue(new Date().toString()),
new SlackField().setTitle("Headers").setValue("Authorization: " + request.getHeader("Authorization")),
new SlackField().setTitle("Request Body").setValue(requestBody),
new SlackField().setTitle("Request IP").setValue(request.getRemoteAddr()),
new SlackField().setTitle("Request User-Agent").setValue(request.getHeader("User-Agent"))
));
SlackMessage slackMessage = new SlackMessage();
slackMessage.setAttachments(Collections.singletonList(slackAttachment));
slackMessage.setIcon(":ghost:");
slackMessage.setText("Error Detected");
slackMessage.setUsername("DutyPark");
threadPoolTaskExecutor.execute(() -> slackApi.call(slackMessage));
// slackApi.call(slackMessage);
return proceedingJoinPoint.proceed();
}
}
ThreadPoolTaskExecutor
→ 스레드 풀 기반으로 동시에 여러 태스크를 비동기적으로 처리할 수 있도록 스레드를 관리함.execute()
→ 주어진 Runnable 객체(여기서는 람다 표현식으로 둠)를 비동기적으로 실행하는 역할을 함.@EnableAsync
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setMaxPoolSize(5); //최대 스레드 수
threadPoolTaskExecutor.setCorePoolSize(5); //기본 스레드 수
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}