Deferring object mapper until Spring 3.1 when it will be used by other projects

master
Keith Donald 15 years ago
parent 3cdb942cbe
commit b5cda8db7b
  1. 288
      spring-framework-reference/src/validation.xml

@ -1454,292 +1454,4 @@ public class MyController {
</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&lt;PersonDto, Person&gt; {
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>
Loading…
Cancel
Save