blog

私を約束し、リクエストパラメータをif/elseチェックすることはできない

2点目:今はインターフェイス、その何十ものインターフェイス、そのフルスクリーンかもしれない唯一のif/elseです。 3点目は:それはより複雑であるか、または条件を追加したい場合は、後で、まだ全体のi...

Jan 7, 2021 · 10 min. read
シェア

ねえ!って感じですが...。

その昔、SAOの操作として出てきたリクエストエントリーを検証する if/elseチェーンが、実は特に好きでした

今のところ、プロジェクトはフロントエンドとバックエンドに分かれており、フロントエンドとバックエンドはリクエストパラメータで合意し、オブジェクトにカプセル化され、フロントセクションはオブジェクトに従ってパラメータを渡しますが、受信パラメータが空であるかどうかを判断する方法!

例えば、よく理解されている簡単な例を挙げてみましょう:

リクエストパラメータ
@Data
@ApiModel(value = "ログイン要求パラメータ")
public class LoginReqDTO {
 @ApiModelProperty(value = "携帯電話番号")
 private String mobile;
 @ApiModelProperty(value = "パスワード")
 private String password;
 @ApiModelProperty(value = "CAPTCHA")
 private String code;
}
応答結果
@Data
public class RespResult<T> {
 private Integer code;
 private String message;
 private T data;
}

例えば、ログインインターフェイスがフロントエンドから呼び出されたとき、 リクエストパラメータが空かどうかを判断する必要があり、このときに**SAOコード**が表示されます:

@RestController
public class LoginController{
 @ApiOperation(value = "ログインインターフェイス")
 @PostMapping(value = "/login")
 public RespResult login(LoginReqDTO loginReqDTO) {
 if (StringUtils.isEmpty(loginReqDTO.getMobile)) { // 携帯電話番号が空であるかどうかを判断する
 return new RespResult(400, "携帯電話番号を空にすることはできない");
 } else if (StringUtils.isEmpty(loginReqDTO.getPassword)) { // パスワードが空かどうかを判断する
 return new RespResult(400, "パスワードは空にすることはできない");
 } else if (StringUtils.isEmpty(loginReqDTO.getCode)) { // 検証コードが空かどうかを判断する
 return new RespResult(400, "CAPTCHAは空にすることはできない");
 } else {
 return new RespResult(200, "成功"); // 
 }
 }
}

クラウチングCAO!今リクエストオブジェクト内のパラメータはわずか3つであり、パラメータの数十がある場合、if/elseの 入れ子のそれらの数十は 、疲れていない、それは非常に酸っぱいと言うことができます......

そこで質問です:

  • 最初のポイント:他の誰かがそれを読んだ時点で、あなたは冷静です(そして、ジャオを思いつくことを恐れています!)。

  • 2点目:今は1つのインターフェイスなので、何十ものインターフェイスになると、そのフルスクリーンはif/elseだけになるかもしれません。(やっほー!(ムズムズ)

  • 3つ目のポイントは、もっと複雑にしたい、もっと条件を増やしたいと思っても、if/elseが残っていて、拡張性が非常に悪いということです。

  • ポイント4:最後にコードが変更されたとしても、古い機能は再テストされなければなりません

では、以下を読まずに、頭痛の種となるif/else文に対処するにはどうしたらよいでしょうか?

もちろん、**ループ文のswitch/case **を使って判定した方がエレガントだという意見もあるでしょう?答えは、「ハンマーほどの差はあるが、毛ほどの差はない」です!

次に、if/elseがなくならないように、それを改善するいくつかの方法について簡単に説明します。

第二に、Boot独自のパラメータがあり、それを検証することができます。

ブート**について学んだはずですから、ブート独自のスプリングバリデーション、つまり**@Validatedアノテーション**を使うのはどうでしょう?

まず、リクエスト・パラメーターに ** @*NotBlank アノテーション ** を追加し、空のときの **メッセージ ** を定義します。

@Data
@ApiModel(value = "ログイン要求パラメータ")
public class LoginReqDTO {
 @NotBlank(message = "携帯電話番号を空にすることはできない")
 @ApiModelProperty(value = "携帯電話番号")
 private String mobile;
 @NotBlank(message = "パスワードは空にすることはできない")
 @ApiModelProperty(value = "パスワード")
 private String password;
 @NotBlank(message = "CAPTCHAは空にすることはできない")
 @ApiModelProperty(value = "CAPTCHA")
 private String code;
}

次のステップでは、 リクエストパラメータが null かどうかを 判断するのは spring のバリデーションに任せて、例外を処理する グローバル例外ハンドラを定義します:

GlobalExceptionHandler :

@RestControllerAdvice
public class GlobalExceptionHandler {
 @ExceptionHandler(value = BindException.class)
 public RespResult violationException(BindException exception) {
 // パラメータなしでインターフェースにアクセスするとBindExceptionがスローされる。
 // だから、ちょうど例外をキャッチし、設定されたメッセージを返す。
 String message = exception.getAllErrors().get(0).getDefaultMessage();
 return new RespResult(400, message);
 }
}

次のインターフェイス呼び出しは 非常にシンプルに なり、行に**@Validatedアノテーション**を追加します:

@RestController
public class LoginController {
 @ApiOperation(value = "ログインインターフェイス")
 @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
 public RespResult login(@Validated LoginReqDTO loginReqDTO) {
 return new RespResult(200, "成功"); 
 }
}

最後に、 空のリクエストパラメータを使って 、Postmanを使ってログインインターフェイスにアクセスします:

{
 "code": 400,
 "message": "携帯電話番号を空にすることはできない",
 "data": null
}

SAOのコードもなくなって、いい気分じゃないですか。

また、この方法では、将来的に条件を拡張したい場合、以前のコードを変更する代わりに、 「@NotBlank(message = "XXX")アノテーションをリクエストパラメータオブジェクトに追加する」だけでよく、とても簡単です!

の例です:

 @NotBlank(message = "XXX空にすることはできない")
 private String XXX;

それはいいのですが、リクエストパラメーターがオブジェクトでない場合はどうなるのでしょうか?

例えば、少し古いプロジェクトのリクエスト・パラメーターは実体がない可能性があります!

慌てないでください!この時点では、 グローバル例外ハンドラであるGlobalExceptionHandlerを刷新するだけの簡単な問題 です:

@RestControllerAdvice
public class GlobalExceptionHandler {
 @ExceptionHandler({ConstraintViolationException.class})
 public RespResult violationException(Exception exception) {
 if (exception instanceof ConstraintViolationException) { //キーワードinstanceofを使用して、ConstraintViolationExceptionがExceptionの直接サブクラスか間接サブクラスかを判断する。
 return constraintViolationException((ConstraintViolationException) exception); //次のメソッドを呼び出し、結果を返す
 }
 return new RespResult(500, "server error"); // そうでなければ、サーバーエラーを実行する。
 }
 // そのようなメソッドがない場合は、ローカルホストにアクセスする:8080/login ConstraintViolationExceptionがスローされる。
 public RespResult constraintViolationException(ConstraintViolationException ex) {
 Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
 if (!CollectionUtils.isEmpty(constraintViolations)) { //ヌルかどうかを判断する
 StringBuilder sb = new StringBuilder();
 for (ConstraintViolation constraintViolation : constraintViolations) { //反復制約違反
 sb.append(constraintViolation.getMessage()).append(","); // エラーメッセージはsbにループされ、カンマで区切られている。
 }
 String errorMessage = sb.toString(); // 例外メッセージ文字列を取得する
 return new RespResult(400, errorMessage);
 }
 return new RespResult(500, "server error"); // そうでなければ、サーバーエラーを実行する。
 }
}

次に、 リクエスト・パラメーターの 前に"@NotBlank"アノテーションを追加し、インターフェース・ クラスに"@Validated "を 追加するだけで、if/elseもKOされます!

@Validated // このメモを忘れるな
@RestController
public class LoginController {
 
 @ApiOperation(value = "ログインインターフェイス")
 @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
 public RespResult login(@NotBlank(message = "携帯電話番号を空にすることはできません") 文字列モバイル,
 @NotBlank(message = "パスワードを空にすることはできない") 文字列パスワード,
 @NotBlank(message = "検証コードは空にすることはできない")文字列コード) {
 return new RespResult(200, "成功");
 }
}

もう一度、 リクエストパラメータを空にして 、Postmanを使ってログインインターフェイスにアクセスします:

{
 "code": 400,
 "message": "認証コードは空にすることはできません、パスワードは空にすることはできません、携帯電話番号は空にすることはできない,",
 "data": null
}

nullであるリクエストパラメータを すべて検証するのはいいにおいがしないということを見つけるのは難しいことではありません

しかし実際には、3 + 2 - 5 * 0 のように単純ではありません。

リクエストが**携帯電話番号**の正しいフォーマットであることを確認する必要がある場合はどうしますか?慌てないでください、カスタムアノテーションが ここにあります!

第二に、以下のようなカスタム注釈があります。

まず、アノテーションをカスタマイズするには、アノテーションの本質を理解することが重要です;

java.lang.annotation.Annotation "**インターフェイスでは、'アノテーション'を説明するためにこのような表現があります。

これは少し抽象的な表現ですが、アノテーションの本質を物語っています。

JDKの組み込みアノテーションの定義を見てください。

@Target(ElementType.METHOD) //アノテーション配置の目標位置
@Retention(RetentionPolicy.SOURCE) //アノテーションは、実行の段階である
public @interface Override {	// 
}

当然のことながら、これは@Overrideというアノテーションの定義であり、実際には本質的なものです:

public interface Override extends Annotation{ //継承アノテーション
} 

その通り、アノテーションは基本的にアノテーション・インターフェースを継承したインターフェイスです。 私は幸運にも、ある本でその片方の説明を読んだことがあります:

カスタムアノテーションKO if/elseの使用に 戻り、if/elseを待たせてはいけません。

まず、パラメータ検証アノテーション@ValidParam をカスタマイズします。

/**
 * Create By CodeCow on 2020/7/21.
 * カスタム注釈
 * @Target注釈をどこに置くか
 * @Retention注釈のどの段階を実行する?
 * @Constraintこのアノテーションの実装を指定する。:バリデータ...
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ParamValidator.class)
public @interface ValidParam {
 String message() default "携帯電話番号の形式が正しくない"; // 失敗したときに返される情報を確認し、デフォルト値を指定することができる
 Class<?>[] groups() default {};
 Class<? extends Payload>[] payload() default {};
}

次に、アノテーションを検証する チェッカーを書きます

/**
 * Create By CodeCow on 2020/7/21.
 * バリデータを書く この上のカスタムアノテーションを検証する: ValidParam
 * ConstraintValidatorインターフェイスを実装し、isValidメソッドをオーバーライドする必要がある。
 */
public class ParamValidator implements ConstraintValidator<ValidParam, String> {
 @Override
 public void initialize(ValidParam constraintAnnotation) {
 //  
 }
 @Override
 public boolean isValid(String param, ConstraintValidatorContext constraintValidatorContext) {
 // 検証を開始する
 // 携帯電話番号のフォーマット
 String mobileFormat = "^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|" + 
 "(18[0-9])|(19[8,9])|(166))[0-9]{8}$";
 Pattern pattern = Pattern.compile(mobileFormat);
 return pattern.matcher(param).matches(); // 携帯電話番号の書式が正しい場合はtrueを返し、そうでない場合はfalseを返す
 }
}

最後に、カスタムアノテーション(@ValidParam) をリクエストパラメータの前に追加して、ログインインターフェイスを変更します:

同様に、リクエストパラメータのチェックは単なるアノテーションであり、if/elseはKOされます。

@RestController
public class LoginController {
 @ApiOperation(value = "ログインインターフェイス")
 @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
 public RespResult login(@ValidParam String mobile) {
 return new RespResult(200, "成功");
 }
}

最後に、Postmanを使ってlocalhost:8080/loginという 空のパラメータで ログインインターフェイスにアクセスしてみます:

{
 "code": 400,
 "message": "携帯電話番号の書式が間違っている,",
 "data": null
}

余談。

さて、これはここで最初のチャットですが、この記事はちょうど**ヒスイを誘致するためにレンガを投げる**であり、我々はより多くの "愛if/else "は、サンプルを演奏しているこの段階で使用されているif/elseが良くないということではなく、ちょうど将来のコーディングで誰もが、乱用しないことを願って、コードがあまりにも多くありません私はちょうどあなたが将来のコーディングでそれを乱用しないことを望み、あなたのコードがあまりにも "冗長 "にならないことを願っています。

第二に、実際のプロジェクトでは、前回の記事で述べたように、ビジネスシナリオは上記のように単純ではありえませんので、マウスを握る瞬間、あるいはもっと考えて、書くことが妥当かどうか、スケーラビリティがあるかどうかを検討します。

おすすめ度 ★★★★★ おすすめ度 ★★★★★ おすすめ度 ★★★★★ おすすめ度 ★★★★★ おすすめ度

Read next