티스토리 뷰
이번 포스팅에서는 Spring Boot에서 사용되는 핵심 기술인 AOP에 대해서 간략하게 설명하고, 이를 통해 구현하는 대표적인 예시인 Exception Handler를 구현하는 방법에 대해서 살펴본다.
AOP?
AOP는 Aspect Oriented Programming 의 약자로 OOP(Object Oriented Programming) 의 단점을 보완하기 위한 프로그래밍 방식이다. 간략하게 설명하면 여러 곳에서 사용되는 공통 기능을 모듈화하고, 쓰이는 곳에 필요할 때 연결함으로써 유지보수, 재사용성에 용이하도록 프로그래밍 하는 것을 AOP라고 한다.
공통 기능 before(), after() 메서드가 존재하는 것을 확인할 수 있고, 이를 AOP를 적용해 공통된 요소로 추출하는 것이다. 결국 공통된 기능을 재사용하는 기법인 것이다.
자세한 내용과 다양한 방법으로 AOP를 활용하는 것은 추후에 다루도록 한다.
Exception Handler with AOP
일반화된 예외를 처리하기 위해 AOP를 활용해 Exception Handler를 구현하는 방법을 살펴보고, 이렇게 구현된 Exception Handler를 어떻게 사용하는지 알아보자.
우선 @ExceptionHandler 는 하나의 클래스(컨트롤러 등)에 메서드에 추가되어 해당 클래스의 Exception Handler로써 동작한다. 조금 더 직관적인 예제를 살펴보면 다음과 같다.
@RestController
public class MyRestController {
@Autowired
private MyService myService;
@GetMapping("/hello")
public String hello() {
return "hello"; //문자열 반환
}
@GetMapping("/myData") public MyData myData() {
return new MyData("myName"); //object 반환
}
@GetMapping("/service")
public String serviceCall() {
return myService.serviceMethod(); //일반적인 service호출
}
@GetMapping("/serviceException")
public String serviceException() {
return myService.serviceExceptionMethod(); //service에서 예외발생
}
@GetMapping("/controllerException") public void controllerException() {
throw new NullPointerException(); //controller에서 예외발생
}
@GetMapping("/customException")
public String custom() {
throw new CustomException(); //custom예외 발생
}
@ExceptionHandler(NullPointerException.class)
public Object nullex(Exception e) {
System.err.println(e.getClass());
return "myService";
}
}
출처: https://jeong-pro.tistory.com/195 [기본기를 쌓는 정아마추어 코딩블로그]
MyRestController 내부에서 발생한 NullPointerException 의 경우 기본 Exception Handler가 아니라 @ExceptionHandler 어노테이션을 추가한 nullex Exception Handler에 의해서 처리된다.
이와같이 @ExceptionHandler 어노테이션을 사용할 경우 하나의 클래스에 대해서 발생하는 예외를 처리해주는 Exception Handler를 추가할 수 있다. 그렇다면 하나의 클래스가 아니라 모든 클래스(컨트롤러 등)에서 발생하는 예외를 처리하고 싶으면 어떻게 할까?
이때 사용할 수 있는 개념이 바로 AOP이다.
@ControllerAdvice 어노테이션은 모든 컨트롤러에서 발생할 수 있는 예외를 해당 어노테이션을 추가한 클래스에 존재하는 Exception Handler로 처리할 수 있도록 해준다. @RestControllerAdvice도 있는데 이는 @Controller + @ResponseBody 어노테이션이 추가된 어노테이션이다. 즉 @RestController + @ControllerAdvice 어노테이션을 사용하면 동일한 효과를 볼 수 있다.
import com.ch4njun.cspm.demo.assessment.AssessmentResultNotFoundException;
import com.ch4njun.cspm.demo.history.HistoryAlreadyExistsException;
import com.ch4njun.cspm.demo.history.HistoryNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
@RestController
@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(HistoryNotFoundException.class)
public final ResponseEntity<Object> handleHistoryNotFoundException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(HistoryAlreadyExistsException.class)
public final ResponseEntity<Object> handleHistoryAlreadyExistsException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(AssessmentResultNotFoundException.class)
public final ResponseEntity<Object> handleAssessmentResultNotFoundException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
}
이 예제에서 볼 수 있듯이 사용자가 개인적으로 커스터마이징해 추가한 예외까지도 Exception Handler를 통해 처리할 수 있는 것을 확인할 수 있다.
@ControllerAdvice가 이와같이 동작할 수 있는 것은 모든 컨트롤러가 실행될 때 반드시 해당 Bean이 실행되어야(? 포함되어야?) 한다는 것을 의미하기 때문이다. 즉 내부적으로 모든 컨트롤러에 @ControllerAdvice 어노테이션이 붙은 Bean이 추가된다는 의미이다.
위 코드를 잠시 살펴보면 ExceptionResponse 클래스와, ResponseEntitty<Object> 클래스가 무엇인지 설명하면, ExceptionResponse는 에러가 발생했을때 Client에게 보여줄 정보를 포함하는 객체이다.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
private Date timestamp;
private String message;
private String details;
}
기본적으로 timestamp, message, details 를 포함했지만 구현하는 사람에 따라서 추가하거나 삭제할 수 있다. ResponseEntity<Object> 객체는 HTTP Status를 다르게해 Client에게 보낼때 사용하는 객체이다. 보낼 Object와 두 번째 인자로 아래와 같이 HTTP Status를 지정할 수 있다.
@ExceptionHandler(AssessmentResultNotFoundException.class)
public final ResponseEntity<Object> handleAssessmentResultNotFoundException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
'Back-End > Spring Boot' 카테고리의 다른 글
[Reactive Programming] 1. Iterable Pattern vs Observer Pattern (0) | 2021.09.20 |
---|---|
[Spring Boot] Data Validation(Bean Validation + Hibernate Validation) (0) | 2021.04.13 |
[Spring Boot] Bean과 의존성 주입(Dependency Injection) (0) | 2021.04.07 |
[Spring Boot] @Controller와 @RestController (0) | 2021.04.05 |
[Spring Boot] Lombok (0) | 2021.04.04 |