소개
AOP에 대한 간략한 설명과 예시코드를 작성했다.
ArgumentResolver
설명
매개변수에 임의의 클래스를 넣고, 해당 클래스에 미리 값을 부여하는 기능
기본 코드
java@Component @RequiredArgsConstructor public class CustomArgsResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(Object.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return null; } }
- 사용법 WebMvcConfig
활용 코드
java// 사용자 정보 미리 저장 // Header의 Token 정보를 이용하여, 사용자 정보를 미리 가져온다 // 인증/인가 중 "인가"의 역할을 해준다 @Component @RequiredArgsConstructor public class UserArgsResolver implements HandlerMethodArgumentResolver { private final JwtTokenProvider jwtTokenProvider; private final UserRepository userRepository; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(UserDto.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String authorization = request.getHeaders("Authoriaztion"); if (authorization == null) return null; if (!authorization.startsWith("Bearer ")) return null; String token = authorization.substring(7); String id = jwtTokenProvider.getUsername(token); UserEntity entity = userRepository.findById(Long.valueOf(id)).orElse(null); if (entity == null) return null; return UserDto.builder().build(); } }
Interceptor
설명
Filter 진입 후, Controller 로 들어가기 전 사이의 로직 단계 Filter 와 다르게 Database 접근 등의 WAS 로직을 사용할 수 있다.
기본 코드
java@Component @RequiredArgsConstructor public class CustomInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 허용 여부에 대한 로직 추가 return true; } }
- 사용법 WebMvcConfig
활용 코드
java// JwtAuthenticationFilter의 경우 인
Aspect
설명
@Aspect로 지정된 클래스에 아래와 같은 Advice를 지정할 수 있다@Before("${pattern}"): 지정된 패턴 실행 전 먼저 동작. void로 리턴@Around("${pattern}"): 지정된 패턴 실행 전, 실행 후 모두 동작. Object로 리턴@After("${pattern}"): 지정된 패턴 실행 후 동작. void로 리턴@AfterReturning(value = "${pattern}", returning = "result"): 지정된 패턴 실행이 성공이면 동작. void로 리턴. 성공으로 리턴 받은 변수 이름을 returning에 지정한다@AfterThrowing("${pattern}", throwing = "ex"): 지정된 패턴실행이 실패면 동작. void로 리턴. Exception 변수 이름을 throwing에 지정한다.
- PointCut 표현식 (자주 사용한 것만 적었다)
- 작성법 Regex
-
- : 모든 것을 포함
- .. : 0개 이상을 포함
-
execution메소드가 실행될 때 동작- 예시 : @Around("execution(* com.example.demo.controller..*Controller.*(..))")
annotationAnnotation 클래스의 Package 주소 또는 변수에 지정된 Annotation 클래스 변수 이름 작성- 예시 : @Around("@annotation(com.package.CustomAnnotation)") @Around("@annotation(actionLog)")
- 작성법 Regex
@Order를 사용하여 Aspect의 실행 순서를 지정할 수 있다.
코드
- aspect 예시 코드
java@Aspect @Component @RequiredArgsConstructor public class NoticeEncryptAspect { @Before("") public void before(JoinPoint joinPoint) { } @Around("") public Object around(ProceedingJoinPoint joinPoint) { return joinPoint.proceed(); } @After("") public void after(JoinPoint joinPoint) { } @afterReturning("") public void afterReturning(JoinPoint joinPoint) { } @AfterThrowing("") public void afterThrowing(JoinPoint joinPoint) { } }
- annotation 예시 코드
java// 기본 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CustomAnnotation { } // 변수가 들어갈 때 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CustomAnnotation { String targetId() default ""; }
- SpEL
- Method에 있는 파라미터의 변수이름을
#변수이름으로 #을 붙이면 가져올 수 있다.
- Method에 있는 파라미터의 변수이름을
RestControllerAdvice
Controller에 대해 응답 처리에 대한 공통사항을 설정하는데 사용된다. 주로 Exception 처리시 사용된다.
ExceptionHandler
Exception.class로 예외 발생시에 대한 리턴값을 보여준다.
java/** * 제일 무난한 설정 */ @Slf4j @RestControllerAdvice // 공통 @RestControllerAdvice(basePackages = "") // 특정 Package 안의 Controller 클래스를 설정하고 싶은 경우 @RestControllerAdvice(annotations = CustomAnnotation.class) // 특정 Annotation의 컨트롤러에만 설정하고 싶은 경우 public class GlobalExceptionHandler { // 약속된 Exception처리가 있을 때 @ExceptionHandler(CustomException.class) @ResponseStatus(HttpStatus.OK) // 원하는 Response Status 코드로 변경 가능 public CustomResponse<String> handleNoticeApiException(NoticePlusApiException ex) { if (ex.getCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { if (null != ex.getCause()) { log.error(ex.getMessage(), ex.getCause().getMessage(), ex.getCause()); } else { log.error(ex.getMessage(), ex); } } return CustomResponse.<String>builder() .code(ex.getCode()) .message(ex.getMessage()) .data(null) .build(); } // @Valid를 통한 validation에 대해 충족하지 않은 조건인 경우 @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public CustomResponse<String> handleValidationExceptions(MethodArgumentNotValidException ex) { String errorMessage = ex.getBindingResult().getAllErrors().getFirst().getDefaultMessage(); return CustomResponse.builder() .code(ResponseEnum.INVALID_PARAM.getCode()) .message(errorMessage) .build(); } }
ResponseBodyAdvice
공통된 ResponseBody로 일괄 처리가 가능하다. 해당 기능 사용시, Controller의 return, ExceptionHandler return 모두 이곳을 지나가게 된다.
java@RestControllerAdvice public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { Class<?> type = returnType.getParameterType(); // 파일 다운로드로 리턴해야하는 경우 예외 처리 if (Resource.class.isAssignableFrom(type)) return false; if (StreamingResponseBody.class.isAssignableFrom(type)) return false; if (byte[].class.isAssignableFrom(type)) return false; // 그 밖에 모두 허용 return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // Controller의 Return 값, ExceptionHandler의 Return값은 다 여기로 온다. // swagger를 사용중이라면ㅋ String path = request.getURI().getPath(); if (path.startsWith("/v3/api-docs") || path.startsWith("/swagger-ui") || path.startsWith("/swagger-resources")) { return body; } // 이미 Response로 정의되어있다면 통과 if (body instanceof CustomResponse) { return body; } return CustomResponse.builder() .code(HttpStatus.OK.getCode()) .message(HttpStatus.OK.getMessage()) .data(body) .build(); } }