Deferring object mapper until Spring 3.1 when it will be used by other projects
This commit is contained in:
parent
3cdb942cbe
commit
b5cda8db7b
|
|
@ -1454,292 +1454,4 @@ public class MyController {
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</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>
|
</chapter>
|
||||||
Loading…
Reference in New Issue