ExceptionHandler와 Validation

서지우·2023년 8월 2일

Spring Boot

목록 보기
11/18

AuthController.java

주석으로 처리된 모델앤뷰 보다 스트링으로 많이쓴다.

    // @GetMapping("/auth/login")
    // public ModelAndView login() {
    //     ModelAndView modelAndView = new ModelAndView();
    //     modelAndView.setViewName("auth/login");
    //     return modelAndView;
    // }

    @GetMapping("/auth/login")
    public String login() {
        return "auth/login";
    }

    // @GetMapping("/auth/join")
    // public ModelAndView join() {
    //     ModelAndView modelAndView = new ModelAndView();
    //     modelAndView.setViewName("auth/join");
    //     return modelAndView;
    // }

    @GetMapping("/auth/join")
    public String join() {
        return "auth/join";
    }

    // @GetMapping("/auth/logout")
    // public ModelAndView logout(HttpSession session) {

    //     session.invalidate();

    //     ModelAndView modelAndView = new ModelAndView();
    //     // 다른 페이지로 이동하겠다..
    //     modelAndView.setViewName("redirect:/auth/login");
    //     return modelAndView;
    // }

    @GetMapping("/auth/logout")
    public String logout(){
        return "redirect:/auth/login";
    }

익섹셥 핸들러

@ExceptionHandler는 Controller계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능이다.
Service, Repository에서 발생하는 에러는 제외한다.

여러개의 Exception 처리 중 @RestControllerAdvice를 사용할 것이다.


@RestControllerAdvice

@RestControllerAdvice는 @ControllerAdvice와 @ResponseBody을 가지고 있다.
@Controller처럼 작동하며 @ResponseBody를 통해 객체를 리턴할 수 있다.

@RestControllerAdvice 인터페이스

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
	// ...	
}

@ControllerAdvice vs @RestControllerAdvice

@ControllerAdvice는 @Componenet 어노테이션을 가지고 있어 컴포넌트 스캔을 통해 스프링 빈으로 등록된다.
@RestControllerAdvice는 @Controlleradvice와 @ResponseBody 어노테이션으로 이루어져있고 HTML 뷰 보다는 Response body로 값을 리턴할 수 있다.

참고자료
@ExceptionHandler를 통한 예외처리


AuthServiceApiV1.java

주석으로 처리하고 에러로 처리해서 던짐

    public ResponseEntity<?> join(ReqJoinDTO dto) {
        // 회원가입 정보 입력했는지 확인
        if(
            dto.getUser().getId() == null ||
            dto.getUser().getId().equals("") ||
            dto.getUser().getPassword() ==null ||
            dto.getUser().getPassword().equals("")

            ){
                //처리하기 귀찮으니까 에러를 만들어서 던진 것
                throw new BadRequestException("아이디나 비밀번호를 입력해주세요");
            // return new ResponseEntity<>( 
            // ResponseDTO.builder()
            // .code(1)
            // .message("아이디나 비밀번호를 입력해주세요")
            // .build(),
            // HttpStatus.BAD_REQUEST
            // );
        }
        // 리파지토리에서 아이디로 유저 찾기
        Optional<UserEntity> userEntityOptional = userRepository.findById(dto.getUser().getId());
        // optional - null  체크 하기위해서 쓴다
        
        // 있으면 (이미 존재하는 아이디입니다.) 메시지 리턴
        if(userEntityOptional.isPresent()){
            throw new BadRequestException("이미 존재하는 아이디입니다.");
            // return new ResponseEntity<>(
            //     ResponseDTO.builder()
            //     .code(1)
            //     .message("이미 존재하는 아이디입니다.")
            //     .build(),
            //     HttpStatus.BAD_REQUEST
            // );
        }

입력안했으니까 에러난다.


RestExceptionHandler.java

폴더구조

@RestControllerAdvice로 처리한다.

package com.example.my.common.exception.handler;

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

import com.example.my.common.dto.ResponseDTO;
import com.example.my.common.exception.BadRequestException;

@RestControllerAdvice
// Rest방식으로 에러 처리
public class RestExceptionHandler {

    // 처리할 에러를 특정함
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<?> handleBadRequestException(Exception exception) {
        return new ResponseEntity<>(ResponseDTO.builder()
            .code(1)
            .message(exception.getMessage())
            .build(), HttpStatus.BAD_REQUEST);
    }
}

실행하면 에러가 터졌을 때 특정 처리한다.


모든에러를 처리한다.
(주석으로 설명)

@RestControllerAdvice
// Rest방식으로 에러 처리
public class RestExceptionHandler {

    // 처리할 에러를 특정함
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<?> handleBadRequestException(Exception exception) {
        exception.printStackTrace();
        return new ResponseEntity<>(ResponseDTO.builder()
            .code(1)
            .message(exception.getMessage())
            .build(), HttpStatus.BAD_REQUEST);
    }

    // 모든 에러를 처리할 때
    // 이렇게 하면 찾아내기 어려움
    // class별로 에러를 처리하는게 나음
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleException(Exception exception){
        exception.printStackTrace();// 콘솔창에 에러를 뿌려주고 화면에 뿌려줌(하고 싶으면 하고)
        return new ResponseEntity<>(ResponseDTO.builder()
            .code(1)
            .message(exception.getMessage())
            .build(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

실행하면콘솔창에서도


Validation

validation 의존성 추가

gradle의 디펜덴시에 붙여준다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

유효성 검사

유효성 검사가 필요한 Request 객체에 Validation 어노테이션을 사용해 유효성 검사를 적용할 수 있다.

참고자료
Validation에 대해


AuthControllerApiV1.java

주석으로 설명~~

    @PostMapping("/join")
    // 체크를 하겠다는 것 - @Valid
    public ResponseEntity<?> join(@Valid @RequestBody ReqJoinDTO dto) {
        // 서비스에서 회원가입하기
        return authServiceApiV1.join(dto);
    }

ReqJoinDTO.java

public class ReqJoinDTO {

    @NotNull(message = "유저 정보를 입력해주세요.")
    private User user;

실행하면 이렇게 message에 유저 정보를 입력해주세요라는 글이 보인다.


AuthServiceApiV1.java

AuthServiceApiV1에서 원래는 이렇게 null을 설정해서 하나하나 다 적어야 했는데

        if(
            dto.getUser().getId() == null ||
            dto.getUser().getId().equals("") ||
            dto.getUser().getPassword() ==null ||
            dto.getUser().getPassword().equals("")
        ){

ReqJoinDTO에 이렇게 간단히 할 수 있음

public class ReqJoinDTO {

    @Valid
    @NotBlank(message = "유저 정보를 입력해주세요.")
    private User user;

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    public static class User {

        @NotBlank(message = "아이디를 입력해주세요")
        @Size(min = 4, message = "아이디는 4자 이상 입력해주세요.")
        private String id;

        @NotBlank(message = "비밀번호를 입력해주세요")
        @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@#$%^&+=])(?=\\S+$).{8,16}$", message = "비밀번호 양식에 맞추어서 입력해주세요.")
        private String password;
    }

}

     public ResponseEntity<?> login(ReqLoginDTO dto, HttpSession session) {
        // 유효성체크
        // if(
        //     dto.getUser().getId() == null ||
        //     dto.getUser().getId().equals("") ||
        //     dto.getUser().getPassword() ==null ||
        //     dto.getUser().getPassword().equals("")
        // ){
        //      throw new BadRequestException("아이디나 비밀번호를 입력해주세요");
        //     // return new ResponseEntity<>( 
        //     // ResponseDTO.builder()
        //     // .code(1)
        //     // .message("아이디나 비밀번호를 입력해주세요")
        //     // .build(),
        //     // HttpStatus.BAD_REQUEST
        //     // );
        // }
        // 리파지토리에서 아이디로 삭제되지 않은 유저 찾기
        Optional<UserEntity> userEntityOptional = userRepository.findByIdAndDeleteDateIsNull(dto.getUser().getId());

실행해서 message에 보면 양식을 똑바로 입력하라고 나온다.


RestExceptionHandler.java

에러가 한개가 아니라 여러개가 나온거 그래서 다찾아서 뿌려주면 좋으니까 만들어준다.

    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> handleBindException(BindException bindException){

        for (FieldError fieldError : bindException.getBindingResult().getFieldErrors()) {
            System.out.println(fieldError.getField());
            System.out.println(fieldError.getDefaultMessage());
        }

        return null;
    }

실행해보면 에러 처리가 되었고

콘솔창에서는 메세지가 뜬다.

에러가 몇개가 들어오든 다 처리 가능하다.

public class RestExceptionHandler {

    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> handleBindException(BindException bindException){

        HashMap<String, String> errorMap = new HashMap<>();

        for (FieldError fieldError : bindException.getBindingResult().getFieldErrors()) {
            errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
        }

        return new ResponseEntity<>(
            ResponseDTO.builder()
            .code(1)
            .message("요청 데이터를 확인해주세요")
            .data(errorMap)
            .build(),
            HttpStatus.BAD_REQUEST
        );
    }

다시 실행해보면 잘나온다.

로그인도 해준다!!


AuthControllerApiV1.java

유효성체크도 주석처리해줌..(위에서 함)

    @PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody ReqLoginDTO dto, HttpSession session) {
        // 서비스에서 로그인하기
        return authServiceApiV1.login(dto, session);
    }

ReqLoginDTO.java

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ReqLoginDTO {

    @Valid
    @NotNull(message = "유저 정보를 입력해주세요")
    private User user;

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    public static class User {

        @NotBlank(message = "아이디를 입력해주세요")
        private String id;

        @NotBlank(message = "비밀번호를 입력해주세요")
        private String password;
    }

}

실행하면 로그인도 잘된다.

이게 AOP라고 한다.


profile
미래가 기대되는 풀스택개발자 공부 이야기~~

0개의 댓글