Spring MVC, Part 3 Spencer Uresk Notes • • • • • This is a training, NOT a presentation Please ask questions This is being recorded https://tech.lds.org/wiki/Java_Stack_Training Prerequisites – Spring MVC 1 and 2 Objectives • • • • Learn the basics of data binding Be able to write your own conversion service Save time by using Spring’s form tags Be able to validate your model objects in an easy, reusable way Data Binding • Binding is the way in which request parameters get turned into model objects • Properties get set based on their names public class Person { private String name; private int age; // Getters and Setters } @RequestMapping("/echo/person") public @ResponseBody String doSomething(Person person) return person.getName(); } { /echo/person?name=Spencer&age=5 Properties • name - getName()/setName() • person.name – getPerson().getName()/getPerson().setName() • person[1] – The first person in an array, list, or other ordered collection named person. • people[BOB] – The value of the map entry indexed by the key BOB in the Map people. Demo DEMO ConversionService • Since all parameters are Strings, how do they get turned into non-String values? • How do they get turned back into Strings for display on the page? • Spring’s ConversionService • Takes a type S and returns type T • Spring MVC comes with a lot of these preconfigured, but you can add your own Some pre-configured converters • • • • • • • String to Boolean String to Number (ie, int, long, etc..) String to Date String to Locale String to Enum (and vice-versa for all of these) Doesn’t always have to include a String on one side Adding new Converters • Figure out what source you want to use (S) • Figure out what the target type should be (T) • Implement the Converter<S, T> interface public class BigIntNumberConverter implements Converter<String, BigInteger> { public BigInteger convert(String source) { return new BigInteger(source); } } Custom Converter • Add some configuration to tell Spring MVC to use a custom conversion service • Add some configuration to your Spring MVC config to tell Spring MVC about your converter <mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" > <property name="converters"> <set> <bean class="org.lds.view.BigIntNumberConverter" /> </set> </property> </bean> Field Formatting • Conversions are useful when just converting from one type of data to another • Field formatting can be useful for when there is actual formatting that needs to occur • Number and Date formatters are built in (Date formatters require Joda time) public class Person { @NumberFormat(style=Style.CURRENCY) private BigDecimal salary; } What about errors? • What happens if you put ‘asdf’ in a number field? • A conversion error occurs • Which throws an exception • You can tell Spring to add that error to an Errors object to more gracefully deal with conversion errors Errors • Errors holds a list of errors that apply to a model object • Allows you to deal with binding (and other) errors yourself • Must be positioned immediately after the model object in the parameter list • Also used for Validation errors • BindingResult extends Errors and contains more information about the model object itself Customizing Error Messages • Default error messages are long and ugly • Customize them by adding properties to your message bundle • By target type: – typeMismatch.java.lang.Long={0} must be a number. – typeMismatch.int=Must be a number. • Note primitives are different than their wrappers • Globally: – typeMismatch=You entered something in wrong. Displaying Error Messages • Add a <spring:hasBindErrors /> tag for your model object • It makes an errors variable available that has a list of all error messages • You choose how to display the actual messages <spring:hasBindErrors name="person"> The following errors were found: <br /><br /> <ul> <c:forEach items="${errors.allErrors}" var="error"> <li><spring:message message="${error}"/></li> </c:forEach> </ul> </spring:hasBindErrors> Demo DEMO Lab 1 • Create a model object that we’ll use for data binding • Populate its values by adding parameters to the query string • Create a simple form to do the same • Customize the conversion error messages Spring’s form tag library • Binding-aware JSP tags for handling form elements • Integrated with Spring MVC to give the tags access to the model object and reference data • Comes from spring-webmvc.jar • Add the following to make the tags available: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> The form tag • Renders a form tag and exposes the binding to inner tags • You can specify any HTML attributes that are valid for an HTML form • You can also tell it what the form backing object is (it uses ‘command’ by default). The form tag - Example @RequestMapping("/person/add") public String addPerson(Model model) { Person person = new Person(); model.addAttribute(person); return "addPerson"; } <form:form method=”get” commandName=”person”> <form:input path=”name” /> </form:form> <form method=”get”> <input type=”text” name=”name” /> </form> The input tag • Renders an HTML input tag with a type of ‘text’ • You can specify any HTML attributes valid for an HTML input • You bind it to your model object by specifying the path relative to the backing object The input tag - Example @RequestMapping("/person/add") public String addPerson(Model model) { Person person = new Person(); person.setName(”Spencer”); model.addAttribute(person); return "addPerson"; } <form:form method=”get” commandName=”person”> <form:input path=”name” /> </form:form> <form method=”get”> <input type=”text” name=”name” value=”Spencer” /> </form> The checkbox tag • Renders an HTML input tag with a type of ‘checkbox’ public class Person { private boolean admin; private String[] languages; } <form:form commandName=”person”> <form:checkbox path=”admin” /> <form:checkbox path=“languages” value=“Java” /> <form:checkbox path=“languages” value=“Scala” /> </form:form> <form> <input type=”checkbox” name=”admin” value=”true” /> <input type=”checkbox” name=”languages” value=”Java” /> <input type=”checkbox” name=”languages” value=”Scala” /> </form> The checkboxes tag • Similar to checkbox tag, but creates multiple checkboxes instead of one public class Person { private boolean admin; private String[] favoriteLanguages; private List<String> allLanguages; } <form:form commandName=”person”> <form:checkbox path=”admin” /> <form:checkboxes path=”favoriteLanguages” items=”${allLanguages}” /> </form:form> <form> <input type=”checkbox” name=”admin” value=”true” /> <input type=”checkbox” name=” favoriteLanguages” value=”Java” /> <input type=”checkbox” name=” favoriteLanguages” value=”Scala” /> </form> The password tag • Renders an HTML input tag with a type of ‘password’ <form:form> <form:input path=”username” /> <form:password path=”password” /> </form:form> <form> <input type=”text” name=”username” /> <input type=”password” name=”password” /> </form> The select tag • Renders an HTML select tag • It can figure out whether multiple selections should be allowed • You can bind options using this tag, as well as by nesting option and options tags <form:select path=”favoriteLanguage” items=”${allLanguages}” /> <select name=”favoriteLanguage”> <option value=”Java”>Java</option> <option value=”Scala”>Scala</option> </form> The option/options tags • Renders an HTML option (or multiple options) • Nested within a select tag • Renders ‘selected’ based on the value bound to the select tag <form:select path=”favoriteLanguage”> <form:option value=”3” label=”.NET” /> <form:options items=”${languages}” itemValue=”id” itemLabel=”name” /> </form:select> <select name=”favoriteLanguage”> <option value=”.NET”>.NET</option> <option value=”Java”>Java</option> <option value=”Scala”>Scala</option> </form> The errors tag • Renders an HTML span tag, containing errors for given fields • You specify which fields to show errors for by specifying a path – path=“name” – Would display errors for name field. – path=“*” – Would display all errors <form:errors path=”*” cssClass=”errorBox” /> <form:errors path=”name” cssClass=”specificErrorBox” /> <span name=”*.errors” class=”errorBox”>Name is required.</span> <span name=“name.errors” class=“specificErrorBox”>Name is required.</span> Demo DEMO Lab 2 • Rewrite our form from Lab 1 to use Spring’s taglib • Add in some errors tags to handle conversion errors • Add a favorite language checkbox Validation • Spring supports a number of different types of validation • Validation shouldn’t be tied to the web tier • Should be easy to localize • Should be easy to plug in any validator • Spring MVC’s validation support provides all of these attributes Validator interface • org.springframework.validation.Validator public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); } • supports – Returns a boolean indicating whether or not the target class can be validated by this validator • validate – In charge of actually performing validation. Validation errors should be reported by adding them to the errors object. Validator Example public class PersonValidator implements Validator { /** * This Validator validates just Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too. old"); } } } Invoking our Validator • We can invoke our Validator in our controller code @RequestMapping(value = "/person/add", method = RequestMethod.POST) public String createPerson(Person person, Errors errors) { PersonValidator pv = new PersonValidator(); ValidationUtils.invokeValidator(pv, person, errors); if(errors.hasErrors()) { return "addPerson"; } return "viewPerson"; } JSR-303 Bean Validator API • We use the Hibernate Validator implementation of this API • Standardizes validation metadata and declaration • To use it, you annotate model properties with validation annotations • A number of useful built-in annotations are available • You can also make your own (outside the scope of this training) Some Examples @Size(min=3, max=7) private String name; @Max(value=120) @Min(0) private Integer age; • What are we validating here? – Length of ‘name’ is between 3 and 7 chars (inclusive) – Age is between 0 and 120 (inclusive) Custom error messages • Each validation error has an error message • How do we override it? • 1. Change the global message for that validator javax.validation.constraints.Min.message=Value must be at least {value} • 2. Manually pass in a message @Size(min=3, max=7, message = "Your name must be between 3 and 7 characters.") • 3. Define and use a specific property @Max(value=120, message="{age.too.old}") // Annotation age.too.old=Age must be under 120.// Message bundle property Demo DEMO Configuration • How do we tell Spring we want to use JSR-303 validation? • It is already done in the Stack • First, configure a validator in the main applicationContext (remember, this is reusable across tiers!) • Then, tell Spring MVC to use that validator Configuration • applicationContext.xml (we’re also telling it to use our messageSource bean to find messges) <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="validationMessageSource" ref="messageSource"/> </bean> • *-servlet.xml <mvc:annotation-driven validator="validator" /> Validating Controller Input • In Spring MVC, we can automatically validate controller inputs public String createPerson(@Valid Person person) {} • Causes person to be validated, will throw validation exceptions public String createPerson(@Valid Person person, Errors errors) { if(errors.hasErrors()) { return "addPerson"; } return "viewPerson"; } • Allows us to gracefully handle validation errors @Valid • In theory, it should also work on parameters annotated with @RequestBody • But it doesn’t • It will, however, work in Spring 3.1 Lab 3 • Validate our person object • Make sure they supply a name that is at least 3 characters • Make sure they supply an age between 0 and 125 • Customize the error messages Resources • Spring Form taglib documentation • http://static.springsource.org/spring/docs/3.0.x/ spring-frameworkreference/html/view.html#view-jsp-formtaglib • Spring MVC documentation • http://static.springsource.org/spring/docs/3.0.x/ spring-framework-reference/html/mvc.html
© Copyright 2024