Spring MVC, Part 3 Spencer Uresk

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