Monday 1 April 2013

@InitBinder - Custom Spring Validator to a Spring MVC Controller


This technique should be used when you need to do ALL your controller’s validation yourself, and you can’t or don’t want to make use of the Hibernate’s reference implementation of a JSR 303 validator. From this, you’ll guess that you can’t mix your own custom Spring validator with Hibernate’s JSR 303 validator.


The MVC command object is a simple matter of tying together a few address fields:

public class Address {

  private String street;

  private String town;

  private String country;

  private String postCode;

  public String getStreet() {

    return street;
  }

  public void setStreet(String street) {

    this.street = street;
  }

  public String getTown() {

    return town;
  }

  public void setTown(String town) {

    this.town = town;
  }

  public String getCountry() {

    return country;
  }

  public void setCountry(String country) {

    this.country = country;
  }

  public String getPostCode() {

    return postCode;
  }

  public void setPostCode(String post_code) {

    this.postCode = post_code;
  }
}

Create a custom validator

@Component
public class AddressValidator implements Validator {

  /**
   * Return true if this object can validate objects of the given class. This is cargo-cult
   * code: all implementations are the same and can be cut 'n' pasted from earlier examples.
   */
  @Override
  public boolean supports(Class clazz) {

    return clazz.isAssignableFrom(Address.class);
  }

  /**
   * Validate an object, which must be a class type for which the supports() method returned
   * true.
   *
   * @param obj The target object to validate
   * @param errors contextual state info about the validation process (never null)
   */
  @Override
  public void validate(Object obj, Errors errors) {

    Address address = (Address) obj;
    String postCode = address.getPostCode();
    validatePostCode(postCode, errors);
  }

  private void validatePostCode(String postCode, Errors errors) {

    if (isValidString(postCode) && isNotBirminghamPostCode(postCode)) {
      errors.rejectValue("postCode", "AddressValidator.postCode.notBirmingham",
          "Not a Birmingham Post Code");
    }
  }

  private boolean isValidString(String str) {

    return isNotNull(str) && (str.length() > 0);
  }

  private boolean isNotNull(String postCode) {

    return postCode != null;
  }

  /** The first character of the Birmingham post code is 'B' */
  private boolean isNotBirminghamPostCode(String postCode) {

    char val = postCode.charAt(0);
    return val != 'B';
  }
}

Attaching the validator to the controller is pretty straight forward. The first step is to annotate the Address command object with @Valid:

 @RequestMapping(value = PATH, method = RequestMethod.POST)
  public String addAddress(@Valid Address address, BindingResult result, Model model) {




The second step is to inject the validator into the data binder:

@InitBinder
  protected void initBinder(WebDataBinder binder) {

    binder.setValidator(addressValidator);
  }

Adding these two code snippets together, the complete AddressController code looks like this:

@Controller
public class AddressController {

  private static final String FORM_VIEW = "address.page";

  private static final String PATH = "/address";

  @Autowired
  private AddressValidator addressValidator;

  /**
   * Create the initial blank form
   */
  @RequestMapping(value = PATH, method = RequestMethod.GET)
  public String getCreateForm(Model model) {

    model.addAttribute(new Address());
    return FORM_VIEW;
  }

  /**
   * Attach the custom validator to the Spring context
   */
  @InitBinder
  protected void initBinder(WebDataBinder binder) {

    binder.setValidator(addressValidator);
  }

  /**
   * This is the handler method. Check for errors and proceed to the next view
   */
  @RequestMapping(value = PATH, method = RequestMethod.POST)
  public String addAddress(@Valid Address address, BindingResult result, Model model) {

    if (!result.hasErrors()) {
      model.addAttribute("noErrors",
          "No Errors This Time for postal code: " + address.getPostCode());
    }

    return FORM_VIEW;
  }
}

Same Validation Using JSR 303 validator

public class Address {

  // These JSR 303 built in annotations don't do anything
  // When you've injected your own validator
  @NotEmpty
  @Size(min = 1, max = 12)
  private String street;

  @NotEmpty
  private String town;

  @NotEmpty
  private String country;

  @NotEmpty
  private String postCode;

0 comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...