スプリングの各種機能(Vailation、Exception処理)

18602 ワード

スプリングの機能を利用する。


😡😎

Spring Boot Validation


検証は非常に重要な部分です。JavaがNull値にアクセスしようとするとNullポインタ異常が発生します。これを回避するために、事前検証プロセスはValidationと呼ばれます。



前述したように,簡単に検証するだけでよいが,オブジェクトが多い場合は大量のコードを記述する必要がある.正常な論理が必要ですが、ビジネスに関係のないコードがたくさん必要です.整理すると.
  • の値を多く検証する必要がある場合は、コード長が長くなります.
  • これは
  • の実施に依存するが、サービスLogicから分離する必要がある.
  • に分散すると、検証がどこで行われるかが分かりにくく、再利用の制限がある.
  • これは
  • の実施によって異なりますが、検証Logicが変化した場合、テストコードなどの参照クラスでLogicの部分を変更する必要がある場合があります.
  • そのため、彼らはベースとしてサポートを提供します。



    やってみよう。

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-validation'
    }
    😎Dependancyの追加
    public class User {
        private String name;
        private int age;
    
        @Email
        private String email;
    
        @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$")
        private String phoneNumber;
    
    ...
    ...
    }
    😎User Classのメンバーに電話します.
        @PostMapping("/user")
        public User user(@Valid @RequestBody User user){
            System.out.println("user : "+user);
            return user;
        }
    😎@ValidをAPIのパラメータに貼り付けて終了

    😎上記のように、素材ごとに入れると成功しますが

    😎上記のEメール形式に違反したり、正規表現のPattern(電話番号)に不満がある場合は、を選択します.
    2022-01-19 18:16:55.333  WARN 7248 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.validation.dto.User com.example.validation.controller.ApiController.user(com.example.validation.dto.User) with 2 errors: [Field error in object 'user' on field 'phoneNumber': rejected value [0101234-1234]; codes [Pattern.user.phoneNumber,Pattern.phoneNumber,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber],[Ljavax.validation.constraints.Pattern$Flag;@715f31ba,^\d{2,3}-\d{3,4}-\d{4}$]; default message ["^\d{2,3}-\d{3,4}-\d{4}$"와 일치해야 합니다]] [Field error in object 'user' on field 'email': rejected value [asdf]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@41a14204,.*]; default message [올바른 형식의 이메일 주소여야 합니다]] ]
    
    😡こんなに悲しくなったエラーメッセージの設定方法
    public User user(@Valid @RequestBody User user
                , BindingResult bindingResult) {  //BindingResult는
            if(bindingResult.hasErrors()){ //에러가 있으면
                StringBuilder sb = new StringBuilder();
                bindingResult.getAllErrors().forEach(objectError -> {
                    FieldError fidld = (FieldError) objectError; //어떤필드 애러인가?
                    String message = objectError.getDefaultMessage(); //그 애러 매세지 뭔가?
    
                    System.out.println("field : " + fidld.getField());
                    System.out.println(message);// 메세지출력
                });
            }
            System.out.println("user : " + user);
            return user;
        }
    😡この時の情報は間違っていました...変な情報があります.
        @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$",message = "핸드폰 번호의 양식과 맞지 않습니다.. 01x-xxxx-xxxx")
        private String phoneNumber;
    😎 ああ...

    Custom Validation

  • は、指定された方法(例えば、AsserTrue/False)によってクライアントロジック
  • を適用することができる.
  • ConstraintValidatorは、
  • を再利用できる顧客ロジックをサポートします.
        @AssertTrue(message = "yyyyMM 의 형식에 맞지 않습니다.")
        public boolean isReqYearMonthValidation() { //boolean메서드는 is를 붙여주자
            System.out.println("그럼 여기는?");
            //DateTimeFormatter는 yyyyMM"dd" 까지 붙이기 때문에 01일을 임의로 붙여줌
            try{
                //String "000000" 을 날짜로 파싱
                LocalDate localDate = LocalDate.parse(getReqYearMonth() + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
            }catch (Exception e){
                return false;
            }
            return true;
        }
    これにより、Userクラスに配置できますが、再利用できません.再利用する場合は、1つずつコピーして入れる必要があります.コードが長くなり、重複性も増加します.だからこれを無装飾にしましょう
    @Constraint(validatedBy = {YearMonthValidator.class})// 이 클래스를 사용해서 검사할꺼다....
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    public @interface YearMonth {
    
        String message() default "yyyyMM 형식에 맞지 않습니다.";
    
        Class<?>[] groups() default { };
    
       😎 Class<? extends Payload>[] payload() default { };
    
        String pattern() default "yyyyMMdd";
    }
    😎委員会のために参考条件を制定する必要がある.
    public class YearMonthValidator implements ConstraintValidator<YearMonth, String> {
    
        private String pattern;
    
        @Override
        public void initialize(YearMonth constraintAnnotation) {
            this.pattern = constraintAnnotation.pattern();
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
    
            // yyyyMM
            try{
                LocalDate localDate = LocalDate.parse(value+"01" , DateTimeFormatter.ofPattern(this.pattern));
            }catch (Exception e){
                return false;
            }
    
    
            return true;
        }
    }
    😎 では上記の条件をチェックしたところに@annotationを入れて終了です~~
    //    @Size(min = 6, max = 6)
        @YearMonth
        private String reqYearMonth; //yyyyMM
    😎 では今yyymm形式をチェックするには@YaearMothを入れるだけです.

    Exception処理


    😎 Webアプリケーションの観点から、エラーが発生した場合、エラーを低減できる方法は多くありません.
  • エラーページ
  • 4XX Error or 5XX Error
  • クライアントは、200以外のトランザクションを処理すると、200を削減し、追加のエラーメッセージ
  • を送信する.

    やってみよう。

        @NotEmpty
        @Size(min = 1,max = 10)
        private String name;
    
        @Min(1)
        @NotNull
        private Integer age;
    😎 userdtoを作成し、Vald条件を掛けます.
        @GetMapping("")
        public User get(@RequestParam(required = false) String name, @RequestParam(required = false) Integer age){
        User user =new User();
        user.setName(name);
        user.setAge(age);
    
        int a = 10+age;
    
        return user;
        }
    😎 コントローラを作成します.

    何もしないでGetを呼び出すとNullPointexactionが表示されます
    java.lang.NullPointerException: null
    😎 基本的に、スプリングには、自身の例外に対して500個のエラーが簡単に送信されることがわかります.

    😎Postコールもそうです.条件に合わなければ400エラーしかあげられません.

    どのように処理しますか?


    ControllerAdviceとExceptionHandlerの使用


    グローバルコントローラAdviceを作成します.
    //@ControllerAdvice // ViewResolver 쓰면 이거 쓰면됨
    @RestControllerAdvice //REST쓰면
    public class GlobalControllerAdvice {
    
        @ExceptionHandler(value = Exception.class)
        public ResponseEntity exception(Exception e){
            System.out.println("==============================");
            System.out.println(e.getLocalizedMessage()); //에러 메세지 확인해보기
            System.out.println("==============================");
    
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); // 서버에서 일어난 에러 500에러
    
        }
    }
    もう一度お願いします.
    ==============================
    Validation failed for argument [0] in public com.ex......
    ==============================
    😎このままコンソールウィンドウに現れたら、私が欲しい値をあげてもいいですか?きちんと
    😎しかし、このようにするとどのような間違いなのか分からないので、すべての間違いがこうなるので、もう少し設定しましょう.
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
    
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    
        }
    ExceptionHandlerをもう1つ作成して例外を放出します.きれいじゃないけど、見える.

    🔔😁🎅🤔

    🤔もう少し深化して、ExceptionとValidationを書き出します。

    //@ControllerAdvice // ViewResolver 쓰면 이거 쓰면됨
    @RestControllerAdvice(basePackageClasses = ApiController.class) //REST쓰면
    public class GlobalControllerAdvice {
    
        @ExceptionHandler(value = Exception.class)//모든 Exception
        public ResponseEntity exception(Exception e){
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); // 서버에서 일어난 에러 500에러
        }
    
        @ExceptionHandler(value = MethodArgumentNotValidException.class)//
        public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    }
    🎅 (basePackageClasse=ApiController.class)この項目を追加すると、クラスで発生したことのみが処理されます.
    @Validated
    public class ApiController {
    
        @GetMapping("")
        public User get(
                @Size(min=2)
                @RequestParam String name,
                @NotNull
                @Min(1)
                @RequestParam Integer age){
        User user =new User();
        user.setName(name);
        user.setAge(age);
    
        return user;
        }
    🎅 GetMappingはdto経由で直接掛けなくても大丈夫ですクラスの上に@Validatedを置くのを忘れないでください
    🤔 ではGetは一般的にConstraintViolationExceptionエラーとMissingです...主に間違いがあるので、基本的に彼を捕まえなければならない.
    @RestControllerAdvice(basePackageClasses = ApiController.class) //REST쓰면
    public class GlobalControllerAdvice {
    
        @ExceptionHandler(value = Exception.class) //모든 Exception
        public ResponseEntity exception(Exception e){
            System.out.println(e.getClass().getName() + "  예외가 나온 클래스 이름입니다.");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); // 서버에서 일어난 에러 500에러
        }
    
        @ExceptionHandler(value = MethodArgumentNotValidException.class) // 잘못된 값을 Post받았을때 나오는 애러
        public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
        @ExceptionHandler(value = ConstraintViolationException.class) // 조건에 벗어나면 나오는 애러 (GET)
        public ResponseEntity constraintViolationException(ConstraintViolationException e){
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    
        @ExceptionHandler(value = MissingServletRequestParameterException.class) //아무값도 입력안하면 나오는 애러 (GET)
        public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    }
    🎅 これで400個の楽子が出てくるでしょう?ちなみに、500の哀楽を絶対に出さないでください.つまり、すべてのテロリストは事前に予想して捕まえるべきだ.
    @ExceptionHandler(value = MethodArgumentNotValidException.class) // 잘못된 값을 Post받았을때 나오는 애러
        public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
            BindingResult bindingResult = e.getBindingResult(); //bindingResult는 애러 정보드를 담고있다.
            bindingResult.getAllErrors().forEach(error -> {     //안을 한번 보자.
                FieldError fieldError = (FieldError) error;     //에러를 형변환 시켜주고
    
                String fieldName = fieldError.getField();       // 이름
                String message = fieldError.getDefaultMessage();  // 에러 메세지
                String value =fieldError.getRejectedValue().toString(); //어떤 값이 주입됬는지 보자
    
                System.out.println("fieldName : " +fieldName);
                System.out.println("message : " +message);
                System.out.println("value : " +value);
                System.out.println();
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    🎅 PostValidから情報の表示を開始します.上記のようにこれらの値を記入して送信します.
    fieldName : name
    message : 비어 있을 수 없습니다
    value : 
    
    fieldName : name
    message : 크기가 1에서 10 사이여야 합니다
    value : 
    
    fieldName : age
    message : 1 이상이어야 합니다
    value : 0
    🎅 コンソールウィンドウにエラーメッセージが表示されます.このようにしてすべての人を捕まえる.
        @ExceptionHandler(value = MethodArgumentNotValidException.class) // 잘못된 값을 Post받았을때 나오는 애러
        public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
            BindingResult bindingResult = e.getBindingResult(); //bindingResult는 애러 정보드를 담고있다.
            bindingResult.getAllErrors().forEach(error -> {     //안을 한번 보자.
                FieldError fieldError = (FieldError) error;     //에러를 형변환 시켜주고
    
                String fieldName = fieldError.getField();       // 이름
                String message = fieldError.getDefaultMessage();  // 에러 메세지
                String value =fieldError.getRejectedValue().toString(); //어떤 값이 주입됬는지 보자
    
                System.out.println("fieldName : " +fieldName);
                System.out.println("message : " +message);
                System.out.println("value : " +value);
                System.out.println();
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
        @ExceptionHandler(value = ConstraintViolationException.class) // 조건에 벗어나면 나오는 애러 (GET)
        public ResponseEntity constraintViolationException(ConstraintViolationException e){
            e.getConstraintViolations().forEach(error ->{
    
                Stream<Path.Node> stream = StreamSupport.stream(error.getPropertyPath().spliterator(), false);
                List<Path.Node> list = stream.collect(Collectors.toList());
                String field = list.get(list.size()-1).getName();
    
                String message = error.getMessage();
                String invalidValue = error.getInvalidValue().toString();
                System.out.println("field : " +field);
                System.out.println("message : " +message);
                System.out.println("invalidValue : " +invalidValue);
                System.out.println();
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    
        @ExceptionHandler(value = MissingServletRequestParameterException.class) //아무값도 입력안하면 나오는 애러 (GET)
        public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){
    
            String fieldName = e.getParameterName();
            String fieldType = e.getParameterType();
            String invalidValue = e.getMessage();
    
            System.out.println("fieldName : " + fieldName);
            System.out.println("fieldType : " + fieldType);
            System.out.println("invalidValue : " + invalidValue);
    
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        }
    

    🎅 ここで重要なのは、間違った内容を見て、みんなに見せてあげることです!!!



    デバッガを使用して表示
    単純なシステム.out.クライアントがprintlnエラーの内容を見て理解できるようにすることが重要です!!!

    もっときれいにしましょう。

    public class ErrorResponse {
        //클라이언트가 알아보기 편하게 해야함
        private String statusCode;
        private String requestUrl;
        private String code;
        private String message;
        private String resultCode;
        private List<Error> errorList;
        ...
        ...
        }
    🎅 エラーを含むErrorResponseクラスを作成します.
    public class Error {
        private String field;
        private String message;
        private String invalidValue;
    🎅 エラーの詳細を含むErrorクラスを作成します.
    
            List<Error> errorList = new ArrayList<>();
            BindingResult bindingResult = e.getBindingResult(); //bindingResult는 애러 정보드를 담고있다.
            bindingResult.getAllErrors().forEach(error -> {     //안을 한번 보자.
            ...
            ...
            에러 정보꺼내오는 코드들...
            ...
                Error errorMessage = new Error();
                errorMessage.setField(fieldName);
                errorMessage.setMessage(message);
                errorMessage.setInvalidValue(invalidValue);
    
                errorList.add(errorMessage);
            });
            ErrorResponse errorResponse = new ErrorResponse();
    
            errorResponse.setErrorList(errorList);
            errorResponse.setMessage("");
            errorResponse.setRequestUrl(httpServletRequest.getRequestURI());
            errorResponse.setStatusCode(HttpStatus.BAD_REQUEST.toString());
            errorResponse.setResultCode("FAIL");
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    🎅 上記のように.

    WoWのようにきれいに整理された状態で、お客様にエラーメッセージを表示することができます!!