162

ユーザー入力のフォーム検証を実行する最もクリーンで最良の方法を知りたいです。一部の開発者が を実装しているのを見てきましたorg.springframework.validation.Validator。それについての質問: クラスを検証するのを見ました。ユーザー入力からの値を手動でクラスに入力し、バリデーターに渡す必要がありますか?

ユーザー入力を検証するための最もクリーンで最良の方法について混乱しています。request.getParameter()を使用してから手動でチェックするという従来の方法については知ってnullsいますが、 ですべての検証を行いたくありませんController。この分野に関する良いアドバイスをいただければ幸いです。このアプリケーションでは Hibernate を使用していません。

4

6 に答える 6

333

Spring MVC では、検証を実行する方法が 3 つあります。アノテーションを使用する方法、手動で行う方法、または両方を組み合わせた方法です。検証するための独自の「最もクリーンで最良の方法」はありませんが、プロジェクト/問題/コンテキストにより適した方法がおそらくあるでしょう。

User を持ってみましょう:

public class User {

    private String name;

    ...

}

方法 1 : Spring 3.x+ と簡単な検証が必要な場合は、javax.validation.constraintsアノテーション (JSR-303 アノテーションとも呼ばれます) を使用します。

public class User {

    @NotNull
    private String name;

    ...

}

リファレンス実装であるHibernate Validatorのような JSR-303 プロバイダがライブラリに必要です(このライブラリはデータベースやリレーショナル マッピングとは関係なく、検証を行うだけです :-)。

次に、コントローラーには次のようなものがあります。

@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @Valid @ModelAttribute("user") User user, BindingResult result){
    if (result.hasErrors()){
      // do something
    }
    else {
      // do something else
    }
}

@Valid に注意してください。ユーザーの名前が null の場合、result.hasErrors() は true になります。

方法 2 :複雑な検証 (大企業の検証ロジック、複数のフィールドにわたる条件付き検証など) がある場合、または何らかの理由で方法 1 を使用できない場合は、手動検証を使用します。コントローラーのコードを検証ロジックから分離することをお勧めします。検証クラスを最初から作成しないでください。Spring は便利なorg.springframework.validation.Validatorインターフェースを提供します (Spring 2 以降)。

だから、あなたが持っているとしましょう

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

また、次のような「複雑な」検証を行いたい場合: ユーザーの年齢が 18 歳未満の場合、responsibleUser を null にすることはできず、responsibleUser の年齢を 21 歳以上にする必要があります。

あなたはこのようなことをします

public class UserValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
      return User.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
      User user = (User) target;

      if(user.getName() == null) {
          errors.rejectValue("name", "your_error_code");
      }

      // do "complex" validation here

    }

}

次に、コントローラーで次のようになります。

@RequestMapping(value="/user", method=RequestMethod.POST)
    public createUser(Model model, @ModelAttribute("user") User user, BindingResult result){
        UserValidator userValidator = new UserValidator();
        userValidator.validate(user, result);

        if (result.hasErrors()){
          // do something
        }
        else {
          // do something else
        }
}

検証エラーがある場合、result.hasErrors() は true になります。

注:「binder.setValidator(...)」を使用して、コントローラーの@InitBinderメソッドでバリデーターを設定することもできます(この場合、デフォルトを置き換えるため、メソッド1と2を混在させることはできませんバリデータ)。または、コントローラーのデフォルトのコンストラクターでインスタンス化することもできます。または、コントローラーに注入する @Component/@Service UserValidator (@Autowired) を用意します。ほとんどのバリデーターはシングルトンであり、単体テストのモックが容易になり、バリデーターは他の Spring コンポーネントを呼び出すことができるため、非常に便利です。

方法 3 : 両方の方法を組み合わせて使用​​しないのはなぜですか? 「名前」属性などの単純なものを注釈で検証します (実行が速く、簡潔で読みやすいです)。バリデーターの重い検証を維持します (カスタムの複雑な検証アノテーションをコーディングするのに何時間もかかる場合、またはアノテーションを使用できない場合)。私は以前のプロジェクトでこれを行いました。それは魅力的で、素早く簡単に機能しました。

警告:検証処理例外処理と間違えてはいけません。この投稿を読んで、いつそれらを使用するかを確認してください。

参考文献:

于 2012-08-27T20:42:26.397 に答える
31

ユーザー入力を検証するには、アノテーションを使用する方法と、Spring の Validator クラスを継承する方法の 2 つがあります。単純なケースでは、注釈は便利です。複雑な検証が必要な場合 (たとえば、「メール アドレスの確認」フィールドなどのフィールド間の検証)、モデルがアプリケーション内の複数の場所で異なるルールで検証される場合、またはモデルを変更する機能がない場合アノテーションを配置してモデル オブジェクトを変更するには、Spring の継承ベースの Validator が最適です。両方の例を示します。

実際の検証部分は、使用している検証の種類に関係なく同じです。

RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
    if(result.hasErrors()) {
        return "fooPage";
    }
    ...
    return "successPage";
}

注釈を使用している場合、Fooクラスは次のようになります。

public class Foo {

    @NotNull
    @Size(min = 1, max = 20)
    private String name;

    @NotNull
    @Min(1)
    @Max(110)
    private Integer age;

    // getters, setters
}

上記の注釈はjavax.validation.constraints注釈です。Hibernate の も使用でき org.hibernate.validator.constraintsますが、Hibernate を使用しているようには見えません。

または、Spring の Validator を実装する場合は、次のようにクラスを作成します。

public class FooValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Foo.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {

        Foo foo = (Foo) target;

        if(foo.getName() == null) {
            errors.rejectValue("name", "name[emptyMessage]");
        }
        else if(foo.getName().length() < 1 || foo.getName().length() > 20){
            errors.rejectValue("name", "name[invalidLength]");
        }

        if(foo.getAge() == null) {
            errors.rejectValue("age", "age[emptyMessage]");
        }
        else if(foo.getAge() < 1 || foo.getAge() > 110){
            errors.rejectValue("age", "age[invalidAge]");
        }
    }
}

上記のバリデーターを使用する場合は、バリデーターを Spring コントローラーにバインドする必要もあります (アノテーションを使用する場合は必要ありません)。

@InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(new FooValidator());
}

Spring のドキュメントも参照してください。

それが役立つことを願っています。

于 2012-08-27T19:28:20.790 に答える
13

ジェローム・ダルバートの素晴らしい答えを拡張したいと思います。JSR-303 の方法で独自の注釈バリデーターを作成するのは非常に簡単であることがわかりました。「1 つのフィールド」の検証に限定されません。型レベルで独自の注釈を作成し、複雑な検証を行うことができます (以下の例を参照)。Jerome のように異なる種類の検証 (Spring と JSR-303) を混在させる必要がないため、私はこの方法を好みます。また、このバリデーターは「Spring 対応」であるため、すぐに @Inject/@Autowire を使用できます。

カスタム オブジェクト検証の例:

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { YourCustomObjectValidator.class })
public @interface YourCustomObjectValid {

    String message() default "{YourCustomObjectValid.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {

    @Override
    public void initialize(YourCustomObjectValid constraintAnnotation) { }

    @Override
    public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {

        // Validate your complex logic 

        // Mark field with error
        ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
        cvb.addNode(someField).addConstraintViolation();

        return true;
    }
}

@YourCustomObjectValid
public YourCustomObject {
}

ジェネリック フィールドの等価性の例:

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { FieldsEqualityValidator.class })
public @interface FieldsEquality {

    String message() default "{FieldsEquality.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * Name of the first field that will be compared.
     * 
     * @return name
     */
    String firstFieldName();

    /**
     * Name of the second field that will be compared.
     * 
     * @return name
     */
    String secondFieldName();

    @Target({ TYPE, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    public @interface List {
        FieldsEquality[] value();
    }
}




import java.lang.reflect.Field;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {

    private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);

    private String firstFieldName;
    private String secondFieldName;

    @Override
    public void initialize(FieldsEquality constraintAnnotation) {
        firstFieldName = constraintAnnotation.firstFieldName();
        secondFieldName = constraintAnnotation.secondFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null)
            return true;

        try {
            Class<?> clazz = value.getClass();

            Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
            firstField.setAccessible(true);
            Object first = firstField.get(value);

            Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
            secondField.setAccessible(true);
            Object second = secondField.get(value);

            if (first != null && second != null && !first.equals(second)) {
                    ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
          cvb.addNode(firstFieldName).addConstraintViolation();

          ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
          cvb.addNode(someField).addConstraintViolation(secondFieldName);

                return false;
            }
        } catch (Exception e) {
            log.error("Cannot validate fileds equality in '" + value + "'!", e);
            return false;
        }

        return true;
    }
}

@FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {

    private String password;

    private String confirmPassword;

}
于 2013-05-10T11:02:44.463 に答える
1

Spring Mvc Validation の完全な例を見つける

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;

public class LoginValidator implements Validator {
    public boolean supports(Class aClass) {
        return Login.class.equals(aClass);
    }

    public void validate(Object obj, Errors errors) {
        Login login = (Login) obj;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
                "username.required", "Required field");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
                "userpassword.required", "Required field");
    }
}


public class LoginController extends SimpleFormController {
    private LoginService loginService;

    public LoginController() {
        setCommandClass(Login.class);
        setCommandName("login");
    }

    public void setLoginService(LoginService loginService) {
        this.loginService = loginService;
    }

    @Override
    protected ModelAndView onSubmit(Object command) throws Exception {
        Login login = (Login) command;
        loginService.add(login);
        return new ModelAndView("loginsucess", "login", login);
    }
}
于 2013-11-13T17:52:03.287 に答える
0

この Bean を構成クラスに入れます。

 @Bean
  public Validator localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
  }

そして、あなたは使用することができます

 <T> BindingResult validate(T t) {
    DataBinder binder = new DataBinder(t);
    binder.setValidator(validator);
    binder.validate();
    return binder.getBindingResult();
}

Bean を手動で検証するため。次に、BindingResult ですべての結果を取得し、そこから取得できます。

于 2018-02-15T14:02:36.780 に答える