Spring MVC : 컨트롤러 익셉션 처리하기
1. 컨트롤러 익셉션 처리하기
다음은 ID가 존재할 때의 출력 결과 화면이다. 없는 ID를 경로변수로 사용하면 다음 그림처럼 MemberNotFountException이 발생한다.(회원 데이터가 존재하지 않을 경우 MemberDetailController가 익셉션을 발생시키도록 구현했다)
코드가 궁금하다면 이곳에서 확인하자.
!(그림)
MemberDetailController가 사용하는 경로 변수는 Long 타입인데 실제 요청 경로에 숫자가 아닌 문자를 입력해보자 “/members/a” 주소를 입력하면 “a”를 Long타입으로 변환할 수 없기 때문에 다음과 같이 400에러가 발생한다.
!(그림)
익셉션 화면이 보이는 것보다 알맞게 익셉션을 처리해서 사용자에게 더 적합한 안내를 해 주는 것이 더 좋다. MemberNotFoundException은 try-catch로 잡은 뒤 안내 화면을 보여주는 뷰를 보여주면 될 것 같다. 그런데 타입 변환 실패에 따른 익셉션은 어덯게 해야 에러 화면을 보여줄 수 있을 까? 이럴 때 유용하게 사용할 수 있는 것이 바로 @ExceptionHandler애노테이션이다.
같은 컨트롤러에 @ExceptionHandler 애노테이션을 적용한 메서드가 존재하면 그 메서드가 익셉션을 처리한다.
따라서 컨트롤러에서 발생한 익셉션을 직접 처리하고 싶다면 @ExcptionHandler 애노테이션을 적용한 메서드를 구현하면 된다. 다음은 그 예이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.beans.TypeMismatchException;
import org.springframework.web.bind.annotation.ExceptionHandler;
...
@Controller
public class MemberDetailController{
...
@GetMapping("/members/{id}")
public String detail(@PathVariable("id") Long memId, Model model){
Member member = memberDao.selectById(memID);
if(member == null){
throw new MemberNotFoundException();
}
model.addAttribute("member",member);
return "member/memberDetail";
}
@ExceptionHandler(TypeMismatchException.class)
public String handleTypeMismatchException(){
return "member/invalidID";
}
@ExceptionHandler(MemberNotFoundException.class)
public String handleNotFoundException(){
return "member/noMember";
}
}
-
19행 코드를 보면 @ExceptionHandler의 값으로 TypeMismatchException.class를 주었다. 이 익셉션은 경로 변수값의 타입이 올바르지 않을 때 발생한다. 이 익셉션이 발생하면 에러 응답을 보내는 대신 handleTypeMismatchException() 메서드를 실행한다. 비슷하게 detail() 메서드를 실행하는 과정에서 MemberNotFoundException이 발생하면 24행의 handleNotFoundException() 메서드를 이용해서 익셉션을 처리한다.
-
@ExceptionHandler 애노테이션을 적용한 메서드는 컨트롤러 요청 매핑 애노테이션 적용 메서드와 마찬가지로 뷰 이름을 리턴할 수 있다. 21행 26행에서는 각각 서로 다른 뷰 이름을 리턴했다.
-
익셉션 객체에 대한 정보를 알고 싶다면 메서드의 파라미터로 익셉션 객체를 전달받아 사용하면 된다.
1 2 3 4 5
@ExceptionHandler(TypeMismatchException.class) public String handleTypeMismatchException(TypeMistmatchException ex){ // ex 사용해서 로그 남기는 등 작업 return "member/invalidId"l }
2. @ControllerAdvice를 이용한 공통 익셉션 처리
컨트롤러 클래스에 @ExceptionHandler 애노테이션을 적용하면 해당 컨트롤러에서 발생한 익셉션만을 처리한다. 다수의 컨트롤러에서 동일 타입의 익셉션이 발생할 수도 있다. 이때 익셉션 처리 코드가 동일하다면 어떻게 해야 할까? 각 컨트롤러 클래스마다 익셉션 처리 메서드를 구현하는 것은 불필요한 코드 중복을 발생시킨다.
여러 컨트롤러에서 동일하게 처리할 익셉션이 발생하면 @ControllerAdvice 애노테이션을 이용해서 중복을 없앨 수 있다.
다음은 그 예이다.
1
2
3
4
5
6
7
8
9
10
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice("spring")
public class CommonExceptionHandler{
@ExceptionHandler(RuntimeException.class)
public String hadleRuntimeException(){
return "error/commonException";
}
}
@ControllerAdvice 애노테이션이 적용된 클래스는 지정한 범위의 컨트롤러에 공통으로 사용될 설정을 지정할 수 있다. 위 코드는 “spring” 패키지와 그 하위 패키지에 속한 컨트롤러 클래스를 위한 공통 기능을 정의했다.
spring 패키지와 그 하위 패키지에 속한 컨트롤러에서 RuntimeException이 발생하면 handleRuntimeException()메서드를 통해서 익셉션을 처리한다.
@ControllerAdvice 적용 클래스가 동작하렴녀 해당 클래스를 스프링에 빈으로 등록해야 한다.
3. ExceptionHandler 적용 메서드의 우선 순위
@ControllerAdvice 클래스에 있는 @ExceptionHandler 메서드와 컨트롤러 클래스에 있는 @ExceptionHandler 메서드 중 컨트롤러 클래스에 적용된 @ExceptionHandler 메서드가 우선한다. 즉 컨트롤러의 메서드를 실행하는 과정에서 익셉션이 발생하면 다음의 순서로 익셉션을 처리할 @ExceptionHandler 메서드를 찾는다.
- 같은 컨트롤러에 위치한 @ExceptionHandler 메서드 중 해당 익셉션을 처리할 수 있는 메서드를 검색
- 같은 클래스에 위치한 메서드가 익셉션을 처리할 수 없을 경우 @ControllerAdvice 클래스에 위치한 @ExceptionHandler 메서드를 검색
@ControlerAdvice 애노테이션은 공통 설정을 적용할 컨트롤러 대상을 지정하기 위해 다음과 같은 속성을 제공한다.
속성 | 타입 | 설명 |
---|---|---|
value basePackages |
String[] | 공통 설정을 적용할 컨트롤러가 속하는 기준 패키지 |
annotations | Class<? extends Annotation>[ ] | 특정 애노테이션이 적용된 컨트롤러 대상 |
assignableTypes | Class<?>[ ] | 특정 타입 또는 그 하위 타입인 컨트롤러 대상 |
4.@ExceptionHandler 애노테이션 적용 메서드의 파라미터와 리턴 타입
@ExceptionHandler 애노테이션을 붙인 메서드는 다음 파라미터를 가질 수 있다.
- HttpServletRequest, HttpServletResponse, HttpSession
- Model
- 익셉션
리턴 가능한 타입은 다음과 같다.
- ModelAndView
- String (뷰이름)
- (@ResponseBody 애노테이션을 붙인 경우) 임의 객체
- ResponseEntity
Ref.
- 최범균, 스프링프로그래밍입문5, 가메출판사.