440 likes | 585 Views
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
E N D
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 /echo/person?name=Spencer&age=5 public class Person { private String name; private int age; // Getters and Setters } @RequestMapping("/echo/person") public @ResponseBody String doSomething(Person person) { return person.getName(); }
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 pre-configured, 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 BigIntNumberConverterimplements 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-drivenconversion-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 • 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. public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); }
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 • What are we validating here? • Length of ‘name’ is between 3 and 7 chars (inclusive) • Age is between 0 and 120 (inclusive) @Size(min=3, max=7) private String name; @Max(value=120) @Min(0) private Integer age;
Custom error messages • Each validation error has an error message • How do we override it? • 1. Change the global message for that validator • 2. Manually pass in a message • 3. Define and use a specific property javax.validation.constraints.Min.message=Value must be at least {value} @Size(min=3, max=7, message = "Your name must be between 3 and 7 characters.") @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) • *-servlet.xml <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="validationMessageSource" ref="messageSource"/> </bean> <mvc:annotation-driven validator="validator" />
Validating Controller Input • In Spring MVC, we can automatically validate controller inputs • Causes person to be validated, will throw validation exceptions • Allows us to gracefully handle validation errors public String createPerson(@Valid Person person) {} public String createPerson(@Valid Person person, Errors errors) { if(errors.hasErrors()) { return "addPerson"; } return "viewPerson"; }
@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-framework-reference/html/view.html#view-jsp-formtaglib • Spring MVC documentation • http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html