@ControllerAdvice
@ControllerAdvice란 Controller 계층 전역에서 발생하는 에러를 잡아 처리해주는 애노테이션이다.
@ExceptionHandler
@ExceptionHandler는 Controller로 등록된 빈에서 발생하는 에러를 메서드에서 처리해주는 애노테이션이다.
구성
MemberController
@RestController
@RequestMapping("member")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@PostMapping("/signup")
public ResponseEntity signup(@Valid @RequestBody SignupRequestDto signupRequestDto) {
return memberService.signup(signupRequestDto);
}
}
SignupRequestDto
@Getter
@Setter
public class SignupRequestDto {
@NotBlank
@Size(min = 4, max = 10)
@Pattern(regexp = "^[a-z0-9]*$")
private String userId;
@NotBlank
@Size(min = 8, max = 15)
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,15}")
private String password;
}
Member
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String userId;
@Column(nullable = false)
private String password;
public Member(SignupRequestDto requestDto) {
this.userId = requestDto.getUserId();
this.password= requestDto.getPassword();
}
}
MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
MemberService
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* 회원가입
*/
public ResponseEntity signup(SignupRequestDto requestDto) {
Member member = new Member(requestDto);
memberRepository.save(member);
return new ResponseEntity(member, HttpStatus.OK);
}
}
@ControllerAdvice, @ExceptionHandler 예외처리
@Valid 를 통해 유효성 검사를 할 때, 유효하지 않은 경우 MethodArgumentNotValidException 에러가 발생한다.
@ControllerAdvice 애노테이션을 통해 모든 Controller 에서 예외를 찾고, @ExceptionHandler와 함께 캐치할 에러를 작성하여 예외처리가 가능하다.
예외처리를 적용하기 전 PostMan으로 테스트해보면 다음과 같은 결과가 나온다.
콘솔창에는 다음과 같이 뜬다.
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity com.example.sample.controller.MemberController.signup(com.example.sample.dto.SignupRequestDto) with 2 errors: [Field error in object 'signupRequestDto' on field 'password': rejected value [1234]; codes [Pattern.signupRequestDto.password,Pattern.password,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [signupRequestDto.password,password]; arguments []; default message [password],[Ljakarta.validation.constraints.Pattern$Flag;@4ccb9e30,(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\W)(?=\S+$).{8,15}]; default message ["(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\W)(?=\S+$).{8,15}"와 일치해야 합니다]] [Field error in object 'signupRequestDto' on field 'password': rejected value [1234]; codes [Size.signupRequestDto.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [signupRequestDto.password,password]; arguments []; default message [password],15,8]; default message [크기가 8에서 15 사이여야 합니다]] ]
@ControllerAdvice와 @ExceptionHandler를 적용해보고 다시 결과를 확인해보자.
ExceptionAdvice
@ControllerAdvice
public class ExceptionAdvice {
/**
* 회원가입 Valid 예외
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public String signValidException(MethodArgumentNotValidException exception) {
BindingResult bindingResult = exception.getBindingResult();
StringBuilder builder = new StringBuilder();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
builder.append("[");
builder.append(fieldError.getField());
builder.append("](은)는 ");
builder.append(fieldError.getDefaultMessage() + "\n");
}
return builder.toString();
}
}
해당 과정을 한 번 살펴보자.
@Valid에서 MethodArgumentNotValidException 에러가 발생하면 해당 메서드로 진입한다.
BindingResult 객체에는 해당 에러에 대한 여러 정보가 담겨있다. BindingResult 객체 안에는 FieldErrors라는 리스트가 있는데, 이 리스트에는 에러와 관련한 정보가 담긴 FieldError 객체가 들어있다.
반복문을 통해 각각의 FieldError 객체 안에 담긴 에러가 발생한 필드명, 메시지를 가져와 반환한다.
FieldError에 담기는 정보는 다음과 같다.
public class FieldError extends ObjectError {
private final String field;
@Nullable
private final Object rejectedValue;
private final boolean bindingFailure;
/* ... */
}
field: 예외가 발생한 field
RejectedValue: 어떤 값으로 인해 예외가 발생했는가
DefaultMessage: 예외가 발생했을 때 제공할 메세지
PostMan을 통해 결과를 확인하면 다음과 같이 예외처리가 된 것을 확인할 수 있다.