Spring Boot에서의 Exception 처리

Web Application의 입장에서, 에러가 발생했을 때 처리해줄 수 있는 방법은 많지 않다.
크게 에러 페이지, 4 ~ 500번대 에러, 별도의 에러 메세지 전달 정도가 있다.

아래 예제 코드를 통해 확인해보도록 하자.

name(String), age(Integer) 속성을 가지고 있는 User 객체에, controller에서 Integer와 int의 합을 도출하도록 해서 억지로 에러를 발생시켜 보았다.

User 코드 확인하기
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
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.exception.dto;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {

@NotEmpty
@Size(min = 2, max = 10)
private String name;

@Min(1)
@NotNull
private Integer age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Controller 코드 확인하기
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
package com.example.exception.controller;

import com.example.exception.dto.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class ApiController {

@GetMapping("")
public User get(@RequestParam(required = false) String name, @RequestParam(required = false) Integer age){
User user = new User();
user.setName(name);
user.setAge(age);
// 에러 발생 시키기
int a = 10+age;

return user;
}

@PostMapping("")
public User post(@RequestBody User user){
System.out.println(user);
return user;
}
}

Client에게 친절한 Exception 처리는 기본적으로 두 가지 방식이 있다.

이를 Advice(조언) 이라고 한다. Server에서 Client에게 오류에 대한 조언을 해주는 것이다.

관련 Annotation

@RestControllerAdvice

  • 페이징 처리를 하는 view resolver 영역의 white label page, error page를 global하게 처리할 수 있는 집합 장소

@ExceptionHandler

  • Global하게 처리할 때는 RestControllerAdvice Annotation이 붙은 클래스 내에, 특정 Controller의 예외 처리를 할 때 특정 컨트롤러 내부에 사용한다.
  • 동일한 Handler가 Global, Controller에 모두 존재하면 특정 Controller 내부의 handler가 처리된다.
전역으로 처리하는 handler 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.exception.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalControlAdvice {

@ExceptionHandler(value = Exception.class)
public ResponseEntity exception(Exception e){
System.out.println(e.getClass().getName());
System.out.println("------------");
System.out.println(e.getLocalizedMessage());
System.out.println("------------");

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
특정 Exception 처리하는 handler생성
1
2
3
4
5
@ExceptionHandler(value = NullPointerException.class)
public ResponseEntity methodArg(NullPointerException e){

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("값이 유효하지 않습니다.");
}

혹은 RestControllerAdvice Annotation에 basePackageClasses인자를 추가하여 특정 컨트롤러에 한정 시킬 수 있다.

  • @RestControllerAdvice(basePackageClasses = ApiController.class)

Client 단에서 발생할 수 있는 에러

Client가 발생시킬 수 있는 에러는 형식에 맞지 않게 값을 입력하는 것이다. 이름을 1글자만 입력하거나, 나이를 0살로 입력하는 등의 예가 있다.

Validated Annotation을 클래스 단에 추가해준 뒤 GetMapping을 해주는 get 메서드의 RequestParam에 제한 사항을 추가해 주었다.

이름을 최소 2글자 이상 작성하여야 하고, 나이는 최소 1살이며 비워두면 안된다.

Validated 예제코드 확인하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping("/api")
@Validated
public class ApiController {

@GetMapping("")
public User get(
@Size(min = 2)
@RequestParam String name,

@NotNull
@Min(1)
@RequestParam Integer age){
User user = new User();
user.setName(name);
user.setAge(age);

return user;
}
}

처음 접근은 Client단에서 값을 잘못입력했을 때

어떠한 Exception이 발생하는지 확인하는 것으로 시작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// ApiController 클래스에서 발생한 오류
@RestControllerAdvice(basePackageClasses = ApiController.class)
public class ApiControllerAdvice {
// Exception 클래스 내부의 객체가 들어오면 실행 (모든 Exception을 받는다)
@ExceptionHandler(value = Exception.class)
public ResponseEntity exception(Exception e) {
// Exception의 클래스 이름을 출력하여 확인한다.
System.out.println(e.getClass().getName());
// 테스트 코드이므로 Client 단에는 아무것도 출력되지 않도록 하였습니다.
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("");
}
...
}

img_1.png

Chrome의 확장 프로그램인 Talend API를 통해 서버 테스트를 해보았다.

RequestParameter에 아무 값도 넣어주지 않아 아래와 같은 에러가 도출됐다.

img.png

위와 같이 Spring에서 자체적으로 예외에 대한 기본적인 처리는 해준다.

출력된 클래스는 org.springframework.web.bind.MissingServletRequestParameterException가 IDE에 출력되었다.

MissingServletRequestParameterException이 잡혔으니 value값을 이로 설정해서 원하는 정보를 제공할 수 있도록 구현해보자.

1
2
3
4
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("값이 입력되지 않았습니다.");
}

위와 같이 body에 정보를 보내주고, HttpStatus는 400을 띄워주었다.

img.png

보다 Client들이 발생시키는 오류를 줄일 수 있다.

Spring Boot에서의 Exception 처리

http://inwoo.github.io/10/18/SpringException/

Author

Inwoo Jeong

Posted on

2021-10-18

Updated on

2021-10-28

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

댓글