본문 바로가기

내일배움캠프

Spring 심화주차 Part3

1. Spring AOP

spring aop의 원리

Spring AOP는 특정 기능들이 실행되기 전, 후로 미리 정해진 기능이 수행되는 부가기능을 모듈화한 것을 뜻한다.

사진처럼 핵심기능이 실행되기 전, 후로 실행되는 부가기능을 Spring AOP라고 할 수 있다.

 

Spring AOP에는 실제 실행되는 기능인 어드바이스와 기능이 실행되는 위치에 대한 정보인 포인트컷 두 가지 내용물로 이루어져 있다.

 

부가기능에 해당하는 클래스에는 @Aspect 어노테이션을 붙여야 한다.

 

어드바이스는 메소드 위에 어드바이스를 의미하는 어노테이션을 붙여서 표현한다. 어드바이스임을 표시하는 어노테이션에는 여러 종류가 있다.

  • @Around: '핵심기능' 수행 전과 후 (@Before + @After)
  • @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)
  • @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
  • @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)
  • @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)

포인트컷은 Pointcut Expression을 위에 설명한 어드바이스 관련 어노테이션의 속성으로 추가하거나 @Pointcut이라는 어노테이션의 속성에 추가한다.

Pointcut Expression: 

execution(modifiers-pattern? return-type-pattern declaring-type-pattern?
method-name-pattern(param-pattern) throws-pattern?)

modifiers-pattern: public, private, *(접근 제어자 상관없이 모두 적용), 생략가능

return-type-pattern : void, String, List, *(리턴형 상관없이 모두 적용) ... 리턴되는 자료형

declaring-type-pattern: 생략 가능

com.example.package.controller.* : controller의 하위 패키지의 모든 클래스에 적용

com.example.package.controller..: controller에 속하는 모든 클래스와 하위 패키지에 속하는 모든 클래스에 적용

method-name-pattern: 메소드 이름

예시: addFolders-> addFolders라는 이름의 메소드에만 적용

add* -> add로 시작하는 모든 메소드에 적용

(param-pattern): 매개변수

예시: (com.example.package.requestDto) -> requestDto타입의 객체만 받음

() : 매개변수 없음

(*) : 매개변수 1개(타입은 상관없음)

(..) : 매개변수 0~N개(타입은 상관없음

 

 

Spring AOP 예시:

@Slf4j(topic = "request 정보")
@Aspect
@Component
public class RequestLog {
    @Pointcut("execution(* com.sparta.newspeed.*.controller..*(..))")
    private void forAllController() {}

    @After("forAllController()")
    public void showRequestLog(JoinPoint joinPoint) throws Throwable {
        final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        final String method = request.getMethod();
        final String requestUri = request.getRequestURI();

        log.info("URI : " + requestUri + " method : " + method);
    }
}

@Pointcut을 적용할 때는 빈 메소드를 선언하고 빈 메소드 위에 붙여야 한다.

 

@After의 포인트 컷 속성으로 @Pointcut이 붙은 메소드 이름을 추가한다.

예시의 AOP는 핵심 기능이 실행된 후에만 실행되고 핵심 기능의 조건은 이렇다.

  • 접근 제어자: 상관없음
  • 리턴 타입: 상관없음
  • 패키지 이름: com.sparta.newspeed에 속하는 모든 하위 패키지의 controller패키지 및 controller의 하위 패키지에 속하는 모든 클래스
  • 메소드 이름: 상관없음
  • 매개변수: 갯수 제한없음(0~N개)

2. api의 예외처리

지금까지 spring에서 에러가 발생하면 콘솔에만 메세지를 출력해서 내부적으로 에러를 처리하는 방법으로만 예외를 처리했다.

지금 보여주는 예시는 예외가 발생하면 예외가 발생했을 때의 상태 코드와 메세지를 리턴하는 방법을 예시로 보여줄 것이다.

 

우선 예외가 발생할 때 실행되는 메소드를 만들 것이다.

@Getter
@AllArgsConstructor
public class RestApiException {
    private String errorMessage;
    private int statusCode;
}
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class FolderController {
    private final FolderService folderService;
    @PostMapping("/folders")
    public void addFolders(@RequestBody FolderRequestDto folderRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        List<String> folderNames = folderRequestDto.getFolderNames();
        folderService.addFolders(folderNames, userDetails.getUser());
    }
    @GetMapping("/folders")
    public List<FolderResponseDto> getFolders(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        return folderService.getFolders(userDetails.getUser());
    }

    @ExceptionHandler({IllegalArgumentException.class})
    public ResponseEntity<RestApiException> handleException(IllegalArgumentException ex) {
        System.out.println("FolderController.handleException");
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.BAD_REQUEST
        );
    }
}

 

 

IllegalArgumentException 예외를 throw하고 catch를 하면 handleException이 실행된다. handleException은 예외가 발생할 때 생성되는 메세지와 statuscode를 responseentity에 담아서 리턴하는 exception handler 메소드이다.

 

@ExceptionHandler는 예외처리를 위한 어노테이션으로 특정 컨트롤러에서 발생한 예외처리에 사용된다. 예시에서의 Exceptionhandler는 FolderController에서만 동작한다.

 

이렇게 예외처리하면 모든 컨트롤러마다 throw하는 예외 클래스 별로 Exceptionhandler를 만들어야 하는 번거로움이 있다.

 

3. Global 예외처리

spring에서는 앞서 보여준 exceptionhandler를 모든 컨트롤러에서 예외를 받아서 처리할 수 있는 방법이 있다.

@ControllerAdvice는 예외처리를 위한 어노테이션으로 모든 컨트롤러에서 발생한 예외를 처리하도록 만들 수 있다.

예시:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({IllegalArgumentException.class})
    public ResponseEntity<RestApiException> illegalArgumentExceptionHandler(IllegalArgumentException ex) {
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.BAD_REQUEST
        );
    }

    @ExceptionHandler({NullPointerException.class})
    public ResponseEntity<RestApiException> nullPointerExceptionHandler(NullPointerException ex) {
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.NOT_FOUND.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.NOT_FOUND
        );
    }

    @ExceptionHandler({ProductNotFoundException.class})
    public ResponseEntity<RestApiException> notFoundProductExceptionHandler(ProductNotFoundException ex) {
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.NOT_FOUND.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.NOT_FOUND
        );
    }
}

예시에는 @RestColtrollerAdvice를 사용했는데 이건 @ControllerAdvice기능에 @RequestBody기능이 붙어서 뷰를 리턴하는 대신 값 그 자체를 리턴하게 만들 수 있다.(여기서는 responseEntity객체를 만들어서 그대로 리턴할 수 있게 했다.)

'내일배움캠프' 카테고리의 다른 글

아웃소싱 프로젝트 2일차  (0) 2024.06.21
아웃소싱 프로젝트 1일차  (0) 2024.06.19
Spring 심화주차 Part2  (0) 2024.06.18
gradle의 clean기능  (0) 2024.06.13
Spring 심화주차 Part1  (0) 2024.06.13