Today
-
Yesterday
-
Total
-
  • 스프링 부트 핵심 가이드 6주차: 10장
    책 공부/스프링 부트 핵심 가이드 2023. 12. 6. 00:02

    📖 책 정보

    모바일 가이드

    유효성 검사와 예외 처리

    1. 일반적인 애플리케이션 유효성 검사의 문제점
    2. Hibernate Validator
    3. 스프링 부트에서 검증에 사용되는 대표적인 어노테이션
    4. 스프링 부트에서의 유효성 검사 + BindingResult로 valid 에러 다루기
    5. 예외 처리



    10장 유효성 검사와 예외 처리

    1. 일반적인 애플리케이션 유효성 검사의 문제점
    2. Hibernate Validator
    3. 스프링 부트에서의 유효성 검사
    4. 예외 처리



    1. 일반적인 애플리케이션 유효성 검사의 문제점

    일반적으로 사용되는 데이터 검증 로직에는 몇 가지 문제점이 있다.

    1. 관리의 어려움

    계층별로 진행하는 유효성 검사는 검증 로직이 각 클래스별로 분산돼 있어 관리하기가 어렵다.

    2. 중복 코드

    검증 로직에 의외로 중복이 많아, 여러 곳에 유사한 기능의 코드가 존재할 수 있다.

    3. 가독성 문제

    검증해야 할 값이 많다면 검증하는 코드가 길어지므로 코드가 복잡해지고 가독성이 떨어진다.


    이러한 문제를 해결하기 위해

    자바 진영에서는 2009년부터

    Bean Validation 이라는

    데이터 유효성 검사 프레임워크를 제공하고 있다.


    Bean Validation은

    어노테이션을 통해

    다양한 데이터를 검증하는 기능을 제공한다.


    Bean Validation을 사용한다는 것은

    유효성 검사를 위한 로직을

    DTO 같은 도메인 모델과 묶어서

    각 계층에서 사용하면서

    검증 자체를

    도메인 모델에 얹는 방식으로

    수행한다는 의미이다.


    또한,

    Bean Validation은

    어노테이션을 사용한 검증 방식이기 때문에

    코드의 간결함도 유지할 수 있다.



        🐢🎈


    2. Hibernate Validator

    Hibernate Validator

    Bean Validation 명세의 구현체 이다.


    스프링 부트에서는

    Hibernate Validator를

    유효성 검사 표준으로 채택해서 사용하고 있다.


    Hibernate Validator는

    JSR-380 ( Bean Validation 2.0 ~ ) 명세의 구현체로서

    도메인 모델에서 어노테이션을 통한 필드값 검증을 가능하게 도와준다.



        🐢🎈


    3. 스프링 부트에서 검증에 사용되는 대표적인 어노테이션

    - 문자열 검증

    @Null

    null 만 허용

    @NotNull

    null 불가를 의미

    @NotEmpty

    null , "" 불가를 의미

    @NotBlank

    null , "" , " " 불가를 의미

    - 문자열 길이 검증

    @Size(min = 숫자, max = 숫자)

    int Type 불가 문자열 길이 측정

    - 정규식 검증

    @Pattern(regexp = "정규식")

    정규 표현식(pattern)에 맞아야 함을 나타냄.

    - 이메일 검증

    @Email

    문자열이 유효한 이메일 주소 형식이어야 함을 나타냄.

    - 최솟값/최댓값 검증

    BigDecimal , BigInteger, int, long 타입에 사용

    @Min(value = 숫자)

    숫자 이상이어야 함을 나타냄.

    @Max(value = 숫자)

    숫자 이하여야 함을 나타냄.

    @DecimalMin(value = "숫자")

    BigDecimal 타입을 지원하기 위한 어노테이션으로, "숫자" 보다 크거나 같은 값을 허용

    @DecimalMax(value = "숫자")

    BigDecimal 타입을 지원하기 위한 어노테이션으로, "숫자" 보다 작거나 같은 값을 허용

    - 값의 범위 검증

    BigDecimal , BigInteger, int, long 타입에 사용

    @Positive

    양수 허용

    @PositiveOrZero

    0을 포함한 양수 허용

    @Negative

    음수 허용

    @NegativeOrZero

    0을 포함한 음수 허용

    - 자릿수 범위 검증

    BigDecimal , BigInteger, int, long 타입에 사용

    @Digits(integer = 숫자1, fraction = 숫자2)

    숫자1 의 정수 자릿수와 숫자2 의 소수 자릿수를 허용

    - 시간에 대한 검증

    Date , LocalDate, LocalDateTime 타입에 사용

    @Past

    과거 날짜

    @PastOrPresent

    오늘이거나 과거 날짜

    @Future

    미래 날짜

    @FutureOrPresent

    오늘이거나 미래 날짜

    - Boolean 검증

    @AssertTrue / False

    별도 Logic 적용 재사용이 불가능하다.

    해당 로직이 필요한 다른 DTO 클래스가 있다면, 거기에도 똑같이 작성해주어야 하는 단점이 있음.

    간단 사용 예시

    @AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.") // 메서드 이름이 is로 시작해야됨
    public boolean isReqYearMonthValidation() {
        try {
            LocalDate localDate = LocalDate.parse(
                                  this.reqYearMonth + "01",
                                  DateTimeFormatter.ofPattern("yyyyMMdd")
                             );
    
    } catch (Exception e) {
        return false;
    }
    
    return true;
    

    }



        🐢🎈


    4. 스프링 부트에서의 유효성 검사

    - validation 의존성 추가

    스프링 부트 2.3 버전 이전까지는 spring-boot-starter-web에 포함되어있었지만,

    스프링 부트 2.3 버전부터 별도의 라이브러리로 제공된다.

    build.gradle

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-validation'
    }

    - 스프링 부트의 유효성 검사

    유효성 검사는

    각 계층으로 데이터가 넘어오는 시점에

    해당 데이터에 대한 검사를 실시한다.


    스프링 부트 프로젝트에서는

    계층 간 데이터 전송에

    대체로 DTO 객체를 활용하고 있기 때문에

    유효성 검사를

    DTO 객체를 대상으로 수행하는 것이 일반적이다.


    실습을 위한 DTO와 컨트롤러 작성!

    ValidRequestDto 작성

    @Data
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ValidRequestDto {
        
        @NotBlank
        private String name;
        
        @Email
        private String email;
        
        @Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
        private String phoneNumber;
        
        @Min(value = 20) @Max(value = 40)
        private int age;
        
        @Size(min = 0, max = 40)
        private String description;
        
        @Positive // 양수 허용
        private int count;
        
        @AssertTrue
        private boolean booleanCheck;
        
    }

    ValidationController 작성

    @Slf4j
    @RestController
    @RequestMapping("/validation")
    public class ValidationController {
    
        @PostMapping("/valid")
        public ResponseEntity<ValidRequestDto> checkValidationByValid(
                @Valid @RequestBody ValidRequestDto dto
        ) {
    
            log.info("🚩 ValidRequestDto ==> {}", dto);
    
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(dto);
        }
    
    }

    application 실행 후 swagger를 통해 검증 에러 내보기

    age를 19로 하면 , 최솟값을 20이라고 조건을 걸어두었기 때문에 에러가 나야 한다.

    결과 화면

    spring boot에 가보면 log 창에서도 경고메세지로 알려준다.


    age를 20으로 하면 통과!


    - @Validated 활용

    @Valid 어노테이션은

    자바에서 지원하는 어노테이션이며,


    @Validated 어노테이션은

    스프링에서 지원하는 어노테이션이다.


    @Validated 어노테이션은 @Valid 어노테이션의 기능을 포함하고 있기 때문에

    @Valid를 @Validated로 변경할 수 있다.


    @Validated는 유효성 검사를 그룹으로 묶어 대상을 특정할 수 있는 기능이 있다.


    그룹으로 묶어버리면,

    DTO 객체에서 몇개의 필드를 그룹으로 묶은 다음,

    컨트롤러에서 DTO를 인자로 받는 메서드에 @Validated(그룹이름) 을 사용하여

    DTO 전체를 검증하지 않고, DTO에서 특정 그룹으로 지정된 필드에 대해서만 검증을 수행할 수 있다.


    1. 검증 그룹 인터페이스 생성

    검증 그룹 이름은

    ValidationGroup1 과 ValidationGroup2 라고 작성!

    내용은 작성하지 않는다.

    public interface ValidationGroup1 {}
    
    public interface ValidationGroup2 {}

    2. DTO 객체에서 몇몇 변수에 검증 그룹 지정

    age는 ValidationGroup1로, count는 ValidationGroup2로 지정해주었다.

    public class ValidRequestDto {
    
    	...
        
        @Min(value = 20, groups = ValidationGroup1.class) 
        @Max(value = 40, groups = ValidationGroup1.class)
        private int age;
    
    	...
        
        @Positive(groups = ValidationGroup2.class) // 양수 허용
        private int count;
    
    	...
    }

    3. Controller에 메서드 추가

    @Slf4j
    @RestController
    @RequestMapping("/validation")
    public class ValidationController {
    
    	// dto 전체 값 검증
        @PostMapping("/validated")
        public ResponseEntity<ValidRequestDto> checkValidationByValidated(
                @Validated
                @RequestBody
                ValidRequestDto dto
        ) {
    
            log.info("🚩 ValidatedRequestDto ==> {}", dto);
    
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(dto);
        }
    
    	// 그룹 1 로 지정된 값만 검증
        @PostMapping("/validated/1")
        public ResponseEntity<ValidRequestDto> checkValidationByValidated1(
                @Validated(ValidationGroup1.class)
                @RequestBody
                ValidRequestDto dto
        ) {
    
            log.info("🚩 ValidatedRequestDto group 1 ==> {}", dto);
    
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(dto);
        }
    
    	// 그룹 2 로 지정된 값만 검증
        @PostMapping("/validated/2")
        public ResponseEntity<ValidRequestDto> checkValidationByValidated2(
                @Validated(ValidationGroup2.class)
                @RequestBody
                ValidRequestDto dto
        ) {
    
            log.info("🚩 ValidatedRequestDto group 2 ==> {}", dto);
    
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(dto);
        }
    
    	// 그룹1,2 로 지정된 값만 검증
        @PostMapping("/validated/all")
        public ResponseEntity<ValidRequestDto> checkValidationByValidatedAll(
                @Validated({ValidationGroup1.class, ValidationGroup2.class})
                @RequestBody
                ValidRequestDto dto
        ) {
    
            log.info("🚩 ValidatedRequestDto group 1, 2 ==> {}", dto);
    
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(dto);
        }
    
    }

    - 커스텀 Validation 추가

    생각보다 커스텀 검증이 필요한 경우가 많이 있다.

    이럴 땐

    ConstraintValidator

    커스텀 어노테이션을 조합해서

    별도의 유효성 검사 어노테이션을 생성하여 사용할 수 있다.


    커스텀하는 가장 흔한 사례로

    동일한 정규식을 계속 쓰는 @Pattern 어노테이션의 경우가 있다.


    전화번호 형식이 일치하는지 확인하는

    간단한 유효성 검사 어노테이션 생성


    1. ConstraintValidator 인터페이스 구현 클래스 작성

    ConstraintValidator 인터페이스를 상속 받을 때 ConstraintValidator<어노테이션, 체크할_자료형> 형식으로 작성해주어야 한다.

    ConstraintValidator 에서 isValid()를 오버라이드해서 작성한다.

    public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
        
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null)
                return false;
            
            return value.matches("01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$");
        }
    }

    2. Telephone 어노테이션 작성

    @Target(ElementType.FIELD) // 이 어노테이션이 어떤 위치에서 선언할 수 있는지 정의 
    @Retention(RetentionPolicy.RUNTIME) // 어노테이션이 실제로 적용되고 유지되는 범위
    @Constraint(validatedBy = TelephoneValidator.class) // 사용할 ConstrantValidator 구현체 명시
    public @interface Telephone {
        String message() default "전화번호 형식이 일치하지 않습니다.";
        Class[] groups() default {};
        Class[] payload() default {};
    }

    @Target 어노테이션에 사용 가능한 정보

    @Retention 어노테이션에 사용 가능한 정보

    • RetentionPolicy.RUNTIME 컴파일 이후에도 JVM에 의해 계속 참조. 리플렉션이나 로깅에 많이 사용 됨

    • RetentionPolicy.CLASS 컴파일러가 클래스를 참조할 때까지 유지.

    • RetentionPolicy.SOURCE 컴파일 전까지만 유지. 컴파일 이후에는 사라짐!

    어노테이션 내부에 작성해야 하는 정보

    • String message() : 유효성 검사가 실패할 경우 반환되는 메세지
    • Class[] groups() : 유효성 검사를 사용하는 그룹으로 설정
    • Class[] payload() : 사용자가 추가 정보를 위해 전달하는 값

    3. 생성한 커스텀 어노테이션 적용!

    public class ValidRequestDto {
    
    	...
        
        @Telephone
        private String phoneNumber;
    
    	...
    }

    실행해보면 먼저 사용했던 아래의 코드와 같은 기능을 하는 걸 확인할 수 있다.

    @Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
    private String phoneNumber;



        🐢🎈

    + BindingResult 로 Valid 예외 다루기

    출처

    예외 처리 방법 외에, BindingResult 로 에러 출력 해보기

    @Valid 어노테이션을 사용한 메서드에서 BindingResult 를 사용하면 결과값이 BindingResult로 들어온다.

    메서드에 BindingResult 추가

    @PostMapping("/user")
    public ResponseEntity user(@Valid @RequestBody Users user, BindingResult bindingResult) {
    
        if (bindingResult.hasErrors()) {
            StringBuilder sb = new StringBuilder();
            bindingResult.getAllErrors().forEach(objectError -> {
                FieldError field = (FieldError) objectError;
                String message = objectError.getDefaultMessage();
    
                sb.append("\nfield : ").append(field.getField())
                  .append("\nmessage : ").append(message);
            });
    
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(sb.toString());
        }
    
        System.out.println(user);
    
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(user);
    }

    에러 발생확인 bindingResult.hasErrors()

    발생한 모든 에러에 대한 정보 조회 List<ObjectError> bindingResult.getAllErrors()

    FieldError 정보 조회를 위한 형 변환 FieldError field = (FieldError) objectError;

    field 이름 정보 조회 field.getField()

    오류 메세지 조회 objectError.getDefaultMessage()


    실행 결과

    spring boot 에러 로그는 출력되지 않음.



        🐢🎈


    5. 예외 처리

    - 예외와 에러

    프로그래밍에서 예외 exception이란

    입력 값의 처리가 불가능하거나

    참조된 값이 잘못된 경우 등

    애플리케이션이 정상적으로 동작하지 못하는 상황을 의미한다.

    예외는 개발자가 직접 처리할 수 있기 때문에

    미리 코드 설계를 통해 처리할 수 있다.


    에러 error는

    주로 자바 가상머신에서 발생시키는 것으로

    예외와 달리

    애플리케이션 코드에서 처리할 수 있는 것이 거의 없다.

    대표적인 예로, 메모리 부족 OutOfMemory 스택 오버플로 StackOverFlow 등이 있다.


    - 예외 클래스

    모든 예외 클래스는 Throwable 클래스를 상속받는다.

    그리고

    가장 익숙하게 볼 수 있는 Exception 클래스는 다양한 자식 클래스를 가지고 있다.


    Exception 클래스는

    크게

    Checked Exception

    Unchecked Exception으로

    구분할 수 있다.


    CheckedException UncheckedException
    처리 여부 반드시 예외 처리 필요 명시적 처리를 강제하지 않음
    확인 시점 컴파일 단계 실행 중 단계
    대표적인 예외 클래스




    IOException
    SQLException



    RuntimeException
    NullPointerException
    IllegalArgumentException
    IndexOutOfBoundException
    SystemException

    간단히 표현한다면,

    RuntimeException을 상속받는 Exception 클래스는 UncheckedException 이고,

    그렇지 않은 Exception 클래스는 CheckedException 이다.


    - 예외 처리 방법

    예외가 발생했을 때 이를 처리하는 방법은 크게 3가지이다.

    1. 예외 복구

    상황을 파악해서 문제를 해결하는 방법. 대표적인 방법이 try/catch 구문이다.

    2. 예외 처리 회피

    예외가 발생한 시점에서 바로 처리하는 것이 아니라,

    예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식이다.

    throw 키워드를 사용하여 호출부에 어떤 예외가 발생했는지 내용을 전달할 수 있다.

    3. 예외 전환

    이 방법은 앞의 두 방식을 적절히 섞은 방식이다.

    예외가 발생했을 때 어떤 예외가 발생했느냐에 따라 호출부로 예외 내용을 전달하면서 좀 더 적합한 예외 타입으로 전달할 필요가 있다.

    또는

    애플리케이션에서 예외 처리를 좀 더 단순하게 하기 위해 래핑해야 하는 경우도 있다.

    이런 경우에는 try/catch 방식을 사용하면서 catch 블록에서 throw 키워드를 사용해 다른 예외 타입으로 전달하면 된다.


    - 스프링 부트의 예외 처리 방식

    예외가 발생했을 때

    클라이언트에 오류 메시지를 전달하려면

    각 레이어에서 발생한 예외를

    엔드포인트 레벨인

    컨트롤러로 전달해야 한다.


    이렇게 전달받은 예외를

    스프링 부트에서 처리하는 방식으로

    크게 두 가지가 있다.


    1. Advice를 이용, 모든 컨트롤러의 예외 처리 : @RestControllerAdvice@ExceptionHandler 사용

    2. Advice를 이용하지 않고, 특정 컨트롤러의 예외 처리 : @ExceptionHandler 사용


    1) Advice를 이용하는 방법

    @RestControllerAdvice를 활용한 핸들러 클래스를 생성하고 생성한 클래스에 @ExceptionHandler를 이용해서 예외를 처리한다.

    1-1. 핸들러 클래스 작성

    @Slf4j
    @RestControllerAdvice
    public class CustomExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        public ResponseEntity<Map<String, String>> handleException(
                RuntimeException e,
                HttpServletRequest request
        ) {
            HttpHeaders responseHeaders = new HttpHeaders();
            HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
    
            log.error("🚩 Advice 내 handleException 호출 {} {}", request.getRequestURI(), e.getMessage());
            
            Map<String, String> map = new HashMap<>();
            map.put("error type", httpStatus.getReasonPhrase());
            map.put("code", "400");
            map.put("message", e.getMessage());
            
            return new ResponseEntity<>(map, responseHeaders, httpStatus);
        }
    }

    1-2. 예외를 발생시킬 컨트롤러 작성

    @RestController
    @RequestMapping("/exception")
    public class ExceptionController {
    
        @GetMapping
        public void getRuntimeException() {
            throw new RuntimeException("runtime exception 발생ㅅ기키닏다");
        }
    }

    1-3. 실행 확인!


    2) 특정 컨트롤러의 예외 처리

    2-1. ExceptionController에서 발생하는 예외만 처리

    @Slf4j
    @RestController
    @RequestMapping("/exception")
    public class ExceptionController {
    
        @GetMapping
        public void getRuntimeException() {
            throw new RuntimeException("runtime exception 발생ㅅ기키닏다");
        }
    
        @ExceptionHandler(RuntimeException.class)
        public ResponseEntity<Map<String, String>> handleException(
                RuntimeException e,
                HttpServletRequest request
        ) {
            HttpHeaders responseHeaders = new HttpHeaders();
            HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
    
            log.error("🚩 Controlelr 내 handleException 호출 {} {}", request.getRequestURI(), e.getMessage());
    
            Map<String, String> map = new HashMap<>();
            map.put("error type", httpStatus.getReasonPhrase());
            map.put("code", "400");
            map.put("message", e.getMessage());
    
            return new ResponseEntity<>(map, responseHeaders, httpStatus);
        }
    
    }

    이렇게 특정 컨트롤러에서

    @ExceptionHandler 를 사용하여 예외 처리를 하면

    @RestControllerAdvice보다 컨트롤러의 @ExceptionHandler가 더 우선시 되어서 동작한다.

    2-2. 실행 확인!


    - 커스텀 예외

    네이밍에 개발자의 의도를 담아 이름만으로 예외 상황을 짐작하기 위해 커스텀 예외를 사용하는 일이 빈번하다.

    커스텀 예외를 사용하면 애플리케이션에서 발생하는 예외를 개발자가 직접 관리하기가 수월해진다.

    그리고 예외 상황에 대한 처리도 용이해진다.

    의도하지 않은 예외가 발생하면 커스텀 예외가 발생하지 않기 때문에 개발 과정에서 혼동할 여지가 줄어든다.


    - 커스텀 예외 클래스 생성하기

    커스텀 예외는 만드는 목적에 따라 생성하는 방법이 다르다.


    이 책에서는

    @ControllerAdvice 와 @ExceptionHandler 의

    무분별한 예외 처리를 방지하기 위한

    커스텀 예외를 생성하는 과정을 실습한다.


    커스텀 예외는

    예외가 발생하는 상황에 해당하는

    상위 예외 클래스를 상속받는다.


    그래서 커스텀 예외는

    상위 예외 클래스보다

    좀 더 구체적인 이름을 사용하기도 한다.


    그러나 여기서는 커스텀 예외의 네이밍 보다는

    클래스의 구조적인 설계를 통한 예외 클래스 생성 방법을 알아본다.


    먼저 Exception 클래스의 커스텀 예외를 생성한다.

    커스텀 예외 클래스를 생성하는 데 필요한 내용은 다음과 같이 작성해볼 수 있다.

    • 에러 타입 : HttpStatus의 reasonPhrase
    • 에러 코드 : HttpStatus의 value
    • 메시지 : 상황별 상세 메세지

    추가적으로

    애플리케이션에서 가지고 있는

    도메인 레벨을 메세지에 표현하기 위해

    열거형 ExceptionClass 타입을 생성한다.

    ExceptionClass

    @Getter
    @RequiredArgsConstructor
    public enum ExceptionClass {
        PRODUCT("Product")
        ;
    
        private final String exceptionClass;
    
    
        @Override
        public String toString() {
            return "💩 " + getExceptionClass() + " Exception. ";
        }
    }

    열거형을 생성했으니, 커스텀 예외 클래스를 작성한다.

    public class CustomException extends Exception{
    
        private ExceptionClass exceptionClass;
        private HttpStatus httpStatus;
        
        public CustomException(ExceptionClass exceptionClass, HttpStatus httpStatus, String message) {
            super(exceptionClass.toString() + message);
            this.exceptionClass = exceptionClass;
            this.httpStatus = httpStatus;
        }
        
        public ExceptionClass getExceptionClass() {
            return exceptionClass;
        }
        
        public int getHttpStatusCode() {
            return httpStatus.value();
        }
        
        public String getHttpStatusType() {
            return httpStatus.getReasonPhrase();
        }
        
        public HttpStatus getHttpStatus() {
            return httpStatus;
        }
    }

    작성한 커스텀 예외를 활용하기 위해, ExceptionHandler 클래스에 예외 처리 코드를 작성해준다.

    @Slf4j
    @RestControllerAdvice
    public class CustomExceptionHandler {
    
    	...
    
        @ExceptionHandler(CustomException.class)
        public ResponseEntity<Map<String, String>> handleException(
                CustomException e,
                HttpServletRequest request
        ) {
            HttpHeaders responseHeaders = new HttpHeaders();
    
            log.error("🚩 Advice 내 handleException 호출 {} {}", request.getRequestURI(), e.getMessage());
    
            Map<String, String> map = new HashMap<>();
            map.put("error type", e.getHttpStatusType());
            map.put("code", Integer.toString(e.getHttpStatusCode()));
            map.put("message", e.getMessage());
    
            return new ResponseEntity<>(map, responseHeaders, e.getHttpStatus());
        }
    
    }

    실행 확인을 위해 controller를 작성한다.

    @Slf4j
    @RestController
    @RequestMapping("/exception")
    public class ExceptionController {
    
     	...
    
        @GetMapping("/custom")
        public void getCustomException() throws CustomException {
            throw new CustomException(
                    ExceptionClass.PRODUCT,
                    HttpStatus.BAD_REQUEST,
                    "getCutomException 메서드에서 호출"
            );
        }
    
    }

    실행 확인!



        🐢🎈

Designed by Tistory / Custom by 얼거스