๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

[Spring] @Valid, @ControllerAdvice, @Exception์„ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ฒ˜๋ฆฌ

by ์•ˆ์ฃผํ˜• 2022. 5. 20.

์„œ๋ก 

๋ณดํ†ต @ControllerAdvice์™€ @ExceptionHandler๋ฅผ ์ด์šฉํ•˜์—ฌ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ๋ถ„๋ฆฌ ๋ฐ ํ†ตํ•ฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ํ•ด๋‹น ๋ฐ์ดํ„ฐ์— ์—๋Ÿฌ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ํ•„๋“œ์— ์ ์šฉํ–ˆ๋˜ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋งŒ ๊น”๋”ํ•˜๊ฒŒ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์ด ๊ถ๊ธˆํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด

public class User {

    private String name;
    @Min(value = 19, message = "๋‚˜์ด๋Š” 19์‚ด ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    private int age;
    
}

์œ„์™€ ๊ฐ™์€ Entitiy์ผ ๋•Œ ๋‚˜์ด๋ฅผ 10์‚ด๋กœ ํ•˜๊ณ  Post๋ฅผ ํ•˜์˜€์„ ๋•Œ ์•„๋ž˜ ์ด๋ฏธ์ง€์˜ ์ฒซ ๋ฒˆ์งธ ๊ฒฐ๊ณผ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‘ ๋ฒˆ์งธ ๊ฒฐ๊ณผ์ฒ˜๋Ÿผ details ๋ถ€๋ถ„์—์„œ default message์ธ "๋‚˜์ด๋Š” 19์‚ด ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."๋งŒ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ๊ฒฐ๊ณผ
๋‘ ๋ฒˆ์งธ ๊ฒฐ๊ณผ

์ฆ‰, MethodArgumentNotValidException ex ์—์„œ default message๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์ด ๊ถ๊ธˆํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ•ด๋‹น ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋ง์”€๋“œ๋ฆฌ์ž๋ฉด ํ˜„์žฌ ex.getMessage()๋ฅผ ํ†ตํ•ด error ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ defaultMessage๋งŒ ํŒŒ์‹ฑ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ex.getBindingResult().getAllErrors().get(0).getDefaultMessage()
ํ•˜์ง€๋งŒ ๋‚ด์šฉ์ด ๋ถ€์‹คํ•  ๊ฒƒ ๊ฐ™์•„ ์œ„ ๋‚ด์šฉ์„ ํฌํ•จํ•˜์—ฌ @Valid๋ฅผ ์ด์šฉํ•œ ๊ฐ„๋‹จํ•œ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•๊ณผ @ControllerAdvice์™€ @ExceptionHandler๋ฅผ ์ด์šฉํ•˜์—ฌ ํ†ตํ•ฉ๋œ json ๋ฐ˜ํ™˜ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
 

๋ณธ๋ก 

public class User {

    private String name;
    @Min(value = 19, message = "๋‚˜์ด๋Š” 19์‚ด ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    private int age;
    
}

๋จผ์ € Entity ํด๋ž˜์Šค๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฒ€์ฆํ•  ํ•„๋“œ์— Annotation์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๋Š”๋ฐ, ํ˜„์žฌ ์œ„ ์ฝ”๋“œ๋Š” ๋‚˜์ด๊ฐ€ 19์‚ด ์ด์ƒ์ด์–ด์•ผ๋งŒ ํ•œ๋‹ค๋Š” ์กฐ๊ฑด์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

@RestController
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<User> createUsers(@Valid @RequestBody User user) {
        return ResponseEntity.ok(user);
    }

    @GetMapping("/users/{id}")
    public ResponseEntity<Integer> getUsers(@PathVariable int id) {
        if (id != 1) {
            throw new UserNotFoundException(String.format("์•„์ด๋”” %d์„ ๊ฐ€์ง„ ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", id));
        }
        return ResponseEntity.ok(id);
        
    }

Controller ํด๋ž˜์Šค๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. POST ๋ฉ”์„œ๋“œ์—์„œ User์˜ ํ•„๋“œ๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ๋งค๊ฐœ๋ณ€์ˆ˜์— @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. 
๋˜ํ•œ ๊ฐ„๋‹จํ•˜๊ฒŒ GET ๋ฉ”์„œ๋“œ์—์„œ๋Š”  id์˜ ๊ฐ’์ด 1์ด ์•„๋‹ ๊ฒฝ์šฐ์— ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
    private LocalDateTime time;
    private Boolean isSuccess;
    private String message;
    private String details;
}

์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ์˜ ํ†ตํ•ฉ์ ์œผ๋กœ  jsonํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด Response๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException() {
        super();
    }

    public UserNotFoundException(String message) {
        super(message);
    }

    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserNotFoundException(Throwable cause) {
        super(cause);
    }

    protected UserNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

ํ•ด๋‹น ์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ๋กœ ํ•˜์˜€๊ธฐ์—, UserNotFoundException.class๋Š” ์œ„์™€ ๊ฐ™์ด ๊ธฐ์ดˆ์ ์ธ ๋ฉ”์„œ๋“œ๋ฅผ overide ํ–ˆ์Šต๋‹ˆ๋‹ค.

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(LocalDateTime.now(), false, "์œ ํšจ์„ฑ ๊ฒ€์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        
        return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handlerNotFoundUserException(Exception ex) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(LocalDateTime.now(), false, "์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", ex.getMessage());
        
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }

}

@ControllerAdvice์ž…๋‹ˆ๋‹ค. 

  • ์ฒซ ๋ฒˆ์งธ ๋ฉ”์„œ๋“œ๋Š” @Valid ๊ฒ€์ฆ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
  • ๋‘ ๋ฒˆ์งธ ๋ฉ”์„œ๋“œ๋Š”UserNotFoundException๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ์„ ๋•Œ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

 

๊ฒฐ๊ณผ

POST ๋ฉ”์„œ๋“œ
GET ๋ฉ”์„œ๋“œ

 

์ฐธ๊ณ 

๋Œ“๊ธ€