Programming/Spring

[Spring] 전역 예외 처리 방법 (@ControllerAdvice, @ExceptionHandler)

JeongKyun 2022. 3. 13.
반응형

서론

필자는 모든 예외사항을 고려하여 개발을 하는것은 불가능하다고 생각이 든다. 그래서 이 예외 처리는 정말 고마운 녀석이라는 생각이 든다.

 

이번 글에서는 Spring에서 예외처리 클래스를 생성하여 전역 또는 특정 패키지 별예외 처리를 할 수 있는 방법을 소개 하려한다.

 


 

예외 처리란 ?

우리는 항상 에러(Error)와 예외에 대해 나눠서 생각해야 한다. 

 

여기서 에러는 "보통 발생 시 수습할 수 없는 심각한 오류"를 말하며,

예외는 "예외 처리를 통해 수습할 수 있는 덜 심각한 오류"를 말한다.

 

에러는 아예 발생하지 않게 처리를 해줘야 하는것에 반면,

예외는 try ~ catch문을 통하여 예외 발생 시 상황에 맞는 구현을 통해 예외 상황을 재활용 할 수 있게 해준다.

 

[예외처리 예시]

(예외처리 예시 - 1)

서버 - 클라이언트 구조에서,

비정상적인 연결종료로 인해 catch문이 실행이되면 서버는 Listen상태로 만들고 클라이언트쪽에서는 Connect하는 상태를 만들 때 사용되는 경우가 있다.

 

(예외처리 예시 - 2)

메서드안에서 어떠한 작업을 처리할 때 객체에 null값이 들어가는 경우있어 NullPointException이 발생하는 경우가 있다. 이런 경우에 우리는 보통 try catch문으로 잡는다.

 

그런데 프로젝트가 커질수록 해당 예와 마찬가지로 공통적인 예외처리(try catch)가 많아지는 것을 알 것이다. 이럴때 이번 글에서 소개하는 방법을 사용하면 공통적인 예외처리를 하나의 클래스에서 실행되게끔 할 수 있다. 

 

이제 아래에서 해당 방법들에 대해 알아보자.

 


@ControllerAdvise란?

전역에서 발생할 수 있는 모든 예외를 잡아 처리해주는 Annotaion이다.

 

사용 방법

//@ControllerAdvice //모든 패키지 적용
@ControllerAdvice("com.exception.ch") // 지정된 패키지에서만 적용
public class GlobalCatcher {
	@ExceptionHandler(Exception.class)
	public String catcher(Exception ex) {		
		return "error";
	}

	@ExceptionHandler(NullPointerException.class)
	public String catcher2(Exception ex) {
		return "error";
	}

위와 같이 사용할 수 있으며 @ControllerAdvice뒤에 패키지 명을 적으면 해당 패키지에만

적용이 되며 생략할 경우 모든 패키지에 적용이 된다.

 

여기서 @ExceptionHandler는 무엇일까?

 

@ExceptionHandler란?

@Controller, @RestController가 적용된 Bean내에서 발생하는 예외를 잡아서 하나의 메서드에서 처리해주는 기능을 한다.

 

아래 예시와 같이 뒤에 {}를 활용하여 여러 예외 클래스를 한번에 사용할 수도 있다.

@ExceptionHandler({Exception.class, NullPointerException.class})
	public String catcher(Exception ex) {	
		return "error";
	}

 

이제 이것을 활용하기 전과 후로 예시를 통해 확인해보자.

 


 

사용 전/후 예시

-- 사용 전 --

[Controller 클래스]

@Controller
public class ExceptionController {
	
	@ExceptionHandler({Exception.class, NullPointerException.class})
	public String catcher(Exception ex) {
		return "error";
	}

	@ExceptionHandler(NullPointerException.class)
	public String catcher2(Exception ex) {
		return "error";
	}
	
	@RequestMapping("/ex")
	public String main() throws Exception {
			try {
				throw new Exception("예외가 발생했습니다.");
			} catch (Exception e) {
				return "error";
			}
	}		
			
}

 

 

-- 사용 후 --

[GlobalCatcher 클래스]

/* GlobalCatcher 클래스 생성 */
//@ControllerAdvice //모든 패키지 적용
@ControllerAdvice("com.exception.ch") // 지정된 패키지에서만 적용
public class GlobalCatcher {
	@ExceptionHandler(Exception.class)
	public String catcher(Exception ex) {
		m.addAttribute("ex", ex);		
		return "error";
	}

	@ExceptionHandler(NullPointerException.class)
	public String catcher2(Exception ex) {
		m.addAttribute("ex", ex);
		return "error";
	}
}

[Controller 클래스]

@Controller
public class ExceptionController {
	
	@RequestMapping("/ex")
	public String main() throws Exception {
			throw new Exception("예외가 발생했습니다.");
		}		
	
	@RequestMapping("/ex2")
	public String main2() throws Exception {
			throw new NullPointerException("예외가 발생했습니다.");
		}		
}

전과 후의 차이는 하나의 클래스에서 @ExceptionHandler를 선언하냐 안하냐이다.

 

컨트롤러 클래스가 많아지면 많아질수록 예외처리를 하는 공통적인 소스도 더불어 많아질 것이다. 이럴때 위 예시처럼 @ControllerAdvice를 선언한 GlobalCatcher라는 클래스를 만들어 예외 처리 소스를 줄인다면 소스도 많이 깨끗해지고 유지보수를 하는데도 꽤나 좋아질 것이라 예상된다.

 

[※참고※]

@ControllerAdvice 실행 순서 (에러 발생 클래스 -> @ControllerAdvice)

@ControllerAdvice클래스는 에러가 발생한 지점에 예외처리가 없다면 실행되는 순서를 갖고있다.

 

만약 위에 기술한 [예시1]처럼 해당 메서드안에서만 처리하고싶은 예외를 만들고싶다면 해당 메서드 안에서 따로 메서드 처리를 해줘서 ControllerAdvice는 실행되지 않게 해주면 된다.

이렇게 상황에 따라 컨트롤러 클래스 안에서 구현하는 것도 필요하다.

 

 

마치며..

예외처리 기법을 활용하여 보다 유지보수성이 높은 좋은 소스를 만들어보자 :)

댓글

💲 많이 본 글