स्प्रिंग MVC: सत्यापन कैसे करें?


156

मैं यह जानना चाहूंगा कि उपयोगकर्ता इनपुट के फार्म सत्यापन को करने के लिए सबसे साफ और सबसे अच्छा तरीका क्या है। मैंने कुछ डेवलपर्स को लागू होते देखा है org.springframework.validation.Validator। उस बारे में एक प्रश्न: मैंने देखा कि यह एक वर्ग को मान्य करता है। क्या उपयोगकर्ता इनपुट से मूल्यों के साथ वर्ग को मैन्युअल रूप से भरना पड़ता है, और फिर सत्यापनकर्ता को पास किया जाता है?

मैं उपयोगकर्ता इनपुट को मान्य करने के लिए सबसे स्वच्छ और सबसे अच्छे तरीके के बारे में उलझन में हूं। मैं उपयोग करने की पारंपरिक विधि के बारे में जानता हूं request.getParameter()और फिर मैन्युअल रूप से जांच nullsकर रहा हूं, लेकिन मैं अपने सभी सत्यापन नहीं करना चाहता Controller। इस क्षेत्र पर कुछ अच्छी सलाह की बहुत सराहना की जाएगी। मैं इस एप्लिकेशन में हाइबरनेट का उपयोग नहीं कर रहा हूं।


जवाबों:


322

स्प्रिंग एमवीसी के साथ, सत्यापन करने के लिए 3 अलग-अलग तरीके हैं: एनोटेशन, मैन्युअल या दोनों का मिश्रण। मान्य करने के लिए एक अद्वितीय "सबसे साफ और सबसे अच्छा तरीका" नहीं है, लेकिन शायद एक है जो आपकी परियोजना / समस्या / संदर्भ को बेहतर ढंग से फिट करता है।

चलो एक उपयोगकर्ता है:

public class User {

    private String name;

    ...

}

विधि 1: यदि आपके पास स्प्रिंग 3.x + और करने के लिए सरल सत्यापन है, तो javax.validation.constraintsएनोटेशन (JSR-303 एनोटेशन भी कहा जाता है) का उपयोग करें।

public class User {

    @NotNull
    private String name;

    ...

}

आपको अपने पुस्तकालयों में जेएसआर -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 देखें: यदि उपयोगकर्ता के पास अशक्त नाम है, तो result.hasErrors () सही होगा।

विधि 2: यदि आपके पास जटिल सत्यापन है (जैसे बड़े व्यावसायिक सत्यापन तर्क, कई क्षेत्रों में सशर्त सत्यापन, आदि), या किसी कारण से आप विधि 1 का उपयोग नहीं कर सकते हैं, तो मैनुअल सत्यापन का उपयोग करें। नियंत्रक तर्क को मान्यता तर्क से अलग करने के लिए यह एक अच्छा अभ्यास है। खरोंच से अपना सत्यापन वर्ग (एस) न बनाएं, वसंत एक आसान org.springframework.validation.Validatorइंटरफ़ेस प्रदान करता है (वसंत 2 के बाद से)।

तो चलिए बताते हैं आपको

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

और आप कुछ "जटिल" सत्यापन करना चाहते हैं जैसे: यदि उपयोगकर्ता की आयु 18 वर्ष से कम है, तो जिम्मेदार को अशक्त नहीं होना चाहिए और जिम्मेदार की आयु 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 () सत्य होगा।

नोट: आप "binder.setValidator (...)" के साथ नियंत्रक के @InitBinder विधि में सत्यापनकर्ता को भी सेट कर सकते हैं (जिस स्थिति में विधि 1 और 2 का मिश्रण उपयोग संभव नहीं होगा, क्योंकि आप डिफ़ॉल्ट को प्रतिस्थापित करते हैं सत्यापनकर्ता)। या आप इसे कंट्रोलर के डिफॉल्ट कंस्ट्रक्टर में इंस्टेंट कर सकते हैं। या एक @ घटक / @ सेवा उपयोक्ता है जो आप अपने नियंत्रक में (@Autowired) इंजेक्षन करते हैं: बहुत उपयोगी है, क्योंकि अधिकांश सत्यापनकर्ता सिंगलटैन हैं + इकाई परीक्षण मॉकिंग आसान हो जाता है + आपका सत्यापनकर्ता अन्य स्प्रिंग घटकों को कॉल कर सकता है।

विधि 3: दोनों विधियों के संयोजन का उपयोग क्यों नहीं किया जाता है? एनोटेशन के साथ "नाम" विशेषता की तरह सरल सामान को मान्य करें (यह करने के लिए त्वरित, संक्षिप्त और अधिक पठनीय है)। सत्यापनकर्ताओं के लिए भारी सत्यापन रखें (जब कस्टम जटिल सत्यापन एनोटेशन को कोड करने में घंटों लगेंगे या बस जब एनोटेशन का उपयोग करना संभव नहीं होगा)। मैंने यह एक पूर्व परियोजना पर किया था, यह एक आकर्षण, त्वरित और आसान की तरह काम करता था।

चेतावनी: आपको अपवाद हैंडलिंग के लिए सत्यापन सत्यापन में गलती नहीं करनी चाहिए । उनका उपयोग कब करना है, जानने के लिए इस पोस्ट को पढ़ें

संदर्भ:


क्या आप मुझे बता सकते हैं कि इस कॉन्फ़िगरेशन के लिए मेरा सर्वलेट.एक्सएमएल क्या होना चाहिए। मैं त्रुटियों को वापस देखना चाहता हूं
devdar

@dev_darin का अर्थ है JSR-303 सत्यापन के लिए कॉन्फ़िगरेशन?
जेरोम डलबर्ट

2
@dev_marin सत्यापन के लिए, स्प्रिंग 3.x + में, "servlet.xml" या "[सर्वलेट-नाम] -servlet.xml" में कुछ भी विशेष नहीं है। आपको अपनी परियोजना पुस्तकालयों (या मावेन के माध्यम से) में हाइबरनेट-सत्यापनकर्ता जार की आवश्यकता है। यह सब है, तो यह काम करना चाहिए। चेतावनी यदि आप विधि 3 का उपयोग करते हैं: डिफ़ॉल्ट रूप से, प्रत्येक नियंत्रक के पास JSR-303 सत्यापनकर्ता तक पहुंच होती है, इसलिए इसे "setValidator" के साथ ओवरराइड न करने के लिए सावधान रहें। यदि आप एक कस्टम सत्यापनकर्ता जोड़ना चाहते हैं। शीर्ष, बस इसे झटपट करें और इसका उपयोग करें, या इसे इंजेक्ट करें (यदि यह एक स्प्रिंग घटक है)। यदि आपको अभी भी Google और स्प्रिंग डॉक्टर की जाँच के बाद समस्या है, तो आपको एक नया प्रश्न पोस्ट करना चाहिए।
जेरोम डलबर्ट

2
विधि 1 और 2 के मिश्रण उपयोग के लिए, @InitBinder का उपयोग करने का एक तरीका है। "Binder.setValidator (...)" के बजाय, "binder.addValidators (...)" का उपयोग कर सकते हैं
22

1
सही होने पर मुझे सही करें, लेकिन आप @InitBinder एनोटेशन का उपयोग करते समय JSR-303 एनोटेशन (विधि 1) और कस्टम सत्यापन (विधि 2) के माध्यम से सत्यापन को मिला सकते हैं। बस binder.addValidators (userValidator) के बजाय binder.setValidator (userValidator) का उपयोग करें और दोनों सत्यापन विधियाँ प्रभावी होंगी।
सेबस्टियनरिमेर

31

उपयोगकर्ता इनपुट को मान्य करने के दो तरीके हैं: एनोटेशन और वसंत की वैधता वर्ग की विरासत द्वारा। साधारण मामलों के लिए, एनोटेशन अच्छे हैं। यदि आपको जटिल सत्यापन की आवश्यकता है (जैसे क्रॉस-फील्ड सत्यापन, उदाहरण के लिए। "ईमेल पते को सत्यापित करें" फ़ील्ड), या यदि आपका मॉडल आपके आवेदन में कई स्थानों पर विभिन्न नियमों के साथ मान्य है, या यदि आपके पास अपना संशोधन करने की क्षमता नहीं है। मॉडल ऑब्जेक्ट को उस पर एनोटेशन रखकर, स्प्रिंग के वंशानुक्रम-आधारित वैलिडेटर जाने का रास्ता है। मैं दोनों के उदाहरण दिखाता हूँ।

वास्तविक सत्यापन हिस्सा उसी प्रकार का है, जिस प्रकार का सत्यापन आप उपयोग कर रहे हैं:

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एनोटेशन एनोटेशन हैं। आप हाइबरनेट का उपयोग भी कर सकते हैं org.hibernate.validator.constraints, लेकिन ऐसा नहीं लगता है कि आप हाइबरनेट का उपयोग कर रहे हैं।

वैकल्पिक रूप से, यदि आप स्प्रिंग के वैलिडेटर को लागू करते हैं, तो आप निम्नानुसार एक वर्ग बनाएंगे:

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]");
        }
    }
}

यदि उपरोक्त सत्यापनकर्ता का उपयोग कर रहे हैं, तो आपको सत्यापनकर्ता को स्प्रिंग नियंत्रक से बाँधना होगा (यदि एनोटेशन का उपयोग करना आवश्यक नहीं है):

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

स्प्रिंग डॉक्स भी देखें ।

उम्मीद है की वो मदद करदे।


जब स्प्रिंग वैलिडेटर का उपयोग करते हैं, तो मुझे नियंत्रक से पूजो सेट करना होगा और फिर इसे मान्य करना होगा?
devdar

मुझे यकीन नहीं है कि मैं आपके सवाल को समझ सकता हूँ। यदि आप नियंत्रक कोड स्निपेट देखते हैं, तो स्प्रिंग स्वचालित रूप से सबमिट किए गए फॉर्म को Fooहैंडलर विधि में पैरामीटर से बांध रहा है। क्या आप स्पष्ट कर सकते हो?
Stephen.hanson

ठीक है कि मैं क्या कह रहा हूं जब उपयोगकर्ता उपयोगकर्ता इनपुट जमा करता है तो नियंत्रक को http अनुरोध मिलता है, वहां से ऐसा होता है कि आप सभी उपयोगकर्ता मापदंडों को प्राप्त करने के लिए request.getParameter () का उपयोग करते हैं, फिर POJO में मान सेट करें: पास करें सत्यापन ऑब्जेक्ट के लिए वर्ग। सत्यापन वर्ग त्रुटियों को किसी त्रुटि के साथ दृश्य में वापस भेज देगा यदि कोई पाया जाता है। क्या ऐसा ही होता है?
देवधर २०

1
यह इस तरह से होता है लेकिन एक सरल तरीका है ... यदि आप JSP और एक <form: form कमांड नाम = "उपयोगकर्ता"> सबमिशन का उपयोग करते हैं, तो डेटा को नियंत्रक में @ModelAttribute ("उपयोगकर्ता") उपयोगकर्ता में स्वचालित रूप से डाला जाता है। तरीका। डॉक्टर देखें: static.springsource.org/spring/docs/3.0.x/…
जेरोम डलबर्ट

+1 क्योंकि यह पहला उदाहरण है जो मुझे मिला जो @ModelAttribute का उपयोग करता है; इसके बिना मैं काम करता है ट्यूटोरियल में से कोई भी नहीं मिला।
रिकार्डो कोस्सु

12

मैं जेरोम डलबर्ट के अच्छे उत्तर का विस्तार करना चाहूंगा। मुझे JSR-303 तरीके से अपने एनोटेशन सत्यापनकर्ताओं को लिखना बहुत आसान लगा। आप "एक क्षेत्र" सत्यापन के लिए सीमित नहीं हैं। आप टाइप स्तर पर अपना एनोटेशन बना सकते हैं और जटिल सत्यापन कर सकते हैं (नीचे उदाहरण देखें)। मैं इस तरह से पसंद करता हूं क्योंकि मुझे विभिन्न प्रकार के सत्यापन (स्प्रिंग और जेएसआर -303) की आवश्यकता नहीं है जैसे जेरोम करते हैं। इसके अलावा यह सत्यापनकर्ता "स्प्रिंग अवगत" हैं, इसलिए आप बॉक्स से @ इनजेक्ट / @ ऑटोवेयर का उपयोग कर सकते हैं।

कस्टम ऑब्जेक्ट सत्यापन का उदाहरण:

@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;

}

1
मैं यह भी सोच रहा था कि एक नियंत्रक के पास आमतौर पर एक सत्यापनकर्ता होता है और मैंने देखा कि आपके पास कई सत्यापनकर्ता हो सकते हैं, लेकिन अगर आपके पास एक वस्तु के लिए निर्धारित सत्यापन का एक सेट है, लेकिन जिस वस्तु को आप सुधारना चाहते हैं वह ऑपरेशन अलग है जैसे कि एक सेव / अपडेट के लिए सत्यापन के एक निश्चित सेट को बचाने की आवश्यकता होती है और एक अलग सेट के सत्यापन की आवश्यकता होती है। क्या ऑपरेशन के आधार पर सत्यापन को पूरा करने के लिए सत्यापनकर्ता वर्ग को कॉन्फ़िगर करने का एक तरीका है या आपको कई सत्यापनकर्ताओं का उपयोग करने की आवश्यकता होगी?
देवदार

1
आपके पास विधि पर एनोटेशन सत्यापन भी हो सकता है। यदि आप मेरे प्रश्न को समझते हैं तो आप अपना स्वयं का "डोमेन सत्यापन" बना सकते हैं। इसके लिए आपको निर्दिष्ट करना होगा ElementType.METHODमें @Target
माईकल।क्रूज़मैन

मैं समझता हूँ कि तुम क्या कह रहे हो, तुम मुझे एक स्पष्ट तस्वीर के लिए एक उदाहरण की ओर भी इशारा कर सकते हो।
देवदार

4

यदि आपके पास अलग-अलग विधि संचालकों के लिए एक ही त्रुटि तर्क है, तो आप निम्नलिखित कोड पैटर्न के साथ बहुत से संचालकों को समाप्त करेंगे:

if (validation.hasErrors()) {
  // do error handling
}
else {
  // do the actual business logic
}

मान लीजिए कि आप RESTful सेवाएँ बना रहे हैं और 400 Bad Requestहर सत्यापन त्रुटि मामले के लिए त्रुटि संदेशों के साथ वापस लौटना चाहते हैं । फिर, त्रुटि हैंडलिंग भाग हर एक REST समापन बिंदु के लिए समान होगा जिसे सत्यापन की आवश्यकता होती है। हर एक हैंडलर में इतना ही तर्क दोहराना DRY ish नहीं है!

इस समस्या को हल करने का एक तरीका BindingResultप्रत्येक To-Be-Validated बीन के तुरंत बाद ड्रॉप करना है । अब, आपका हैंडलर इस तरह होगा:

@RequestMapping(...)
public Something doStuff(@Valid Somebean bean) { 
    // do the actual business logic
    // Just the else part!
}

इस तरह, यदि बाध्य बीन वैध नहीं था, तो MethodArgumentNotValidExceptionवसंत द्वारा फेंक दिया जाएगा। आप इसे परिभाषित कर सकते हैं ControllerAdviceजो इस अपवाद को उसी त्रुटि से निपटने वाले तर्क को संभालता है:

@ControllerAdvice
public class ErrorHandlingControllerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
        // do error handling
        // Just the if part!
    }
}

तुम अब भी अंतर्निहित जांच कर सकते हैं BindingResultका उपयोग कर getBindingResultकी विधि MethodArgumentNotValidException


1

स्प्रिंग 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);
    }
}

0

इस बीन को अपने विन्यास वर्ग में रखें।

 @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();
}

मैन्युअल रूप से बीन को मान्य करने के लिए। तब आपको BindingResult में सभी परिणाम मिलेंगे और आप वहां से पुनः प्राप्त कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.