1683 lines
83 KiB
XML
1683 lines
83 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||
|
||
<chapter id="validation">
|
||
<title>Validation, Data Binding, and Type Conversion</title>
|
||
|
||
<section id="validation-introduction">
|
||
<title>Introduction</title>
|
||
|
||
<para>There are pros and cons for considering validation as business logic,
|
||
and Spring offers a design for validation (and data binding) that
|
||
does not exclude either one of them. Specifically validation should not be
|
||
tied to the web tier, should be easy to localize and it should be
|
||
possible to plug in any validator available. Considering the above, Spring
|
||
has come up with a <interfacename>Validator</interfacename> interface that
|
||
is both basic and eminently usable in every layer of an application.</para>
|
||
|
||
<para>Data binding is useful for allowing user input to be dynamically
|
||
bound to the domain model of an application (or whatever objects you use
|
||
to process user input). Spring provides the so-called
|
||
<interfacename>DataBinder</interfacename> to do exactly that. The
|
||
<interfacename>Validator</interfacename> and the
|
||
<interfacename>DataBinder</interfacename> make up the <literal>validation</literal> package,
|
||
which is primarily used in but not limited to the MVC framework.</para>
|
||
|
||
<para>The <interfacename>BeanWrapper</interfacename> is a fundamental concept in the
|
||
Spring Framework and is used in a lot of places. However, you probably
|
||
will not have the need to use the <interfacename>BeanWrapper</interfacename> directly. Because this
|
||
is reference documentation however, we felt that some explanation might be
|
||
in order. We will explain the <interfacename>BeanWrapper</interfacename> in this chapter since, if you were
|
||
going to use it at all, you would most likely do so when trying to bind data to objects.</para>
|
||
|
||
<para>Spring's DataBinder and the lower-level BeanWrapper both use PropertyEditors to parse and format property values.
|
||
The <interfacename>PropertyEditor</interfacename> concept is part of the JavaBeans specification, and is also explained in this chapter.
|
||
Spring 3 introduces a "core.convert" package that provides a general type conversion facility, as well as a higher-level "format" package for formatting UI field values.
|
||
These new packages may be used as simpler alternatives to PropertyEditors, and will also be discussed in this chapter.</para>
|
||
|
||
</section>
|
||
|
||
<section id="validator">
|
||
<title>Validation using Spring's <interfacename>Validator</interfacename> interface</title>
|
||
<para>Spring's features a <interfacename>Validator</interfacename> interface that you can
|
||
use to validate objects. The <interfacename>Validator</interfacename> interface works using
|
||
an <interfacename>Errors</interfacename> object so that while validating, validators can report
|
||
validation failures to the <interfacename>Errors</interfacename> object.</para>
|
||
<para>Let's consider a small data object:</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class Person {
|
||
|
||
private String name;
|
||
private int age;
|
||
|
||
]]><lineannotation>// the usual getters and setters...</lineannotation><![CDATA[
|
||
}]]></programlisting>
|
||
<para>We're going to provide validation behavior for the <classname>Person</classname>
|
||
class by implementing the following two methods of the
|
||
<interfacename>org.springframework.validation.Validator</interfacename> interface:
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para><methodname>supports(Class)</methodname> - Can this
|
||
<interfacename>Validator</interfacename> validate instances of the supplied
|
||
<classname>Class</classname>?</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><methodname>validate(Object, org.springframework.validation.Errors)</methodname> -
|
||
validates the given object and in case of validation errors, registers
|
||
those with the given <interfacename>Errors</interfacename> object</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
<para>
|
||
Implementing a <interfacename>Validator</interfacename> is fairly straightforward,
|
||
especially when you know of the <classname>ValidationUtils</classname> helper class
|
||
that the Spring Framework also provides.</para>
|
||
<programlisting language="java"><![CDATA[public class PersonValidator implements Validator {
|
||
|
||
]]><lineannotation>/**
|
||
* This <interfacename>Validator</interfacename> validates <emphasis role="bold">just</emphasis> <classname>Person</classname> instances
|
||
*/</lineannotation><![CDATA[
|
||
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.darn.old");
|
||
}
|
||
}
|
||
}]]></programlisting>
|
||
<para>As you can see, the <literal>static</literal> <methodname>rejectIfEmpty(..)</methodname>
|
||
method on the <classname>ValidationUtils</classname> class is used to reject the
|
||
<literal>'name'</literal> property if it is <literal>null</literal> or the empty string.
|
||
Have a look at the Javadoc for the <classname>ValidationUtils</classname> class to see
|
||
what functionality it provides besides the example shown previously.</para>
|
||
<para>While it is certainly possible to implement a single
|
||
<interfacename>Validator</interfacename> class to validate each of the nested objects
|
||
in a rich object, it may be better to encapsulate the validation logic for each nested
|
||
class of object in its own <interfacename>Validator</interfacename> implementation. A
|
||
simple example of a <emphasis>'rich'</emphasis> object would be a
|
||
<classname>Customer</classname> that is composed of two <classname>String</classname>
|
||
properties (a first and second name) and a complex <classname>Address</classname> object.
|
||
<classname>Address</classname> objects may be used independently of
|
||
<classname>Customer</classname> objects, and so a distinct
|
||
<classname>AddressValidator</classname> has been implemented. If you want your
|
||
<classname>CustomerValidator</classname> to reuse the logic contained within the
|
||
<classname>AddressValidator</classname> class without recourse to copy-n-paste you can
|
||
dependency-inject or instantiate an <classname>AddressValidator</classname> within your
|
||
<classname>CustomerValidator</classname>, and use it like so:</para>
|
||
<programlisting language="java"><![CDATA[public class CustomerValidator implements Validator {
|
||
|
||
private final Validator addressValidator;
|
||
|
||
public CustomerValidator(Validator addressValidator) {
|
||
if (addressValidator == null) {
|
||
throw new IllegalArgumentException("The supplied [Validator] is required and must not be null.");
|
||
}
|
||
if (!addressValidator.supports(Address.class)) {
|
||
throw new IllegalArgumentException(
|
||
"The supplied [Validator] must support the validation of [Address] instances.");
|
||
}
|
||
this.addressValidator = addressValidator;
|
||
}
|
||
|
||
]]><lineannotation>/**
|
||
* This <interfacename>Validator</interfacename> validates <classname>Customer</classname> instances, and any subclasses of <classname>Customer</classname> too
|
||
*/</lineannotation><![CDATA[
|
||
public boolean supports(Class clazz) {
|
||
return Customer.class.isAssignableFrom(clazz);
|
||
}
|
||
|
||
public void validate(Object target, Errors errors) {
|
||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
|
||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
|
||
Customer customer = (Customer) target;
|
||
try {
|
||
errors.pushNestedPath("address");
|
||
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
|
||
} finally {
|
||
errors.popNestedPath();
|
||
}
|
||
}
|
||
}]]></programlisting>
|
||
<para>Validation errors are reported to the <interfacename>Errors</interfacename>
|
||
object passed to the validator. In case of Spring Web MVC you can use
|
||
<literal><spring:bind/></literal> tag to inspect the error messages, but
|
||
of course you can also inspect the errors object yourself. More information about
|
||
the methods it offers can be found from the Javadoc.</para>
|
||
</section>
|
||
<section id="validation-conversion">
|
||
<title>Resolving codes to error messages</title>
|
||
<para>We've talked about databinding and validation. Outputting messages corresponding to
|
||
validation errors is the last thing we need to discuss. In the example we've shown
|
||
above, we rejected the <literal>name</literal> and the <literal>age</literal> field.
|
||
If we're going to output the error messages by using a <interfacename>MessageSource</interfacename>,
|
||
we will do so using the error code we've given when rejecting the field ('name' and 'age'
|
||
in this case). When you call (either directly, or indirectly, using for example the
|
||
<classname>ValidationUtils</classname> class) <literal>rejectValue</literal> or one of
|
||
the other <literal>reject</literal> methods from the <interfacename>Errors</interfacename>
|
||
interface, the underlying implementation will not only register the code you've
|
||
passed in, but also a number of additional error codes. What error codes it registers
|
||
is determined by the <interfacename>MessageCodesResolver</interfacename> that is used.
|
||
By default, the <classname>DefaultMessageCodesResolver</classname> is used, which for example
|
||
not only registers a message with the code you gave, but also messages that include the
|
||
field name you passed to the reject method. So in case you reject a field using
|
||
<literal>rejectValue("age", "too.darn.old")</literal>, apart from the
|
||
<literal>too.darn.old</literal> code, Spring will also register
|
||
<literal>too.darn.old.age</literal> and <literal>too.darn.old.age.int</literal>
|
||
(so the first will include the field name and the second will include the type of the
|
||
field); this is done as a convenience to aid developers in targeting error
|
||
messages and suchlike.</para>
|
||
<para>More information on the <interfacename>MessageCodesResolver</interfacename> and the default
|
||
strategy can be found online with the Javadocs for
|
||
<ulink url="http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/validation/MessageCodesResolver.html">MessageCodesResolver</ulink>
|
||
and
|
||
<ulink url="http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/validation/DefaultMessageCodesResolver.html">DefaultMessageCodesResolver</ulink>
|
||
respectively.</para>
|
||
</section>
|
||
|
||
<section id="beans-beans">
|
||
<title>Bean manipulation and the <interfacename>BeanWrapper</interfacename></title>
|
||
|
||
<para>The <literal>org.springframework.beans</literal> package adheres to
|
||
the JavaBeans standard provided by Sun. A JavaBean is simply a class with
|
||
a default no-argument constructor, which follows a naming convention
|
||
where (by way of an example) a property named <literal>bingoMadness</literal> would have a setter
|
||
method <methodname>setBingoMadness(..)</methodname> and a getter method <methodname>getBingoMadness()</methodname>.
|
||
For more information about JavaBeans and the specification, please refer
|
||
to Sun's website ( <ulink url="http://java.sun.com/products/javabeans/">java.sun.com/products/javabeans</ulink>).</para>
|
||
|
||
<para>One quite important class in the beans package is the
|
||
<interfacename>BeanWrapper</interfacename> interface and its corresponding
|
||
implementation (<classname>BeanWrapperImpl</classname>). As quoted from the
|
||
Javadoc, the <interfacename>BeanWrapper</interfacename> offers functionality to set and get property
|
||
values (individually or in bulk), get property descriptors, and to query
|
||
properties to determine if they are readable or writable. Also, the
|
||
<interfacename>BeanWrapper</interfacename> offers support for nested properties, enabling the setting of
|
||
properties on sub-properties to an unlimited depth. Then, the <interfacename>BeanWrapper</interfacename>
|
||
supports the ability to add standard JavaBeans
|
||
<interfacename>PropertyChangeListeners</interfacename> and
|
||
<interfacename>VetoableChangeListeners</interfacename>, without the need for
|
||
supporting code in the target class. Last but not least, the <interfacename>BeanWrapper</interfacename>
|
||
provides support for the setting of indexed properties. The <interfacename>BeanWrapper</interfacename>
|
||
usually isn't used by application code directly, but by the
|
||
<interfacename>DataBinder</interfacename> and the
|
||
<interfacename>BeanFactory</interfacename>.</para>
|
||
|
||
<para>The way the <interfacename>BeanWrapper</interfacename> works is partly indicated by its name:
|
||
<emphasis>it wraps a bean</emphasis> to perform actions on that bean, like
|
||
setting and retrieving properties.</para>
|
||
|
||
<section id="beans-beans-conventions">
|
||
<title>Setting and getting basic and nested properties</title>
|
||
|
||
<para>Setting and getting properties is done using the
|
||
<literal>setPropertyValue(s)</literal> and
|
||
<literal>getPropertyValue(s)</literal> methods that both come with a
|
||
couple of overloaded variants. They're all described in more detail in
|
||
the Javadoc Spring comes with. What's important to know is that there
|
||
are a couple of conventions for indicating properties of an object. A
|
||
couple of examples:</para>
|
||
<table id="beans-beans-conventions-properties-tbl">
|
||
<title>Examples of properties</title>
|
||
|
||
<tgroup cols="2">
|
||
<colspec colname="c1" colwidth="1*" />
|
||
<colspec colname="c2" colwidth="3*" />
|
||
<thead>
|
||
<row>
|
||
<entry>Expression</entry>
|
||
<entry>Explanation</entry>
|
||
</row>
|
||
</thead>
|
||
|
||
<tbody>
|
||
<row>
|
||
<entry><literal>name</literal></entry>
|
||
|
||
<entry>Indicates the property <literal>name</literal>
|
||
corresponding to the methods <methodname>getName()</methodname> or
|
||
<methodname>isName()</methodname> and
|
||
<methodname>setName(..)</methodname></entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><literal>account.name</literal></entry>
|
||
|
||
<entry>Indicates the nested property <literal>name</literal>
|
||
of the property <literal>account</literal> corresponding e.g.
|
||
to the methods <literal>getAccount().setName()</literal> or
|
||
<literal>getAccount().getName()</literal></entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><literal>account[2]</literal></entry>
|
||
|
||
<entry>Indicates the <emphasis>third</emphasis> element of the
|
||
indexed property <literal>account</literal>. Indexed
|
||
properties can be of type <literal>array</literal>,
|
||
<literal>list</literal> or other <emphasis>naturally
|
||
ordered</emphasis> collection</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><literal>account[COMPANYNAME]</literal></entry>
|
||
|
||
<entry>Indicates the value of the map entry indexed by the key
|
||
<emphasis>COMPANYNAME</emphasis> of the Map property
|
||
<literal>account</literal></entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</table>
|
||
|
||
<para>Below you'll find some examples of working with the <interfacename>BeanWrapper</interfacename> to
|
||
get and set properties.</para>
|
||
|
||
<para><emphasis>(This next section is not vitally important to you if you're not
|
||
planning to work with the <interfacename>BeanWrapper</interfacename> directly. If you're
|
||
just using the <interfacename>DataBinder</interfacename> and the
|
||
<interfacename>BeanFactory</interfacename> and their out-of-the-box implementation, you
|
||
should skip ahead to the section about
|
||
<interfacename>PropertyEditors</interfacename>.)</emphasis></para>
|
||
|
||
<para>Consider the following two classes:</para>
|
||
<programlisting language="java"><![CDATA[public class Company {
|
||
private String name;
|
||
private Employee managingDirector;
|
||
|
||
public String getName() {
|
||
return this.name;
|
||
}
|
||
public void setName(String name) {
|
||
this.name = name;
|
||
}
|
||
public Employee getManagingDirector() {
|
||
return this.managingDirector;
|
||
}
|
||
public void setManagingDirector(Employee managingDirector) {
|
||
this.managingDirector = managingDirector;
|
||
}
|
||
}]]></programlisting>
|
||
|
||
<programlisting language="java"><![CDATA[public class Employee {
|
||
private String name;
|
||
private float salary;
|
||
|
||
public String getName() {
|
||
return this.name;
|
||
}
|
||
public void setName(String name) {
|
||
this.name = name;
|
||
}
|
||
public float getSalary() {
|
||
return salary;
|
||
}
|
||
public void setSalary(float salary) {
|
||
this.salary = salary;
|
||
}
|
||
}]]></programlisting>
|
||
|
||
<para>The following code snippets show some examples of how to retrieve
|
||
and manipulate some of the properties of instantiated
|
||
<literal>Companies</literal> and <literal>Employees</literal>:</para>
|
||
<programlisting language="java"><![CDATA[BeanWrapper company = BeanWrapperImpl(new Company());
|
||
]]><lineannotation>// setting the company name..</lineannotation><![CDATA[
|
||
company.setPropertyValue("name", "Some Company Inc.");
|
||
]]><lineannotation>// ... can also be done like this:</lineannotation><![CDATA[
|
||
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
|
||
company.setPropertyValue(value);
|
||
|
||
]]><lineannotation>// ok, let's create the director and tie it to the company:</lineannotation><![CDATA[
|
||
BeanWrapper jim = BeanWrapperImpl(new Employee());
|
||
jim.setPropertyValue("name", "Jim Stravinsky");
|
||
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
|
||
|
||
]]><lineannotation>// retrieving the salary of the managingDirector through the company</lineannotation><![CDATA[
|
||
Float salary = (Float) company.getPropertyValue("managingDirector.salary");]]></programlisting>
|
||
</section>
|
||
|
||
<section id="beans-beans-conversion">
|
||
<title>Built-in <interface>PropertyEditor</interface> implementations</title>
|
||
|
||
<para>Spring uses the concept of <literal>PropertyEditors</literal> to effect the conversion
|
||
between an <classname>Object</classname> and a <classname>String</classname>. If you think about it,
|
||
it sometimes might be handy to be able to represent properties in a different way than the object itself.
|
||
For example, a <classname>Date</classname> can be represented in a human readable way (as the
|
||
<classname>String</classname> '<literal>2007-14-09</literal>'), while we're still able to convert the
|
||
human readable form back to the original date (or even better: convert any date entered in a human readable
|
||
form, back to <classname>Date</classname> objects). This behavior can be achieved by
|
||
<emphasis>registering custom editors</emphasis>, of type <interfacename>java.beans.PropertyEditor</interfacename>.
|
||
Registering custom editors on a <interfacename>BeanWrapper</interfacename> or alternately in a specific IoC
|
||
container as mentioned in the previous chapter, gives it the knowledge of how to convert properties to the
|
||
desired type. Read more about <interfacename>PropertyEditors</interfacename> in the Javadoc of the
|
||
<literal>java.beans</literal> package provided by Sun.</para>
|
||
|
||
<para>A couple of examples where property editing is used in Spring:
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para><emphasis>setting properties on beans</emphasis> is done
|
||
using <literal>PropertyEditors</literal>. When mentioning
|
||
<literal>java.lang.String</literal> as the value of a property of
|
||
some bean you're declaring in XML file, Spring will (if the setter
|
||
of the corresponding property has a <classname>Class</classname>-parameter) use the
|
||
<classname>ClassEditor</classname> to try to resolve the parameter to
|
||
a <classname>Class</classname> object.</para>
|
||
</listitem>
|
||
|
||
<listitem>
|
||
<para><emphasis>parsing HTTP request parameters</emphasis> in
|
||
Spring's MVC framework is done using all kinds of <literal>PropertyEditors</literal>
|
||
that you can manually bind in all subclasses of the
|
||
<classname>CommandController</classname>.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<para>Spring has a number of built-in <literal>PropertyEditors</literal> to make life easy.
|
||
Each of those is listed below and they are all located in the
|
||
<literal>org.springframework.beans.propertyeditors</literal> package. Most, but not all (as indicated below),
|
||
are registered by default by <classname>BeanWrapperImpl</classname>. Where the property editor is configurable
|
||
in some fashion, you can of course still register your own variant to override the default one:</para>
|
||
|
||
<table id="beans-beans-property-editors-tbl">
|
||
<title>Built-in <literal>PropertyEditors</literal></title>
|
||
|
||
<tgroup cols="2">
|
||
<colspec colname="c1" colwidth="3*" />
|
||
<colspec colname="c2" colwidth="5*" />
|
||
|
||
<thead>
|
||
<row>
|
||
<entry>Class</entry>
|
||
<entry>Explanation</entry>
|
||
</row>
|
||
</thead>
|
||
|
||
<tbody>
|
||
<row>
|
||
<entry><classname>ByteArrayPropertyEditor</classname></entry>
|
||
|
||
<entry>Editor for byte arrays. Strings will simply be
|
||
converted to their corresponding byte representations.
|
||
Registered by default by <classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>ClassEditor</classname></entry>
|
||
|
||
<entry>Parses Strings representing classes to actual classes
|
||
and the other way around. When a class is not found, an
|
||
<classname>IllegalArgumentException</classname> is thrown. Registered by default by
|
||
<classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>CustomBooleanEditor</classname></entry>
|
||
|
||
<entry>Customizable property editor for <classname>Boolean</classname> properties.
|
||
Registered by default by <classname>BeanWrapperImpl</classname>, but, can be
|
||
overridden by registering custom instance of it as custom
|
||
editor.</entry>
|
||
</row>
|
||
<row>
|
||
<entry><classname>CustomCollectionEditor</classname></entry>
|
||
<entry>Property editor for Collections, converting any source
|
||
<interfacename>Collection</interfacename> to a given target <interfacename>Collection</interfacename> type.</entry>
|
||
</row>
|
||
<row>
|
||
<entry><classname>CustomDateEditor</classname></entry>
|
||
|
||
<entry>Customizable property editor for java.util.Date,
|
||
supporting a custom DateFormat. NOT registered by default. Must
|
||
be user registered as needed with appropriate format.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>CustomNumberEditor</classname></entry>
|
||
|
||
<entry>Customizable property editor for any Number subclass
|
||
like <classname>Integer</classname>, <classname>Long</classname>,
|
||
<classname>Float</classname>, <classname>Double</classname>. Registered
|
||
by default by <classname>BeanWrapperImpl</classname>, but can be
|
||
overridden by registering custom instance of it as a custom editor.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>FileEditor</classname></entry>
|
||
|
||
<entry>Capable of resolving Strings to
|
||
<classname>java.io.File</classname> objects. Registered by default by
|
||
<classname>BeanWrapperImpl</classname>. </entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>InputStreamEditor</classname></entry>
|
||
|
||
<entry>One-way property editor, capable of taking a text
|
||
string and producing (via an intermediate <classname>ResourceEditor</classname> and
|
||
<interfacename>Resource</interfacename>) an
|
||
<interfacename>InputStream</interfacename>, so <interfacename>InputStream</interfacename>
|
||
properties may be directly set as Strings. Note that the default usage
|
||
will not close the <interfacename>InputStream</interfacename> for
|
||
you! Registered by default by <classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>LocaleEditor</classname></entry>
|
||
|
||
<entry>Capable of resolving Strings to
|
||
<classname>Locale</classname> objects and vice versa (the String
|
||
format is [language]_[country]_[variant], which is the same
|
||
thing the toString() method of Locale provides). Registered by
|
||
default by <classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>PatternEditor</classname></entry>
|
||
|
||
<entry>Capable of resolving Strings to JDK 1.5
|
||
<classname>Pattern</classname> objects and vice versa.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>PropertiesEditor</classname></entry>
|
||
|
||
<entry>Capable of converting Strings (formatted using the
|
||
format as defined in the Javadoc for the java.lang.Properties
|
||
class) to <classname>Properties</classname> objects. Registered by
|
||
default by <classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>StringTrimmerEditor</classname></entry>
|
||
|
||
<entry>Property editor that trims Strings. Optionally allows
|
||
transforming an empty string into a <literal>null</literal> value. NOT
|
||
registered by default; must be user registered as needed.</entry>
|
||
</row>
|
||
|
||
<row>
|
||
<entry><classname>URLEditor</classname></entry>
|
||
|
||
<entry>Capable of resolving a String representation of a URL
|
||
to an actual <classname>URL</classname> object. Registered by
|
||
default by <classname>BeanWrapperImpl</classname>.</entry>
|
||
</row>
|
||
</tbody>
|
||
</tgroup>
|
||
</table>
|
||
|
||
<para>
|
||
Spring uses the <interfacename>java.beans.PropertyEditorManager</interfacename> to set
|
||
the search path for property editors that might be needed. The search path also includes
|
||
<literal>sun.bean.editors</literal>, which includes
|
||
<interfacename>PropertyEditor</interfacename> implementations for types such as
|
||
<classname>Font</classname>, <classname>Color</classname>, and most of the primitive types.
|
||
Note also that the standard JavaBeans infrastructure will automatically discover
|
||
<interfacename>PropertyEditor</interfacename> classes (without you having to register them
|
||
explicitly) if they are in the same package as the class they handle, and have the same name
|
||
as that class, with <literal>'Editor'</literal> appended; for example, one could have the
|
||
following class and package structure, which would be sufficient for the
|
||
<classname>FooEditor</classname> class to be recognized and used as the
|
||
<interfacename>PropertyEditor</interfacename> for <classname>Foo</classname>-typed
|
||
properties.
|
||
</para>
|
||
<programlisting><![CDATA[com
|
||
chank
|
||
pop
|
||
Foo
|
||
FooEditor ]]><lineannotation>// the <interfacename>PropertyEditor</interfacename> for the <classname>Foo</classname> class</lineannotation></programlisting>
|
||
<para>Note that you can also use the standard <interfacename>BeanInfo</interfacename> JavaBeans
|
||
mechanism here as well (described
|
||
<ulink url="http://java.sun.com/docs/books/tutorial/javabeans/customization/index.html">in not-amazing-detail here</ulink>).
|
||
Find below an example of using the <interfacename>BeanInfo</interfacename> mechanism for
|
||
explicitly registering one or more <interfacename>PropertyEditor</interfacename> instances
|
||
with the properties of an associated class.</para>
|
||
<programlisting><![CDATA[com
|
||
chank
|
||
pop
|
||
Foo
|
||
FooBeanInfo ]]><lineannotation>// the <interfacename>BeanInfo</interfacename> for the <classname>Foo</classname> class</lineannotation></programlisting>
|
||
<para>
|
||
Here is the Java source code for the referenced <classname>FooBeanInfo</classname> class. This
|
||
would associate a <classname>CustomNumberEditor</classname> with the <literal>age</literal>
|
||
property of the <classname>Foo</classname> class.
|
||
</para>
|
||
<programlisting language="java"><![CDATA[public class FooBeanInfo extends SimpleBeanInfo {
|
||
|
||
public PropertyDescriptor[] getPropertyDescriptors() {
|
||
try {
|
||
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
|
||
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
|
||
public PropertyEditor createPropertyEditor(Object bean) {
|
||
return numberPE;
|
||
};
|
||
};
|
||
return new PropertyDescriptor[] { ageDescriptor };
|
||
}
|
||
catch (IntrospectionException ex) {
|
||
throw new Error(ex.toString());
|
||
}
|
||
}
|
||
}]]></programlisting>
|
||
|
||
|
||
<section id="beans-beans-conversion-customeditor-registration">
|
||
<title>Registering additional custom <interfacename>PropertyEditors</interfacename></title>
|
||
|
||
<para>When setting bean properties as a string value, a Spring IoC container
|
||
ultimately uses standard JavaBeans <literal>PropertyEditors</literal> to convert these
|
||
Strings to the complex type of the property. Spring pre-registers a number
|
||
of custom <literal>PropertyEditors</literal> (for example, to convert a classname expressed
|
||
as a string into a real <classname>Class</classname> object). Additionally, Java's standard
|
||
JavaBeans <interfacename>PropertyEditor</interfacename> lookup mechanism allows a
|
||
<classname>PropertyEditor</classname> for a class simply to be named appropriately and
|
||
placed in the same package as the class it provides support for, to be found automatically.</para>
|
||
<para>If there is a need to register other custom <literal>PropertyEditors</literal>, there
|
||
are several mechanisms available. The most manual approach, which is not normally convenient or
|
||
recommended, is to simply use the <methodname>registerCustomEditor()</methodname> method of the
|
||
<interfacename>ConfigurableBeanFactory</interfacename> interface, assuming you have a
|
||
<interfacename>BeanFactory</interfacename> reference. Another, slightly more convenient, mechanism is to use
|
||
a special bean factory post-processor called <classname>CustomEditorConfigurer</classname>.
|
||
Although bean factory post-processors can be used with <interfacename>BeanFactory</interfacename>
|
||
implementations, the <classname>CustomEditorConfigurer</classname> has a nested property setup, so it is
|
||
strongly recommended that it is used with the <interfacename>ApplicationContext</interfacename>, where
|
||
it may be deployed in similar fashion to any other bean, and automatically detected and applied.</para>
|
||
<para>Note that all bean factories and application contexts automatically use a number of built-in property
|
||
editors, through their use of something called a <interfacename>BeanWrapper</interfacename> to handle
|
||
property conversions. The standard property editors that the <interfacename>BeanWrapper</interfacename>
|
||
registers are listed in <link linkend="beans-beans-conversion">the previous section</link>. Additionally,
|
||
<literal>ApplicationContexts</literal> also override or add an additional number of editors
|
||
to handle resource lookups in a manner appropriate to the specific application context type.</para>
|
||
|
||
<para>Standard JavaBeans <interfacename>PropertyEditor</interfacename> instances are used to convert
|
||
property values expressed as strings to the actual complex type of the property.
|
||
<classname>CustomEditorConfigurer</classname>, a bean factory post-processor, may be used to conveniently
|
||
add support for additional <interfacename>PropertyEditor</interfacename> instances to an
|
||
<interfacename>ApplicationContext</interfacename>.</para>
|
||
<para>Consider a user class <classname>ExoticType</classname>, and another class
|
||
<classname>DependsOnExoticType</classname> which needs <classname>ExoticType</classname> set as a property:</para>
|
||
|
||
<programlisting language="java"><![CDATA[package example;
|
||
|
||
public class ExoticType {
|
||
|
||
private String name;
|
||
|
||
public ExoticType(String name) {
|
||
this.name = name;
|
||
}
|
||
}
|
||
|
||
public class DependsOnExoticType {
|
||
|
||
private ExoticType type;
|
||
|
||
public void setType(ExoticType type) {
|
||
this.type = type;
|
||
}
|
||
}]]></programlisting>
|
||
<para>When things are properly set up, we want to be able to assign the type property as a string, which a
|
||
<interfacename>PropertyEditor</interfacename> will behind the scenes convert into an actual
|
||
<classname>ExoticType</classname> instance:</para>
|
||
<programlisting language="xml"><![CDATA[<bean id="sample" class="example.DependsOnExoticType">
|
||
<property name="type" value="aNameForExoticType"/>
|
||
</bean>]]></programlisting>
|
||
<para>The <interfacename>PropertyEditor</interfacename> implementation could look similar to this:</para>
|
||
<programlisting language="java"><lineannotation>// converts string representation to <classname>ExoticType</classname> object</lineannotation><![CDATA[
|
||
package example;
|
||
|
||
public class ExoticTypeEditor extends PropertyEditorSupport {
|
||
|
||
private String format;
|
||
|
||
public void setFormat(String format) {
|
||
this.format = format;
|
||
}
|
||
|
||
public void setAsText(String text) {
|
||
if (format != null && format.equals("upperCase")) {
|
||
text = text.toUpperCase();
|
||
}
|
||
ExoticType type = new ExoticType(text);
|
||
setValue(type);
|
||
}
|
||
}]]></programlisting>
|
||
<para>Finally, we use <classname>CustomEditorConfigurer</classname> to register the new
|
||
<interfacename>PropertyEditor</interfacename> with the <interfacename>ApplicationContext</interfacename>,
|
||
which will then be able to use it as needed:</para>
|
||
<programlisting language="xml"><![CDATA[<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||
<property name="customEditors">
|
||
<map>
|
||
<entry key="example.ExoticType">
|
||
<bean class="example.ExoticTypeEditor">
|
||
<property name="format" value="upperCase"/>
|
||
</bean>
|
||
</entry>
|
||
</map>
|
||
</property>
|
||
</bean>]]></programlisting>
|
||
|
||
<section id="beans-beans-conversion-customeditor-registration-per">
|
||
<title>Using <interfacename>PropertyEditorRegistrars</interfacename></title>
|
||
|
||
<para>Another mechanism for registering property editors with the Spring container is to create and use
|
||
a <interfacename>PropertyEditorRegistrar</interfacename>. This interface is particularly useful when you
|
||
need to use the same set of property editors in several different situations: write a corresponding
|
||
registrar and reuse that in each case. <literal>PropertyEditorRegistrars</literal> work in conjunction
|
||
with an interface called <interfacename>PropertyEditorRegistry</interfacename>, an interface
|
||
that is implemented by the Spring <interfacename>BeanWrapper</interfacename> (and
|
||
<interfacename>DataBinder</interfacename>). <literal>PropertyEditorRegistrars</literal> are particularly
|
||
convenient when used in conjunction with the <classname>CustomEditorConfigurer</classname>
|
||
(introduced <link linkend="beans-beans-conversion-customeditor-registration">here</link>), which exposes a
|
||
property called <methodname>setPropertyEditorRegistrars(..)</methodname>:
|
||
<literal>PropertyEditorRegistrars</literal> added to a <classname>CustomEditorConfigurer</classname> in this
|
||
fashion can easily be shared with <interfacename>DataBinder</interfacename> and Spring MVC
|
||
<interfacename>Controllers</interfacename>. Furthermore, it avoids the need for synchronization on custom
|
||
editors: a <interfacename>PropertyEditorRegistrar</interfacename> is expected to create fresh
|
||
<interfacename>PropertyEditor</interfacename> instances for each bean creation attempt.</para>
|
||
<para>Using a <interfacename>PropertyEditorRegistrar</interfacename> is perhaps best illustrated with an
|
||
example. First off, you need to create your own <interfacename>PropertyEditorRegistrar</interfacename>
|
||
implementation:</para>
|
||
|
||
<programlisting language="java"><![CDATA[package com.foo.editors.spring;
|
||
|
||
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
|
||
|
||
public void registerCustomEditors(PropertyEditorRegistry registry) {
|
||
|
||
]]><lineannotation>// it is expected that new <interfacename>PropertyEditor</interfacename> instances are created</lineannotation><![CDATA[
|
||
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
|
||
|
||
]]><lineannotation>// you could register as many custom property editors as are required here...</lineannotation><![CDATA[
|
||
}
|
||
}]]></programlisting>
|
||
<para>See also the <classname>org.springframework.beans.support.ResourceEditorRegistrar</classname> for an
|
||
example <interfacename>PropertyEditorRegistrar</interfacename> implementation. Notice how in its
|
||
implementation of the <methodname>registerCustomEditors(..)</methodname> method it creates new instances
|
||
of each property editor.</para>
|
||
<para>Next we configure a <classname>CustomEditorConfigurer</classname> and inject an
|
||
instance of our <classname>CustomPropertyEditorRegistrar</classname> into it:</para>
|
||
<programlisting language="xml"><![CDATA[<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||
<property name="propertyEditorRegistrars">
|
||
<list>
|
||
<ref bean="customPropertyEditorRegistrar"/>
|
||
</list>
|
||
</property>
|
||
</bean>
|
||
|
||
<bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>]]></programlisting>
|
||
|
||
<para>Finally, and in a bit of a departure from the focus of this chapter, for those of you using
|
||
<link linkend="mvc">Spring's MVC web framework</link>, using <interfacename>PropertyEditorRegistrars</interfacename>
|
||
in conjunction with data-binding <interfacename>Controllers</interfacename> (such as
|
||
<classname>SimpleFormController</classname>) can be very convenient. Find below an example of using a
|
||
<interfacename>PropertyEditorRegistrar</interfacename> in the implementation of an <methodname>initBinder(..)</methodname>
|
||
method:</para>
|
||
|
||
<programlisting language="java"><![CDATA[public final class RegisterUserController extends SimpleFormController {
|
||
|
||
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
|
||
|
||
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
|
||
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
|
||
}
|
||
|
||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
|
||
]]><emphasis role="bold">this.customPropertyEditorRegistrar.registerCustomEditors(binder);</emphasis><![CDATA[
|
||
}
|
||
|
||
]]><lineannotation>// other methods to do with registering a <classname>User</classname></lineannotation><![CDATA[
|
||
}]]></programlisting>
|
||
|
||
<para>This style of <interfacename>PropertyEditor</interfacename> registration can lead to concise code (the
|
||
implementation of <methodname>initBinder(..)</methodname> is just one line long!), and allows common
|
||
<interfacename>PropertyEditor</interfacename> registration code to be encapsulated in a class and then
|
||
shared amongst as many <interfacename>Controllers</interfacename> as needed.</para>
|
||
|
||
</section>
|
||
|
||
</section>
|
||
|
||
</section>
|
||
</section>
|
||
|
||
<section id="core.convert">
|
||
<title>Spring 3 Type Conversion</title>
|
||
<para>
|
||
Spring 3 introduces a <filename>core.convert</filename> package that provides a general type conversion system.
|
||
The system defines an API to implement type conversion logic, as well as an API to execute type conversions at runtime.
|
||
Within a Spring container, if configured, this system can be used as an alternative to PropertyEditors to convert externalized bean property value strings to required property types.
|
||
The public API may also be used anywhere in your application where type conversion is needed.
|
||
</para>
|
||
<section id="core-convert-Converter-API">
|
||
<title>Converter API</title>
|
||
<para>
|
||
The API to implement type conversion logic is simple and strongly typed:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.core.converter;
|
||
|
||
public interface Converter<S, T> {
|
||
|
||
T convert(S source);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
To create your own Converter, simply implement the interface above.
|
||
Parameterize S as the type you are converting from, and T as the type you are converting to.
|
||
For each call to convert(S), the source argument is guaranteed to be NOT null.
|
||
Your Converter may throw any Exception if conversion fails.
|
||
An IllegalArgumentException should be thrown to report an invalid source value.
|
||
Take care to ensure your Converter implementation is thread-safe.
|
||
</para>
|
||
<para>
|
||
Several converter implementations are provided in the <filename>core.convert.converters</filename> package as a convenience.
|
||
These include converters from Strings to Numbers and other common types.
|
||
Consider <classname>StringToInteger</classname> as an example Converter implementation:
|
||
</para>
|
||
<programlisting language="java">package org.springframework.core.convert.converters;
|
||
|
||
public class StringToInteger implements Converter<String, Integer> {
|
||
|
||
public Integer convert(String source) {
|
||
return Integer.valueOf(source);
|
||
}
|
||
|
||
}</programlisting>
|
||
</section>
|
||
<section id="core-convert-ConverterFactory-SPI">
|
||
<title>ConverterFactory</title>
|
||
<para>
|
||
When you need to centralize the conversion logic for an entire class hierarchy, for example, when converting from String to java.lang.Enum objects, implement a <interfacename>ConverterFactory</interfacename>ConverterFactory:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.core.converter;
|
||
|
||
public interface ConverterFactory<S, R> {
|
||
|
||
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
Parameterize S to be type you are converting from and R to be base type defining the <emphasis>range</emphasis> of classes you can convert to.
|
||
Then implement getConverter(Class<T>), where T is a subclass of R.
|
||
</para>
|
||
<para>
|
||
Consider the <classname>StringToEnum</classname> ConverterFactory as an example:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class StringToEnumFactory implements ConverterFactory<String, Enum> {
|
||
|
||
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
|
||
return new StringToEnum(targetType);
|
||
}
|
||
|
||
private final class StringToEnum<T extends Enum> implements Converter<String, T> {
|
||
|
||
private Class<T> enumType;
|
||
|
||
public StringToEnum(Class<T> enumType) {
|
||
this.enumType = enumType;
|
||
}
|
||
|
||
public T convert(String source) {
|
||
return (T) Enum.valueOf(this.enumType, source.trim());
|
||
}
|
||
}
|
||
}]]></programlisting>
|
||
</section>
|
||
<section id="core-convert-ConversionService-API">
|
||
<title>ConversionService API</title>
|
||
<para>
|
||
The ConversionService defines a public API for executing type conversion logic at runtime.
|
||
Converters are often executed behind this facade interface:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public interface ConversionService {
|
||
|
||
boolean canConvert(Class<?> sourceType, Class<?> targetType);
|
||
|
||
<T> T convert(Object source, Class<T> targetType);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
Most ConversionService implementations also implement <interface>ConverterRegistry</interface>, which provides an SPI for registering converters.
|
||
Internally, a ConversionService implementation delegates to its registered Converters and ConverterFactories to carry out type conversion logic.
|
||
</para>
|
||
<para>
|
||
Two ConversionService implementations are provided with the system in the <filename>core.convert.support</filename> package.
|
||
<classname>GenericConversionService</classname> is a generic implementation designed to be explicitly configured, either programatically or declaratively as a Spring bean.
|
||
<classname>DefaultConversionService</classname> is a subclass that pre-registers the common Converters in the <filename>core.converter</filename> package as a convenience.
|
||
</para>
|
||
</section>
|
||
<section id="core-convert-Spring-config">
|
||
<title>Configuring a ConversionService</title>
|
||
<para>
|
||
A ConversionService is a stateless object designed to be instantiated at application startup, then shared between multiple threads.
|
||
In a Spring application, you typically configure a ConversionService instance per Spring container (or ApplicationContext).
|
||
That ConversionService will be picked up by Spring and then used whenever a type conversion needs to be performed by the framework.
|
||
You may also inject this ConversionService into any of your beans and invoke it directly.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
If no ConversionService is registered with Spring, the original PropertyEditor-based system is used.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
To register the DefaultConversionService with Spring, simply configure it as a bean with the id <code>conversionService</code>:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<bean id="conversionService" class="org.springframework.core.convert.support.DefaultConversionService" />]]>
|
||
</programlisting>
|
||
<para>
|
||
To override the default set of converters with your own custom converter(s), set the <code>converters</code> property:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<bean id="conversionService" class="org.springframework.core.convert.support.DefaultConversionService">
|
||
<property name="converters">
|
||
<list>
|
||
<bean class="example.MyCustomConverter" />
|
||
</list>
|
||
</property>
|
||
</bean>]]></programlisting>
|
||
</section>
|
||
<section id="core-convert-programmatic-usage">
|
||
<title>Using a ConversionService programatically</title>
|
||
<para>
|
||
To work with a ConversionService instance programatically, simply inject a reference to it like you would for any other bean:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
@Service
|
||
public class MyService {
|
||
|
||
@Autowired
|
||
public MyService(ConversionService conversionService) {
|
||
this.conversionService = conversionService;
|
||
}
|
||
|
||
public void doIt() {
|
||
this.conversionService.convert(...)
|
||
}
|
||
}]]></programlisting>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="ui.format">
|
||
<title>Spring 3 Field Formatting</title>
|
||
<para>
|
||
<link linkend="core.convert"><filename>core.convert</filename></link> is a simple, general-purpose type conversion system.
|
||
It provides a strongly-typed Converter SPI for implementing <emphasis>one-way</emphasis> conversion logic from one type to another and is not limited to just converting Strings.
|
||
As discussed in the previous section, a Spring Container can be configured to use this system to bind bean property values.
|
||
In addition, both the Spring Expression Language (SpEL) and DataBinder can use this system to coerce values.
|
||
For example, when SpEL needs to coerce a <classname>Short</classname> to a <classname>Long</classname> to complete an <function>expression.setValue(Object bean, Object value)</function> attempt, the core.convert system performs the coercion.
|
||
</para>
|
||
<para>
|
||
Now consider the type conversion requirements of a typical client environment such as a web or desktop application.
|
||
In such environments, you typically convert <emphasis>from String</emphasis> to support the client postback process, as well as back <emphasis>to String</emphasis> to support the rendering process.
|
||
The more general <emphasis>core.convert</emphasis> Converter SPI does not address this specific common scenario directly.
|
||
To directly address this, Spring 3 introduces a conveient <emphasis>format</emphasis> SPI that provides a simple and robust alternative to PropertyEditors in a client environment.
|
||
</para>
|
||
<para>
|
||
In general, use the Converter SPI when you need to implement general-purpose type conversion logic.
|
||
Use Formatters when you're working in a client environment, such as an HTML form of a web application and need to apply String parsing, printing, and localization logic to form field values.
|
||
</para>
|
||
<section id="ui-format-Formatter-SPI">
|
||
<title>Formatter SPI</title>
|
||
<para>
|
||
The <literal>org.springframework.format</literal> SPI to implement field formatting logic is simple and strongly typed:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public interface Printer<T> {
|
||
String print(T fieldValue, Locale locale);
|
||
}]]>
|
||
</programlisting>
|
||
<programlisting language="java"><![CDATA[
|
||
import java.text.ParseException;
|
||
|
||
public interface Parser<T> {
|
||
T parse(String clientValue, Locale locale) throws ParseException;
|
||
}]]>
|
||
</programlisting>
|
||
<programlisting language="java"><![CDATA[
|
||
public interface Formatter<T> extends Printer<T>, Parser<T> {
|
||
}]]>
|
||
</programlisting>
|
||
<para>
|
||
To create your own Formatter, simply implement the Formatter interface above.
|
||
Parameterize T to be the type of object you are formatting, for example, <classname>java.lang.BigDecimal</classname>.
|
||
Implement the <methodname>print</methodname> operation to print an instance of T for display in the client locale.
|
||
Implement the <methodname>parse</methodname> operation to parse an instance of T from the formatted representation returned from the client locale.
|
||
Your Formatter should throw a ParseException or IllegalArgumentException if a parse attempt fails.
|
||
Take care to ensure your Formatter implementation is thread-safe.
|
||
</para>
|
||
<para>
|
||
Several Formatter implementations are provided in <filename>format</filename>subpackages as a convenience.
|
||
The <filename>datetime</filename> package provides a DateFormatter to format java.util.Date objects with a java.text.DateFormat.
|
||
The <filename>number</filename> package provides a DecimalFormatter, IntegerFormatter, CurrencyFormatter, and PercentFormatter to format java.lang.Number objects using a java.text.NumberFormat.
|
||
The <filename>datetime.joda</filename> package provides comprehensive datetime formatting support based on the <ulink url="http://joda-time.sourceforge.net">Joda Time library</ulink>.
|
||
</para>
|
||
<para>
|
||
Consider <classname>DateFormatter</classname> as an example <interfacename>Formatter</interfacename> implementation:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.format.datetime;
|
||
|
||
public final class DateFormatter implements Formatter<Date> {
|
||
|
||
private String pattern;
|
||
|
||
public DateFormatter(String pattern) {
|
||
this.pattern = pattern;
|
||
}
|
||
|
||
public String print(Date date, Locale locale) {
|
||
if (date == null) {
|
||
return "";
|
||
}
|
||
return getDateFormat(locale).format(date);
|
||
}
|
||
|
||
public Date parse(String formatted, Locale locale) throws ParseException {
|
||
if (formatted.length() == 0) {
|
||
return null;
|
||
}
|
||
return getDateFormat(locale).parse(formatted);
|
||
}
|
||
|
||
protected DateFormat getDateFormat(Locale locale) {
|
||
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
|
||
dateFormat.setLenient(false);
|
||
return dateFormat;
|
||
}
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
The Spring team welcomes community-driven Formatter contributions; see <ulink url="http://jira.springframework.org">http://jira.springframework.org</ulink> to contribute.
|
||
</para>
|
||
</section>
|
||
<section id="ui-format-CustomFormatAnnotations">
|
||
<title>Custom Format Annotations</title>
|
||
<para>
|
||
Field formatting can be triggered by annotating model properties.
|
||
To bind a custom annotation to a Formatter instance, implement AnnotationFormatterFactory:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.format;
|
||
|
||
public interface AnnotationFormatterFactory<A extends Annotation> {
|
||
|
||
Set<Class<?>> getFieldTypes();
|
||
|
||
Printer<?> getPrinter(A annotation, Class<?> fieldType);
|
||
|
||
Parser<?> getParser(A annotation, Class<?> fieldType);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
Parameterize A to be the field annotationType you wish to associate formatting logic with, for example <code>org.springframework.format.annotation.DateTimeFormat</code>.
|
||
Implement the <methodname>getFieldTypes</methodname> operation return the types of fields the annotation may be used on.
|
||
Implement the <methodname>getPrinter</methodname> operation to return the Printer to print the value of an annotated field.
|
||
Implement the <methodname>getParser</methodname> operation to return the Parser to parse the value of an annotated field.
|
||
Take care to ensure your AnnotationFormatterFactory implementation is thread-safe.
|
||
</para>
|
||
<para>
|
||
The example implementation below binds a @DecimalFormat instance to a Formatter instance.
|
||
This particular annotation allows the NumberFormat pattern to be configured.
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class DecimalAnnotationFormatterFactory implements AnnotationFormatterFactory<DecimalFormat, Number> {
|
||
|
||
Formatter<Number> getFormatter(DecimalFormat annotation) {
|
||
DecimalFormatter formatter = DecimalFormatter();
|
||
formatter.setPattern(annotation.value());
|
||
return formatter;
|
||
}
|
||
}]]></programlisting>
|
||
<para>
|
||
Then, to trigger formatting, simply annotate a property with @DecimalFormat in your model:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class MyModel {
|
||
|
||
@DecimalFormat("#,###")
|
||
private BigDecimal decimal;
|
||
|
||
}]]></programlisting>
|
||
</section>
|
||
</section>
|
||
<section id="ui-format-FormatterRegistry-SPI">
|
||
<title>FormatterRegistry SPI</title>
|
||
<para>
|
||
Formatters can be registered in a FormatterRegistry.
|
||
A DataBinder uses this registry to resolve the Formatter to use for a specific field.
|
||
This allows you to configure default Formatting rules centrally, rather than duplicating such configuration across your UI Controllers.
|
||
For example, you might want to enforce that all Date fields are formatted a certain way, or fields with a specific annotation are formatted in a certain way.
|
||
With a shared FormatterRegistry, you define these rules once and they are applied whenever formatting is needed.
|
||
</para>
|
||
<para>
|
||
Review the FormatterRegistry SPI below:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.format;
|
||
|
||
public interface FormatterRegistry {
|
||
|
||
void add(Formatter<?> formatter);
|
||
|
||
void add(AnnotationFormatterFactory<?, ?> factory);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
As shown above, Formatters may be registered by field type or annotation.
|
||
<classname>GenericFormatterRegistry</classname> is the implementation suitable for use in most UI binding environments.
|
||
This implementation may be configured programatically or declaratively as a Spring bean.
|
||
</para>
|
||
</section>
|
||
<section id="ui-format-configuring-FormatterRegistry">
|
||
<title>Configuring a FormatterRegistry</title>
|
||
<para>
|
||
A FormatterRegistry is designed to be instantiated at application startup, then shared between multiple threads.
|
||
In a Spring MVC application, you configure a FormatterRegistry as a property of the WebBindingInitializer.
|
||
The FormatterRegistry will then be configured whenever a DataBinder is created by Spring MVC to bind and render model properties.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
If no FormatterRegistry is configured, the original PropertyEditor-based system is used.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
To register a FormatterRegistry with Spring MVC, simply configure it as a property of a custom WebBindingInitializer injected into the
|
||
Spring MVC AnnotationMethodHandlerAdapter:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<!-- Invokes Spring MVC @Controller methods -->
|
||
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
|
||
<property name="webBindingInitializer">
|
||
<!-- Configures Spring MVC DataBinder instances -->
|
||
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
|
||
<property name="formatterRegistry">
|
||
<bean class="org.springframework.format.support.GenericFormatterRegistry">
|
||
<property name="formatters">
|
||
<list>
|
||
<!-- Register Formatter beans here -->
|
||
</list>
|
||
</property>
|
||
<property name="annotationFormatterFactories">
|
||
<list>
|
||
<!-- Register any AnnotationFormatterFactory beans here -->
|
||
</list>
|
||
</property>
|
||
</bean>
|
||
</property>
|
||
</bean>
|
||
</property>
|
||
</bean>]]>
|
||
</programlisting>
|
||
<para>
|
||
When using the @Formatted annotation, no explicit Formatter or AnnotationFormatterFactory registration is required.
|
||
See the JavaDocs for GenericFormatterRegistry for more configuration options.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
<section id="validation.beanvalidation">
|
||
<title>Spring 3 Validation</title>
|
||
<para>
|
||
Spring 3 introduces several enhancements to its validation support.
|
||
First, the JSR-303 Bean Validation API is now fully supported.
|
||
Second, when used programatically, Spring's DataBinder can now validate objects as well as bind to them.
|
||
Third, Spring MVC now has support for declaratively validating @Controller inputs.
|
||
</para>
|
||
<section id="validation.beanvalidation.overview">
|
||
<title>Overview of the JSR-303 Bean Validation API</title>
|
||
<para>
|
||
JSR-303 standardizes validation constraint declaration and metadata for the Java platform.
|
||
Using this API, you annotate domain model properties with declarative validation constraints and the runtime enforces them.
|
||
There are a number of built-in constraints you can can take advantage of.
|
||
You may also define your own custom constraints.
|
||
</para>
|
||
<para>
|
||
To illustrate, consider a simple Person model with two properties:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class Person {
|
||
private String name;
|
||
private int age;
|
||
}]]></programlisting>
|
||
<para>
|
||
JSR-303 allows you to define declarative validation constraints against such properties:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class Person {
|
||
|
||
@NotNull
|
||
@Max(64)
|
||
private String name;
|
||
|
||
@Min(0)
|
||
private int age;
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
When an instance of this class is validated by a JSR-303 Validator, these constraints will be enforced.
|
||
</para>
|
||
<para>
|
||
For general information on JSR-303, see the <ulink url="http://jcp.org/en/jsr/detail?id=303">Bean Validation Specification</ulink>.
|
||
For information on the specific capabilities of the default reference implementation, see the <ulink url="https://www.hibernate.org/412.html">Hibernate Validator</ulink> documentation.
|
||
To learn how to setup a JSR-303 implementation as a Spring bean, keep reading.
|
||
</para>
|
||
</section>
|
||
<section id="validation.beanvalidation.spring">
|
||
<title>Configuring a Bean Validation Implementation</title>
|
||
<para>
|
||
Spring provides full support for the JSR-303 Bean Validation API.
|
||
This includes convenient support for bootstrapping a JSR-303 implementation as a Spring bean.
|
||
This allows a <code>javax.validation.Validator</code> to be injected wherever validation is needed in your application.
|
||
</para>
|
||
<para>
|
||
Use the <classname>LocalValidatorFactoryBean</classname> to configure a default JSR-303 Validator as a Spring bean:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />]]>
|
||
</programlisting>
|
||
<para>
|
||
The basic configuration above will trigger JSR-303 to initialize using its default bootstrap mechanism.
|
||
A JSR-303 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically.
|
||
</para>
|
||
|
||
<tip>
|
||
<title>Using LocalValidatorFactoryBean programmatically</title>
|
||
<para>If you choose to use <classname>LocalValidatorFactoryBean</classname>
|
||
programmatically – i.e., by instantiating it directly – make sure
|
||
you call its <literal>afterPropertiesSet()</literal> method. Otherwise, the
|
||
<classname>LocalValidatorFactoryBean</classname> will not be
|
||
initialized properly.</para>
|
||
</tip>
|
||
|
||
<section id="validation.beanvalidation.spring.inject">
|
||
<title>Injecting a Validator</title>
|
||
<para>
|
||
<classname>LocalValidatorFactoryBean</classname> implements both <code>javax.validation.Validator</code> and <code>org.springframework.validation.Validator</code>.
|
||
You may inject a reference to one of these two interfaces into beans that need to invoke validation logic.
|
||
</para>
|
||
<para>
|
||
Inject a reference to <code>javax.validation.Validator</code> if you prefer to work with the JSR-303 API directly:
|
||
</para>
|
||
<programlisting language="java">import javax.validation.Validator;
|
||
|
||
@Service
|
||
public class MyService {
|
||
|
||
@Autowired
|
||
private Validator validator;
|
||
</programlisting>
|
||
<para>
|
||
Inject a reference to <code>org.springframework.validation.Validator</code> if your bean requires the Spring Validation API:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
import org.springframework.validation.Validator;
|
||
|
||
@Service
|
||
public class MyService {
|
||
|
||
@Autowired
|
||
private Validator validator;
|
||
|
||
}]]></programlisting>
|
||
</section>
|
||
<section id="validation.beanvalidation.spring.constraints">
|
||
<title>Configuring Custom Constraints</title>
|
||
<para>
|
||
Each JSR-303 validation constraint consists of two parts.
|
||
First, a @Constraint annotation that declares the constraint and its configurable properties.
|
||
Second, an implementation of the <code>javax.validation.ConstraintValidator</code> interface that implements the constraint's behavior.
|
||
To associate a declaration with an implementation, each @Constraint annotation references a corresponding ValidationConstraint implementation class.
|
||
At runtime, a <code>ConstraintValidatorFactory</code> instantiates the referenced implementation when the constraint annotation is encountered in your domain model.
|
||
</para>
|
||
<para>
|
||
By default, the <classname>LocalValidatorFactoryBean</classname> configures a <code>SpringConstraintValidatorFactory</code> that uses Spring to create ConstraintValidator instances.
|
||
This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.
|
||
</para>
|
||
<para>
|
||
Shown below is an example of a custom @Constraint declaration, followed by an associated <code>ConstraintValidator</code> implementation that uses Spring for dependency injection:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||
@Retention(RetentionPolicy.RUNTIME)
|
||
@Constraint(validatedBy=MyConstraintValidator.class)
|
||
public @interface MyConstraint {
|
||
}]]></programlisting>
|
||
<programlisting language="java"><![CDATA[
|
||
import javax.validation.ConstraintValidator;
|
||
|
||
public class MyConstraintValidator implements ConstraintValidator {
|
||
|
||
@Autowired;
|
||
private Foo aDependency;
|
||
|
||
...
|
||
}]]></programlisting>
|
||
<para>
|
||
As you can see, a ConstraintValidator implementation may have its dependencies @Autowired like any other Spring bean.
|
||
</para>
|
||
</section>
|
||
<section id="validation.beanvalidation.spring.other">
|
||
<title>Additional Configuration Options</title>
|
||
<para>
|
||
The default <classname>LocalValidatorFactoryBean</classname> configuration should prove sufficient for most cases.
|
||
There are a number of other configuration options for various JSR-303 constructs, from message interpolation to traversal resolution.
|
||
See the JavaDocs of <classname>LocalValidatorFactoryBean</classname> more information on these options.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
<section id="validation.binder">
|
||
<title>Configuring a DataBinder</title>
|
||
<para>
|
||
Since Spring 3, a DataBinder instance can be configured with a Validator.
|
||
Once configured, the Validator may be invoked by calling <code>binder.validate()</code>.
|
||
Any validation Errors are automatically added to the binder's BindingResult.
|
||
</para>
|
||
<para>
|
||
When working with the DataBinder programatically, this can be used to invoke validation logic after binding to a target object:
|
||
</para>
|
||
<programlisting language="java">Foo target = new Foo();
|
||
DataBinder binder = new DataBinder(target);
|
||
binder.setValidator(new FooValidator());
|
||
|
||
<lineannotation>// bind to the target object</lineannotation>
|
||
binder.bind(propertyValues);
|
||
|
||
<lineannotation>// validate the target object</lineannotation>
|
||
binder.validate();
|
||
|
||
<lineannotation>// get BindingResult that includes any validation errors</lineannotation>
|
||
BindingResult results = binder.getBindingResult();
|
||
</programlisting>
|
||
</section>
|
||
<section id="validation.mvc">
|
||
<title>Spring MVC 3 Validation</title>
|
||
<para>
|
||
Beginning with Spring 3, Spring MVC has the ability to automatically validate @Controller inputs.
|
||
In previous versions it was up to the developer to manually invoke validation logic.
|
||
</para>
|
||
<section id="validation.mvc.triggering">
|
||
<title>Triggering @Controller Input Validation</title>
|
||
<para>
|
||
To trigger validation of a @Controller input, simply annotate the input argument as @Valid:
|
||
</para>
|
||
<programlisting language="java">@Controller
|
||
public class MyController {
|
||
|
||
@RequestMapping("/foo", method=RequestMethod.POST)
|
||
public void processFoo(<emphasis role="bold">@Valid</emphasis> Foo foo) { <lineannotation>/* ... */</lineannotation> }
|
||
</programlisting>
|
||
<para>
|
||
Spring MVC will validate a @Valid object after binding so-long as an appropriate Validator has been configured.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
The @Valid annotation is part of the standard JSR-303 Bean Validation API, and is not a Spring-specific construct.
|
||
</para>
|
||
</note>
|
||
</section>
|
||
<section id="validation.mvc.configuring">
|
||
<title>Configuring a Validator for use by Spring MVC</title>
|
||
<para>
|
||
The Validator instance invoked when a @Valid method argument is encountered may be configured in two ways.
|
||
First, you may call binder.setValidator(Validator) within a @Controller's @InitBinder callback.
|
||
This allows you to configure a Validator instance per @Controller class:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
@Controller
|
||
public class MyController {
|
||
|
||
@InitBinder
|
||
protected void initBinder(WebDataBinder binder) {
|
||
binder.setValidator(new FooValidator());
|
||
}
|
||
|
||
@RequestMapping("/foo", method=RequestMethod.POST)
|
||
public void processFoo(@Valid Foo foo) { ... }
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
Second, you may call setValidator(Validator) on the global WebBindingInitializer.
|
||
This allows you to configure a Validator instance across all @Controllers:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<!-- Invokes Spring MVC @Controller methods -->
|
||
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
|
||
<property name="webBindingInitializer">
|
||
<!-- Configures Spring MVC DataBinder instances -->
|
||
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
|
||
<property name="validator" ref="validator" />
|
||
</bean>
|
||
</property>
|
||
</bean>]]></programlisting>
|
||
</section>
|
||
<section id="validation.mvc.jsr303">
|
||
<title>Configuring a JSR-303 Validator for use by Spring MVC</title>
|
||
<para>
|
||
With JSR-303, the default <code>javax.validation.Validator</code> implementation is generic.
|
||
A single instance typically coordinates the validation of <emphasis>all</emphasis> application objects that declare validation constraints.
|
||
To configure such a general purpose Validator for use by Spring MVC, simply inject a <classname>LocalValidatorFactoryBean</classname> reference into the <code>WebBindingInitializer</code>.
|
||
</para>
|
||
<para>
|
||
A full configuration example showing injection of a JSR-303 backed Validator into Spring MVC is shown below:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[
|
||
<!-- Invokes Spring MVC @Controller methods -->
|
||
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
|
||
<property name="webBindingInitializer">
|
||
<!-- Configures Spring MVC DataBinder instances -->
|
||
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
|
||
<property name="validator" ref="validator" />
|
||
</bean>
|
||
</property>
|
||
</bean>
|
||
|
||
<!-- Creates the JSR-303 Validator -->
|
||
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />]]></programlisting>
|
||
<para>
|
||
With this configuration, anytime a @Valid @Controller input is encountered, it will be validated by the JSR-303 provider.
|
||
JSR-303, in turn, will enforce any constraints declared against the input.
|
||
Any ConstaintViolations will automatically be exposed as errors in the BindingResult renderable by standard Spring MVC form tags.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section id="org.springframework.mapping">
|
||
<title>Spring 3 Object Mapping</title>
|
||
<para>
|
||
There are scenarios, particularly in large message-oriented business applications, where object transformation is required.
|
||
For example, consider a complex Web Service where there is a separation between the data exchange model and the internal domain model used to structure business logic.
|
||
In cases like this, a general-purpose object-to-object mapping facility can be useful for automating the mapping between these disparate models.
|
||
Spring 3 introduces such a facility built on the <link linkend="expressions-intro">Spring Expression Language</link> (SpEL).
|
||
This facility is described in this section.
|
||
</para>
|
||
<section id="mapping-Mapping-API">
|
||
<title>Mapper API</title>
|
||
<para>
|
||
The API to implement object mapping logic is simple and strongly typed:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
package org.springframework.mapping;
|
||
|
||
public interface Mapper<S, T> {
|
||
|
||
T map(S source, T target);
|
||
|
||
}]]></programlisting>
|
||
<para>
|
||
To create your own Mapper, simply implement the interface above.
|
||
Parameterize S as the type you are mapping from, and T as the type you are mapping to.
|
||
The source and target arguments provided to you should never be null.
|
||
Your Mapper may throw any RuntimeException if mapping fails.
|
||
Take care to ensure your Mapper implementation is thread-safe.
|
||
</para>
|
||
<para>
|
||
Consider the following hand-coded Mapper example:
|
||
</para>
|
||
<programlisting language="java">
|
||
public class PersonDtoPersonMapper implements Mapper<PersonDto, Person> {
|
||
|
||
public Person map(PersonDto source, Person target) {
|
||
String[] names = source.getName().split(" ");
|
||
target.setFirstName(names[0]);
|
||
target.setLastName(names[1]);
|
||
return target;
|
||
}
|
||
|
||
}</programlisting>
|
||
<para>
|
||
In this trivial example, the Mapper maps the PersonDto's <literal>name</literal> property to the Person's <literal>firstName</literal> and <literal>lastName</literal> properties.
|
||
The fully mapped Person object is returned.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper">
|
||
<title>General Purpose Object Mapper Implementation</title>
|
||
<para>
|
||
A general purpose object-to-object mapping system exists in the <classname>org.springframework.mapping.support</classname> package.
|
||
Built on the Spring Expression Language (SpEL), this system is capable of mapping between a variety of object types, including JavaBeans, Arrays, Collections, and Maps.
|
||
It can perform field-to-field, field-to-multi-field, multi-field-to-field, and conditional mappings.
|
||
It also can carry out type conversion and recursive mapping, which are often required with rich object models.
|
||
</para>
|
||
<section id="mapping.SpelMapper-usage">
|
||
<title>Usage</title>
|
||
<para>
|
||
To obtain a general purpose object Mapper with its default configuration, simply call <methodname>MappingFactory.getDefaultMapper()</methodname>.
|
||
Then invoke the Mapper by calling its <literal>map(Object, Object)</literal> operation:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
MappingFactory.defaultMapper().map(aSource, aTarget);]]>
|
||
</programlisting>
|
||
<para>
|
||
By default, the defaultMapper will map the fields on the source and target that have the same names.
|
||
If the field types differ, the mapping system will attempt a type conversion using Spring 3's <link linkend="core.convert">type conversion system</link>.
|
||
Nested bean properties are mapped recursively.
|
||
Any mapping failures will trigger a MappingException to be thrown.
|
||
If there are multiple failures, they will be collected and returned in the MappingException thrown to the caller.
|
||
</para>
|
||
<para>
|
||
To illustrate this default behavior, consider the following source object type:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class CreateAccountDto {
|
||
private String number;
|
||
private String name;
|
||
private AddressDto address;
|
||
|
||
public static class AddressDto {
|
||
private String street;
|
||
private String zip;
|
||
}
|
||
}]]></programlisting>
|
||
<para>
|
||
And the following target object type:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public class Account {
|
||
private Long number;
|
||
private String name;
|
||
private Address address;
|
||
|
||
public static class Address {
|
||
private String street;
|
||
private String city
|
||
private String state;
|
||
private String zip;
|
||
}
|
||
}]]></programlisting>
|
||
<para>
|
||
Now mapped in the following service method:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
public void createAccount(CreateAccountDto dto) {
|
||
Account account = (Account) MapperFactory.getDefaultMapper().map(dto, new Account());
|
||
// work with the mapped account instance
|
||
}]]>
|
||
</programlisting>
|
||
<para>
|
||
In this example, the <literal>number</literal>, <literal>name</literal>, and <literal>address</literal> properties are automatically mapped since they are present on both the source and target objects.
|
||
The AccountDto's <literal>address</literal> property is a JavaBean, so its nested properties are also recursively mapped.
|
||
Recursively, the <literal>street</literal> and <literal>zip</literal> properties are automatically mapped since they are both present on the nested AddressDto and Address objects.
|
||
Nothing is mapped to the Address's <literal>city</literal> and <literal>state</literal> properties since these properties do not exist on the AddressDto source.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit">
|
||
<title>Registering Explicit Mappings</title>
|
||
<para>
|
||
When default mapping rules are not sufficient, explicit mapping rules can be registered by obtaining a <classname>MapperBuilder</classname> and using it to construct a <classname>Mapper</classname>.
|
||
Explicit mapping rules always override the default rule.
|
||
The MapperBuilder provides a fluent API for registering object-to-object Mapping rules:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
Mapper<PersonDto, Person> mapper =
|
||
MappingFactory.mappingBuilder(PersonDto.class, Person.class)
|
||
.addMapping(...)
|
||
.addMapping(...)
|
||
.getMapper();
|
||
]]>
|
||
</programlisting>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit-differentFieldNames">
|
||
<title>Mapping between two fields with different names</title>
|
||
<para>
|
||
Suppose you need to map <literal>AccountDto.name</literal> to <literal>Account.fullName</literal>.
|
||
Since these two field names are not the same, the default auto-mapping rule would not apply.
|
||
Handle a requirement like this by explicitly registering a mapping rule:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addMapping("name", "fullName")]]>
|
||
</programlisting>
|
||
<para>
|
||
In the example above, the <literal>name</literal> field will be mapped to the <literal>fullName</literal> field when the mapper is executed.
|
||
No default mapping will be performed for <literal>name</literal> since an explicit mapping rule has been configured for this field.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit-singleFieldToMultipleField">
|
||
<title>Mapping a single field to multiple fields</title>
|
||
<para>
|
||
Suppose you need to map <literal>PersonDto.name</literal> to <literal>Person.firstName</literal> and <literal>Person.lastName</literal>.
|
||
Handle a field-to-multi-field requirement like this by explicitly registering a mapping rule:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addMapping("name", new Mapper<String, Person>() {
|
||
public Person map(String name, Person person) {
|
||
String[] names = name.split(" ");
|
||
person.setFirstName(names[0]);
|
||
person.setLastName(names[1]);
|
||
return person;
|
||
}
|
||
});]]>
|
||
</programlisting>
|
||
<para>
|
||
In the example above, the first part of the <literal>name</literal> field will be mapped to the <literal>firstName</literal> field and the second part will be mapped to the <literal>lastName</literal> field.
|
||
No default mapping will be performed for <literal>name</literal> since an explicit mapping rule has been configured for this field.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit-multipleFieldsToField">
|
||
<title>Mapping multiple fields to a single field</title>
|
||
<para>
|
||
Suppose you need to map <literal>CreateAccountDto.activationDay</literal> and <literal>CreateAccountDto.activationTime</literal> to <literal>Account.activationDateTime</literal>.
|
||
Handle a multi-field-to-field requirement like this by explicitly registering a mapping rule:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addMapping(new String[] { "activationDay", "activationTime" }, new Mapper<CreateAccountDto, AccountDto>() {
|
||
public Account map(CreateAccountDto dto, Account account) {
|
||
DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime(
|
||
dto.getActivationDay() + "T" + dto.getActivationTime());
|
||
account.setActivationDateTime(dateTime);
|
||
return account;
|
||
}
|
||
});]]>
|
||
</programlisting>
|
||
<para>
|
||
In the example above, the <literal>activationDay</literal> and <literal>activationTime</literal> fields are mapped to the single <literal>activationDateTime</literal> field.
|
||
No default mapping is performed for <literal>activationDay</literal> or <literal>activationTime</literal> since an explicit mapping rule has been configured for these fields.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit-conditionalMappings">
|
||
<title>Mapping conditionally</title>
|
||
<para>
|
||
Suppose you need to map <literal>Map.countryCode</literal> to <literal>PhoneNumber.countryCode</literal> only if the source Map contains a international phone number.
|
||
Handle conditional mapping requirements like this by explicitly registering a mapping rule:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addConditionalMapping("countryCode", "international == 'true'");]]>
|
||
</programlisting>
|
||
<para>
|
||
In the example above, the <literal>countryCode</literal> field will only be mapped if the international field is 'true'.
|
||
<literal>international == 'true'</literal> is a boolean expression that must evaluate to true for the mapping to be executed.
|
||
No default mapping is performed for <literal>countryCode</literal> since an explicit mapping rule has been configured for this field.
|
||
</para>
|
||
</section>
|
||
<section id="mapping.SpelMapper-Explicit-forcing">
|
||
<title>Forcing Explicit Mappings</title>
|
||
<para>
|
||
You can force that <emphasis>all</emphasis> mapping rules be explicitly defined by disabling the "auto mapping" feature:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.setAutoMappingEnabled(false);]]>
|
||
</programlisting>
|
||
</section>
|
||
<section id="mapping.SpelMapper-CustomConverter">
|
||
<title>Registering Custom Mapping Converters</title>
|
||
<para>
|
||
Sometimes you need to apply field specific type conversion or data transformation logic when mapping a value.
|
||
Do this by registering a converter with a Mapping:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addMapping("name", "fullName").setConverter() { new Converter<String, String>() {
|
||
public String convert(String value) {
|
||
// do transformation
|
||
// return transformed value
|
||
}
|
||
});]]>
|
||
</programlisting>
|
||
</section>
|
||
<section id="mapper.SpelMapper-IgnoringFields">
|
||
<title>Ignoring Fields</title>
|
||
<para>
|
||
Sometimes you need to exclude a specific field on a source object from being mapped.
|
||
Do this by marking one or more source fields as excluded:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.setExcludedFields("name");]]>
|
||
</programlisting>
|
||
</section>
|
||
<section id="mapper.SpelMapper-CustomTypeConverters">
|
||
<title>Registering Custom Type Converters</title>
|
||
<para>
|
||
You may also register custom Converters to convert values between mapped fields of different types:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addConverter(new Converter<String, Date>() {
|
||
public Date convert(String value) {
|
||
// do conversion
|
||
// return transformed value
|
||
}
|
||
});]]>
|
||
</programlisting>
|
||
<para>
|
||
The example Converter above will be invoked anytime a String field is mapped to a Date field.
|
||
</para>
|
||
</section>
|
||
<section id="mapper.SpelMapper-CustomNestedMappers">
|
||
<title>Registering Custom Nested Mappers</title>
|
||
<para>
|
||
When mapping between two object graphs, you may find you need to register explicit mapping rules for nested bean properties.
|
||
Do this by adding a nested Mapper:
|
||
</para>
|
||
<programlisting language="java"><![CDATA[
|
||
builder.addNestedMapper(new Mapper<AddressDto, Address>() {
|
||
public Address map(AddressDto source, Address target) {
|
||
// do target bean mapping here
|
||
return target;
|
||
}
|
||
});]]>
|
||
</programlisting>
|
||
<para>
|
||
The example Mapper above will map nested AddressDto properties to nested Address properties.
|
||
This particular nested Mapper is "hand-coded", but it could have easily been another Mapper instance built by a MapperBuilder.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
<section id="org.springframework.mapping-FurtherReading">
|
||
<title>Further Reading</title>
|
||
<para>
|
||
Consult the JavaDocs of <classname>MapperFactory</classname> and <classname>MapperBuilder</classname> in the <filename>org.springframework.mapping.support</filename> package for more information on the available configuration options.
|
||
</para>
|
||
<para>
|
||
Dozer is another general-purpose object mapper available in the open source Java community.
|
||
Check it out at <ulink url="http://dozer.sourceforge.net">dozer.sourceforge.net</ulink>.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
</chapter> |