์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํํด๋ณด๋ฉด์ ๋ง๋ ์ ์๋ ์์ธ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ํด๋ผ์ด์ธํธ ์์ฒญ ๋ฐ์ดํฐ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฆ(Validation)์์ ๋ฐ์ํ๋ ์์ธ
- ์๋น์ค ๊ณ์ธต์ ๋น์ฆ๋์ค ๋ก์ง์์ ๋์ ธ์ง๋ ์๋๋ ์์ธ
- ์น ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ค์ ๋ฐ์ํ๋ ์์ธ(RuntimeException)
DTO ์ ํจ์ฑ ๊ฒ์ฆ ์ธก์์ ํด๋ผ์ด์ธํธ์ ์์ฒญ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ ๊ฒฝ์ฐ, ๋ค์๊ณผ ๊ฐ์ ์๋ต ๋ฉ์์ง๋ฅผ ํ์ธ ๊ฐ๋ฅํ๋ค.
๋ค์๊ณผ ๊ฐ์ Response Body ๋ด์ฉ๋ง์ผ๋ก๋ ์์ฒญ ๋ฐ์ดํฐ ์ค์์ ์ด๋ค ํญ๋ชฉ์ด ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ๋์ง ์ ์ ์๋ค.
ํด๋ผ์ด์ธํธ ์ชฝ์์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์กฐ๊ธ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ์น์ ํ๊ฒ ์ ์ ์๋๋ก ๋ฐ๊พธ๋ ์์ ์ด ํ์ํ๋ค.
๐ก@ExceptionHandler๋ฅผ ์ด์ฉํ Controller ๋ ๋ฒจ์์์ ์์ธ ์ฒ๋ฆฌ
ํด๋ผ์ด์ธํธ๊ฐ ์ ๋ฌ๋ฐ๋ Request Body๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ธ๊ฐ ๋ฐ์ํ์ ๋, ๋ด๋ถ์ ์ผ๋ก Spring์์ ์ ์กํด์ฃผ๋ ์๋ฌ ์๋ต ๋ฉ์์ง ์ค ํ๋. Spring์ด ์ฒ๋ฆฌํ๋ ์๋ฌ ์๋ต ๋ฉ์์ง๋ฅผ ์ฐ๋ฆฌ๊ฐ ์ง์ ์ฒ๋ฆฌํ๋๋ก ์ฝ๋๋ฅผ ์์ ํด๋ณด์.
Spring์์์ ์์ธ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ์ด๋ฅผ ์๋ ค์ ์ฒ๋ฆฌํ๋ ๊ฒ๋ฟ๋ง ์๋๋ผ ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ์ ๋์ ๊ฐ์ด ์ด ์คํจ๋ฅผ ํ๋์ ์์ธ๋ก ๊ฐ์ฃผํ์ฌ ์ด ์์ธ๋ฅผ ๋์ ธ์(throw) ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ ๋ํจ.
package com.codestates.member.controller;
import com.codestates.member.dto.MemberPatchDto;
import com.codestates.member.dto.MemberPostDto;
import com.codestates.member.dto.MemberResponseDto;
import com.codestates.member.entity.Member;
import com.codestates.member.mapper.MemberMapper;
import com.codestates.member.service.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.util.List;
/**
* - DI ์ ์ฉ
* - Mapstruct Mapper ์ ์ฉ
*/
@RestController
@RequestMapping("/v6/members")
@Validated
@Slf4j
public class MemberControllerV6 {
private final MemberService memberService;
private final MemberMapper mapper;
public MemberControllerV6(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper;
}
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
Member member = mapper.memberPostDtoToMember(memberDto);
Member response = memberService.createMember(member);
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
Member response =
memberService.updateMember(mapper.memberPatchDtoToMember(memberPatchDto));
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
HttpStatus.OK);
}
...
@ExceptionHandler
public ResponseEntity handlerException(MethodArgumentNotValidException e){
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
}
}
- ํด๋ผ์ด์ธํธ ์ชฝ์์ ํ์ ๋ฑ๋ก์ ์ํด MemberController ์ postMember() ํธ๋ค๋ฌ ๋ฉ์๋์ ์์ฒญ์ ์ ์กํจ.
- RequestBody ์ ์ ํจํ์ง ์์ ์์ฒญ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋์ด ์์ด ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ๊ณ , MethodArgumentNotValidException ์ด ๋ฐ์ํจ.
- MemberController ์๋ @ExceptionHandler ์ ๋ํ ์ด์ ์ด ์ถ๊ฐ๋ ์์ธ ์ฒ๋ฆฌ ๋ฉ์๋์ธ handlerException() ์ด ์๊ธฐ ๋๋ฌธ์ ์ ํจ์ฑ ๊ฒ์ฆ ๊ณผ์ ์์ ๋ด๋ถ์ ์ผ๋ก ๋์ ธ์ง MethodArgumentNotValidException ์ handleException() ๋ฉ์๋๊ฐ ์ ๋ฌ๋ฐ์.
- (1)๊ณผ ๊ฐ์ด MethodArgumentNotValidException ๊ฐ์ฒด์์ getBindingResult().getFieldErrors() ๋ฅผ ํตํด ๋ฐ์ํ ์๋ฌ ์ ๋ณด๋ฅผ ํ์ธ ๊ฐ๋ฅํจ.
- (1)์์ ์ป์ ์๋ฌ ์ ๋ณด๋ฅผ (2)์์ ResponseEntity ๋ฅผ ํตํด Response Body๋ก ์ ๋ฌํจ.
ํ์ ๋ฑ๋ก ์ ๋ณด์์ ์ ํจํ์ง ์์ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ํฌํจํด ์์ฒญ์ ์ ์กํ ๊ฒฐ๊ณผ๋ก Response Body๋ฅผ ๋ณด๋ฉด ์ด์ ๊ณผ ๋ค๋ฅธ ์๋ต ๋ฉ์์ง๋ฅผ ์ ๋ฌ ๋ฐ์ ๊ฒ์ ์ ์ ์์.
[
{
"codes": [
"Email.memberPostDto.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"memberPostDto.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"defaultMessage": ".*",
"codes": [
".*"
],
"arguments": null
}
],
"defaultMessage": "must be a well-formed email address",
"objectName": "memberPostDto",
"field": "email",
"rejectedValue": "jeein@",
"bindingFailure": false,
"code": "Email"
}
]
์ ํจ์ฑ ๊ฒ์ฌ ์คํจ์ ๋ํ ์๋ฌ ๋ฉ์์ง๋ฅผ ๊ตฌ์ฒด์ ์ผ๋ก ์ ๋ฌํด์ฃผ๊ธฐ ๋๋ฌธ์ ํด๋ผ์ด์ธํธ ์ ์ฅ์์๋ ์ด์ ์ด๋ ๊ณณ์ ๋ฌธ์ ๊ฐ ์๋์ง๋ฅผ ๊ตฌ์ฒด์ ์ผ๋ก์ ์ ์๊ฒ ๋จ.
๊ทธ๋ฐ๋ฐ ํด๋ผ์ด์ธํธ ์ ์ฅ์์๋ ์๋ฏธ๋ฅผ ์ ์ ์๋ ์ ๋ณด๋ฅผ ์ ๋ถ ํฌํจํ Response Body ์ ์ฒด ์ ๋ณด๋ฅผ ๊ตณ์ด ๋ค ์ ๋ฌ ๋ฐ์ ํ์๋ ์์ด ๋ณด์.
์์ฒญ ์ ์ก์, Request Body์ JSON ์ ํ๋กํผํฐ ์ค์์ ๋ฌธ์ ๊ฐ ๋ ํ๋กํผํฐ๋ ๋ฌด์์ธ์ง์ ์๋ฌ ๋ฉ์์ง ์ ๋๋ง ์ ๋ฌ๋ฐ์๋ ์ถฉ๋ถ!
์๋ฌ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ Error Response ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ํ์ํ ์ ๋ณด๋ฅผ ๋ด์ ํ์ ํด๋ผ์ด์ธํธ ์ชฝ์ ์ ๋ฌํด์ฃผ๋ฉด ๋จ.
๐กErrorResponse ํด๋์ค ์ ์ฉ
package com.codestates.response.v1;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.validation.FieldError;
import java.util.List;
@Getter
@AllArgsConstructor
public class ErrorResponse {
private List<FieldError> fieldErrors;
@Getter
@AllArgsConstructor
public static class FieldError{
private String field;
private Object rejectedValue;
private String reason;
}
}
DTO ํด๋์ค์ ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ ์, ์คํจํ ํ๋(๋ฉค๋ฒ ๋ณ์)์ ๋ํ Error์ ๋ณด๋ง ๋ด์์ ์๋ต์ผ๋ก ์ ์กํ๊ธฐ ์ํ ErrorResponse ํด๋์ค.
Response Body๋ฅผ ๋ณด๋ฉด JSON ์๋ต ๊ฐ์ฒด๊ฐ ๋ฐฐ์ด์ธ ๊ฒ์ ์ ์ ์์.
๋ฐฐ์ด์ธ ์ด์ ๋ฅผ ์๊ฐํด ๋ณด๋ฉด DTO ํด๋์ค์์ ๊ฒ์ฆํด์ผ ๋๋ ๋ฉค๋ฒ ๋ณ์์์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ๋ ๋ฉค๋ฒ ๋ณ์๋ค์ด ํ๋ ์ด์์ด ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ ์๋ฌ ์ญ์ ํ๋ ์ด์์ด ๋ ์ ์๋ค๋ ์๋ฏธ.
ํ ๊ฐ์ ํ๋ ์๋ฌ ์ ๋ณด๋ FieldError ๋ผ๋ ๋ณ๋์ static class๋ฅผ ErrorResponse ํด๋์ค์ ๋ฉค๋ฒ ํด๋์ค๋ก ์ ์.
package com.codestates.member.controller.controller_v7;
import com.codestates.member.dto.MemberPostDto;
import com.codestates.member.entity.Member;
import com.codestates.response.v1.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/v7/members")
@Validated
@Slf4j
public class MemberControllerV7 {
...
...
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
Member member = mapper.memberPostDtoToMember(memberDto);
Member response = memberService.createMember(member);
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
HttpStatus.CREATED);
}
...
...
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e) {
// (1)
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
// (2)
List<ErrorResponse.FieldError> errors =
fieldErrors.stream()
.map(error -> new ErrorResponse.FieldError(
error.getField(),
error.getRejectedValue(),
error.getDefaultMessage()))
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
}
(2)์ ๊ฐ์ด ํ์ํ ์ ๋ณด๋ค๋ง ์ ํ์ ์ผ๋ก ๊ณจ๋ผ์ ErrorResponse.FieldError ํด๋์ค์ ๋ด์์ List๋ก ๋ณํ ํ, List<ErrorResponse.FieldError>๋ฅผ ResponseEntity ํด๋์ค์ ์ค์ด์ ์ ๋ฌ
๐ก@ExceptionHandler์ ๋จ์
@ExceptionHandler ์ ๋ํ ์ด์ ๊ณผ ErrorResponse ํด๋์ค๋ฅผ ์ด์ฉํด์ Request Body์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ ์ ํ์ํ ์๋ฌ ์ ๋ณด๋ง ๋ด์์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ต์ผ๋ก ์ ์กํ ์ ์๊ฒ ๋จ.
ํ์ง๋ง, ๊ฐ๊ฐ์ Controller ํด๋์ค์์ @ExceptionHandler ์ ๋ํ ์ด์ ์ ์ฌ์ฉํ์ฌ Request Body์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ์ ๋ํ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํด์ผ ๋๋ฏ๋ก ๊ฐ Controller ํด๋์ค๋ง๋ค ์ฝ๋ ์ค๋ณต์ด ๋ฐ์ํจ.
Controller์์ ์ฒ๋ฆฌํด์ผ ๋๋ ์์ธ(Exception)๊ฐ ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ์ ๋ํ ์์ธ(MethodArgumentNotValidException)๋ง ์๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ํ๋์ Controller ํด๋์ค ๋ด์์ @ExceptionHandler๋ฅผ ์ถ๊ฐํ ์๋ฌ ์ฒ๋ฆฌ ํธ๋ค๋ฌ ๋ฉ์๋๊ฐ ๋์ด๋จ.
@ExceptionHandler
public ResponseEntity handleException(ConstraintViolationException e){
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
patchMember() ํธ๋ค๋ฌ ๋ฉ์๋์ URI ๋ณ์์ธ "/{member-id}" ์ 0์ด ๋์ด์ฌ ๊ฒฝ์ฐ, ConstraintViolationException์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ConstraintViolationException ์ ์ฒ๋ฆฌํ @ExceptionHandler๋ฅผ ์ถ๊ฐํ ๋ฉ์๋๋ฅผ ํ๋ ๋ ์ถ๊ฐํจ.
์ด์ฒ๋ผ @ExceptionHandler ์ ๋ํ ์ด์ ์ ์ฌ์ฉํ ๋ฐฉ์์ ์ฝ๋ ์ค๋ณต๊ณผ ์ ์ฐํ์ง ์์ ์์ธ ์ฒ๋ฆฌ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ์์ธ ์ฒ๋ฆฌ์ ๋ํ ๊ฐ์ ์ด ํ์ํจ.
๐ก@RestControllerAdvice๋ฅผ ์ฌ์ฉํ ์์ธ ์ฒ๋ฆฌ ๊ณตํตํ
ํน์ ํด๋์ค์ @RestControllerAdvice ์ ๋ํ ์ด์ ์ ์ถ๊ฐํ๋ฉด ์ฌ๋ฌ ๊ฐ์ Controller ํด๋์ค์์ @ExceptionHandler, @InitBinder, ๋๋ @ModelAttribute ๊ฐ ์ถ๊ฐ๋ ๋ฉ์๋๋ฅผ ๊ณต์ ํด์ ์ฌ์ฉ ๊ฐ๋ฅํจ.
cf. @InitBinder๋ ์์ฒญ ๋งค๊ฐ๋ณ์๋ฅผ Java Bean ๊ฐ์ฒด์ ๋ฐ์ธ๋ฉํ๋ ๋ฐ ์ฌ์ฉ๋๋ WebDataBinder ์ธ์คํด์ค๋ฅผ ์ด๊ธฐํํ๋ ๋ฉ์๋๋ฅผ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋๋ ์ฃผ์.
@InitBinder, @ModelAttribute ์ ๋ํ ์ด์ ์ JSP, Thymeleaf ๊ฐ์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR, Server Side Rendering) ๋ฐฉ์์์ ์ฃผ๋ก ์ฌ์ฉ๋๋ ๋ฐฉ์.
์ด ๋ง์ ์๋ฏธ๋ฅผ ์์ธ ์ฒ๋ฆฌ ๊ด์ ์์ ๋ณด๋ฉด, @RestControllerAdvice ์ ๋ํ ์ด์ ์ ์ถ๊ฐํ ํด๋์ค๋ฅผ ์ด์ฉํ๋ฉด ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ณตํตํํ ์ ์๋ค๋ ๊ฒ.
๐กExceptionAdvice ํด๋์ค ์ ์
Controller ํด๋์ค์์ ๋ฐ์ํ๋ ์์ธ๋ค์ ๊ณตํต์ผ๋ก ์ฒ๋ฆฌํ ExceptionAdvice ํด๋์ค๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ์.
package com.codestates.advice;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionAdvice {
}
์์ธ๋ฅผ ์ฒ๋ฆฌํ ExceptionAdvice ํด๋์ค์ @RestControllerAdvice ์ ๋ํ ์ด์ ์ ์ถ๊ฐํ๋ฉด ์ด ํด๋์ค๋ ์ด์ Controller ํด๋์ค์์ ๋ฐ์ํ๋ ์์ธ๋ฅผ ๋๋งก์์ ์ฒ๋ฆฌํ๊ฒ ๋จ.
๐กException ํธ๋ค๋ฌ ๋ฉ์๋ ๊ตฌํ
package com.codestates.advice;
import com.codestates.response.v1.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionAdvice {
//(1)
public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ErrorResponse.FieldError> errors=
fieldErrors.stream()
.map(error->new ErrorResponse.FieldError(
error.getField(),
error.getRejectedValue(),
error.getDefaultMessage()
))
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
//(2)
@ExceptionHandler
public ResponseEntity handleConstraintViolationException(ConstraintViolationException e){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
@RestControllerAdvice ์ ๋ํ ์ด์ ์ ์ด์ฉํด์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ณตํตํํ๋ฉด ๊ฐ Controller๋ง๋ค ์ถ๊ฐ๋๋ @ExceptionHandler ๋ก์ง์ ๋ํ ์ค๋ณต ์ฝ๋๋ฅผ ์ ๊ฑฐํ๊ณ , Controller์ ์ฝ๋๋ฅผ ๋จ์ํํ ์ ์์.
๐กErrorResponse ์์
GlobalExceptionAdvice๋ฅผ ํตํด Controller ํด๋์ค์์ ๋ฐ์ํ๋ RequestBody์ ์ ํจ์ฑ ๊ฒ์ฆ์ ๋ํ ์๋ฌ๋ ์ ์ฐํ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ง.
URI ๋ณ์๋ก ๋์ด์ค๋ ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฆ์ ๋ํ ์๋ฌ(ConstraintViolationException)์ฒ๋ฆฌ๋ ์์ง ๊ตฌํ๋์ง ์์.
ConstraintViolationException์ ๋ํ ์ฒ๋ฆฌ๋ ํ ์ ์๋๋ก ํจ. ์ด ๋ถ๋ถ์ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ๋จผ์ ErrorResponse ํด๋์ค๊ฐ ConstraintViolationException์ ๋ํ Error Response๋ฅผ ์์ฑํ ์ ์๋๋ก ErrorResponse ํด๋์ค๋ฅผ ์์ .
package com.codestates.response.v1;
import lombok.Getter;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import javax.validation.ConstraintViolation;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Getter
public class ErrorResponse {
//(1)์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ ํ๋์ ์๋ฌ ์ ๋ณด๋ฅผ ๋ด๋ ๋ฉค๋ฒ ๋ณ์.
private List<FieldError> fieldErrors;
//(2)URI ๋ณ์ ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจ๋ก ๋ฐ์ํ ์๋ฌ ์ ๋ณด๋ฅผ ๋ด๋ ๋ฉค๋ฒ ๋ณ์
private List<ConstraintViolationError> violationErrors;
//(3)ErrorResponse ์์ฑ์์ private ์ ๊ทผ ์ ํ์๋ฅผ ์ง์ ํจ์ผ๋ก์จ of() ๋ฉ์๋๋ฅผ ์ด์ฉํด์ผ ํ๋ฉฐ, ์ด๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ์์ฑํจ๊ณผ ๋์์ ErrorResponse ์ญํ ์ ๋ช
ํํ๊ฒ ํด ์ค.
private ErrorResponse(List<FieldError> fieldErrors, List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErrors;
this.violationErrors = violationErrors;
}
//(4)BindingResult์ ๋ํ ErrorResponse ๊ฐ์ฒด ์์ฑ
public static ErrorResponse of(BindingResult bindingResult){
return new ErrorResponse(FieldError.of(bindingResult), null);
}
//(5)Set<ConstraintViolation<?>> ๊ฐ์ฒด ์ ๋ํ ErrorResponse ๊ฐ์ฒด ์์ฑ
public static ErrorResponse of(Set<ConstraintViolation<?>> violations){
return new ErrorResponse(null, ConstraintViolationError.of(violations));
}
//(6) Field Error ๊ฐ๊ณต, ํ๋์ ์ ํจ์ฑ ๊ฒ์ฆ์์ ๋ฐ์ํ๋ ์๋ฌ ์ ๋ณด๋ฅผ ์์ฑํจ.
@Getter
public static class FieldError{
private String field;
private Object rejectedValue;
private String reason;
public FieldError(String field, Object rejectedValue, String reason) {
this.field = field;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<FieldError> of(BindingResult bindingResult){
final List<org.springframework.validation.FieldError> fieldErrors =
bindingResult.getFieldErrors();
return fieldErrors.stream()
.map(error->new FieldError(
error.getField(),
error.getRejectedValue()==null?
"":error.getRejectedValue().toString(),
error.getDefaultMessage()
)).collect(Collectors.toList());
}
}
//(7) ConstraintViolation Error ๊ฐ๊ณต, URI ๋ณ์ ๊ฐ์ ๋ํ ์๋ฌ ์ ๋ณด๋ฅผ ์์ฑํจ.
@Getter
public static class ConstraintViolationError{
private String propertyPath;
private Object rejectedValue;
private String reason;
public ConstraintViolationError(String propertyPath, Object rejectedValue, String reason) {
this.propertyPath = propertyPath;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<ConstraintViolationError> of(Set<ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream()
.map(constraintViolation -> new ConstraintViolationError(
constraintViolation.getPropertyPath().toString(),
constraintViolation.getInvalidValue().toString(),
constraintViolation.getMessage()
)).collect(Collectors.toList());
}
}
}
๐กException ํธ๋ค๋ฌ ๋ฉ์๋ ์์
package com.codestates.advice;
import com.codestates.response.v1.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
final ErrorResponse response=ErrorResponse.of(e);
return response;
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleConstraintViolationException(ConstraintViolationException e){
final ErrorResponse response = ErrorResponse.of(e.getConstraintViolations());
return response;
}
}
Error Response ์ ๋ณด๋ฅผ ๋ง๋๋ ์ญํ ์ ErrorResponse ํด๋์ค๊ฐ ๋์ ํด์ฃผ๊ธฐ ๋๋ฌธ์ ์ฝ๋ ์์ฒด๊ฐ ๊ฐ๊ฒฐํด์ง.
๋ ํ๋ ์ค์ํ ๋ณ๊ฒฝ์ฌํญ์ ErrorResponse ๊ฐ์ฒด๋ฅผ ResponseEntity๋ก ๋ํํด์ ๋ฆฌํดํ๋ ๋ฐ๋ฉด, ์์ ์ฝ๋์์๋ ResponseEntity๊ฐ ์ฌ๋ผ์ง๊ณ ErrorResponse ๊ฐ์ฒด๋ฅผ ๋ฐ๋ก ๋ฆฌํดํ๊ณ ์์.
@ResponseStatus ์ ๋ํ ์ด์ ์ ์ด์ฉํด์ HTTP Status๋ฅผ HTTP Response์ ํฌํจํ๊ณ ์์.
๐ก@RestControllerAdvice vs @ControllerAdvice
Spring MVC 4.3 ๋ฒ์ ์ดํ๋ถํฐ @RestControllerAdvice ์ ๋ํ ์ด์ ์ ์ง์ํ๋๋ฐ, ๋ ์ฌ์ด์ ์ฐจ์ด์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
@RestControllerAdvice = @ControllerAdvice + @RepsonseBody
@RestControllerAdvice ์ ๋ํ ์ด์ ์ @ControllerAdvice์ ๊ธฐ๋ฅ์ ํฌํจํ๊ณ ์์ผ๋ฉฐ, @ResponseBody์ ๊ธฐ๋ฅ ์ญ์ ํฌํจํ๊ณ ์๊ธฐ ๋๋ฌธ์ JSON ํ์์ ๋ฐ์ดํฐ๋ฅผ Response Body๋ก ์ ์กํ๊ธฐ ์ํด์ ResponseEntity๋ก ๋ฐ์ดํฐ๋ฅผ ๋ํ ํ ํ์๊ฐ ์๋ค
'Spring๐ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Security - JWT ์ธ์ฆ(Authentication) (0) | 2023.05.17 |
---|---|
Spring MVC - JUnit์ ์ฌ์ฉํ ๋จ์ ํ ์คํธ (0) | 2023.04.30 |
Spring MVC - JDBC ๊ธฐ๋ฐ ๋ฐ์ดํฐ ์ก์ธ์ค ๊ณ์ธต (0) | 2023.04.18 |
Spring Core - DI (0) | 2023.04.10 |
Spring Framework ๊ธฐ๋ณธ, ํน์ง, ์ฌ์ฉํ๋ ์ด์ (0) | 2023.04.02 |