它允许你对象之间的转换通过一个对象的属性值赋值到另外一个对象中,操作使用Java的内部机制,代替传统的XML或者诸如此类的配置。它使用代码生成来创建映射器,并且它的核心类有一个有趣的策略,它允许您优化性能。
它是完全可配置的,例如,默认情况下,java代码生成器是Javassist。但是如果你使用EclipseJdt甚至是使用其他的代码生成器提供者实现适当的接口。最后,这是一个非常有文档的项目,有清晰的解释和有用的代码
我是真的喜欢它,如果你也想尝试的去用它,那就在你的maven项目下面添加下面的依赖吧!
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.5</version><!-- or latest version -->
</dependency>
或者你可以单独下载下面三个必须的资源库
javassist (v 3.12.0+)
slf4j (v 1.5.6+)
paranamer (v 2.0+)
核心概念:
这里面有两个核心class文件,第一个是MapperFactory,这个类将配置映射并获取第二个核心类:MapoerFacade,这实际上提供了Java bean映射的服务。
好了现在来了解一下MapperFactory:
通过使用Orika提供的API,你可以构建一个以MapperFactory为主要实现的通道,然后配合DefaultMapperFactory和一些其他的静态类助手用户构建MapperFactory,然后你可以从MapperFactory中获取ClassMapBuilder以便为了流式申明映射配置,计字段映射(默认是双向的,如果你愿意,可以设置成单项的),不包括字段,指定构造函数和自定义列表、集合、嵌套字段等的映射。
有一个默认映射,就是映射两个类之间名称相同的字段,当然你可以在MapperFactory注册自定义映射规则(ClassMapBuild 就是MapperFactory中通过属性来构建的),这两个操作都有合适分方法来调用它们。
不说废话,让我们来看一个简单的例子:
下面我们有两个类:PersonDTO和Person,我们要把前一个对象转换成后一个对象,让我们来配置MapperFactory
OrikaTest.java
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(PersonDTO.class,Person.class)//A ClassMapBuilder<PersonDTO, Person>
.field("name","firstName")//Register field mappings
.field("age","age")
.byDefault() //两个类上的其余字段都应该按名称映射到字段。
.register();//用MapperFactory注册映射
对于扩展字段,ClassMapBuilder提供了非常有趣的映射,映射字段是单向的(我上面提到的,默认映射是双向的)等等。可以看Declarative Mapping Configuration and Advanced Mapping Configuration获取更多的选项。
MapperFacade and BoundMapperFacade讲解:
MapperFacade执行实际的映射工作,正因如此,你必须从MapperFactory获取MapperFacade和使用它的map方法。
这里的map有两种通用的选项:
第一种是map(ObjectA,B.class):将会生成一个新的实例B,然后把ObjectA中属性的值赋值给B实例的相应属性
第二种是map(ObjectA,ObjectB):它将objectA的属性复制到objectB,两者都不是null。
让我们看一个例子:
OrikaTest.java
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDTO personDTO = new PersonDTO();
//在这里你可以设置personDTO对象里面的属性值
Person person = mapper.map(personDTO, Person.class);//有返回值
//修改一下原来的逻辑,先创建一个对象Person:
//Person person = new Person();
//mapper.map(personDTO, person);//没有返回值,直接赋值到传过来的person
BoundMapperFacade旨在与一对类型进行映射,而不需要进行进一步的定制。它为使用标准的MapperFacade提供了更好的性能。它有相同的用法,加上一个反向映射的mapReverse方法。
看看代码层面的不同:
OrikaTest.java
BoundMapperFacade<PersonDTO, Person> boundMapper = mapperFactory.getMapperFacade(PersonDTO.class, Person.class);
PersonDTO personDTO = new PersonDTO();
//在这里设置属性值
Person person = boundMapper.map(personDTO);
//第二种方式:先创建对象
//Person person = new Person();
//boundMapper.map(personDTO, person);
//增强的方法
PersonDTO personDTO = boundMapper.mapReverse(person);
//当然也可以没有返回对象直接保存到已创建的对象中
//boundMapper.mapReverse(person, personDTO); //返回为void
方式的选择:如果你主要关注特定类型的映射到另一个地方,和你的对象图没有周期,使用BoundMapperFacade因为它有更好的性能,当你调用映射方法,你可以避免开销查找一个合适的映射策略对象映射的字段。
性能分析:最耗时的操作是MapperFactory的实例化和初始化,以及从它获得的MapperFacade。它们都是线程安全的对象,因此单例或静态访问是非常推荐的方法。例如,您可以考虑从MapperFactory静态获取
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
public class BaseMapper {
final static MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
}
然后使用这个类来注册类映射,并通过自定义方式访问MapperFacade:
public enum CustomerMapper {
INSTANCE;
private final MapperFacade mapperFacade;
private CustomerMapper() {
BaseMapper.MAPPER_FACTORY.classMap(CustomerDTO.class, Customer.class)
.byDefault()
.register();
mapperFacade = BaseMapper.MAPPER_FACTORY.getMapperFacade();
}
public Customer map(CustomerDTO customerDTO) {
return this.mapperFacade.map(customerDTO, Customer.class);
}
public CustomerDTO map(Customer customer) {
return this.mapperFacade.map(customer, CustomerDTO.class);
}
}
在这种情况下,我使用两个新类:Customer和CustomerDTO。最后,使用这个助手类:
CustomerDTO customerDTO = new CustomerDTO();
//设置属性值
Customer customer = CustomerMapper.INSTANCE.map(customerDTO);
那么它是如何工作和定制的?
了解Orika如何工作的最好方法是阅读FAQ,但是源代码非常清晰易懂,以便于您想深入了解细节
“Orika uses reflection to investigate the metadata of the objects to be mapped and then generates byte-code at runtime which is used to map those classes […]”
“Orika mapping is configured via Java code at runtime, via a fluent-style ClassMapBuilder API. An instance is obtained from a MapperFactory which is then used to describe the mapping from one type to another. Finally, the specified ClassMap is registered with the MapperFactory for later use in generating a byte-code mapper.”
“了解Orika如何工作的最好方法是阅读FAQ,但是源代码非常清晰易懂,以防您想深入了解细节...”
“Orika映射是在运行时通过Java代码配置的,通过一个流式的classapbuilder API进行配置。一个实例是从一个MapperFactory获得的,它被用来描述从一个类型到另一个类型的映射,最后,指定的ClassMap在MapperFactory注册,以便以后用于生成字节码映射器。”
为了配置Orika,作者建议扩展ConfigurableMapper类,我将使用这个方法来使用Orika结合Spring(参见下面)
如果您想配置代码生成、编译等等,请参见下一个主题中使用MapperFactory的方法。
MapperFactory配置详解
核心类MapperFactory的实例化正如你上面看到的那样,通过构建提供的实现:DefaultMapperFactory。它使用一组策略和工厂对象来解决构造函数,编译代码(实际上,它是通过内部静态类MapperFactoryBuild和继承这个静态类的另外一个静态类Builder去实例化MapperFactory,用于解决属性,特别是构建映射。
因此,您可以在调用Builder()和build()方法之前定制它们。
new DefaultMapperFactory.Builder()
.constructorResolverStrategy(new SimpleConstructorResolverStrategy()) //默认情况,您可以扩展它或实现自己的构造解决方案策略。
.compilerStrategy(new JavassistCompilerStrategy()) //默认情况,您可以扩展它,实现自己的编译策略或者是使用EclipseJdtCompilerStrategy
.propertyResolverStrategy(new IntrospectorPropertyResolver()) //默认情况,您可以扩展它或实现自己的属性扩展策略
.classMapBuilderFactory(new ClassMapBuilder.Factory())
.useAutoMapping(true) //默认启用
.mapNulls(true) //默认启用
.build();
自定义Mappers
一个Mapper将一个对象的属性复制到另一个对象上,它由Orika (MapperFacade、MapperGenerator和MapperFactory使用它)内部使用,但实际上对这篇文章来说并不是很有趣。
如果您想要控制如何将一个对象实例的属性复制到另一个对象实例(not null),那么只需扩展抽象类CustomMapper。
然后,您可以通过它的方法registerMapper(mapper mapper)在MapperFactory注册自定义映射器。
当通过classapbuilder API配置的映射行为不适合您的目的时,它将被用于使用,但仅用于复制属性,即目标对象已经存在。如果您想要控制实例化,请使用两个返回实例的CustomConverter或ObjectFactory。见下文。
如果您需要映射给定对象的嵌套成员,那么可以使用当前的MapperFacade,通过受保护的MapperFacade变量访问它。我在Spring应用程序中使用了转换器作为组件,可以通过扩展ConfigurableMapper来对MapperFactory进行扫描和配置,如下所示。
Object Factories
一个ObjectFactory < D >实例化对象。您可以通过他们的方法registerObjectFactory实现这个接口并在MapperFactory注册实现(这里有几个)我从来没有用这个选项
Custom Converters
Converters<S,D>将ObjectFactory和Mapper结合在一起,返回目标类型的新实例,并从源实例复制所有属性。当您想要完全控制实例化和映射时,只需扩展CustomConverter<S,D>来创建新实例,并从源对象提供属性。
我发现这个选项很有用,当你想在你的代码中有相同含义的类之间创建一些硬的转换,但是在Java中并没有真正的相关。嗯,它是一个转换,比如说,从一个字符串中表示的日期,一个必须是日历的日期。您必须使用一些格式化程序来这样做。
如果需要,也很容易将转换器配置为Spring组件。
使用Orika结合Spring
由于Orika核心类MapperFactory和从它获得的MapperFacade是非常昂贵的实例化和初始化,但是同时是线程安全的,它们应该被共享为单例。也就是说,它们是具有singleton范围的Spring bean的优秀候选对象。
让我们从在maven项目中使用spring框架开始,只需要在pom.xml文件中添加新的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.1.RELEASE</version><!-- or latest version -->
</dependency>
spring的应用程序,让我们创建几个bean来获得一个Person和一个PersonDTO,让我们分别调用它们service和repository
package com.malsolo.orika.test.spring;
import com.malsolo.orika.test.domain.Person;
public interface PersonService {
public Person obtainPerson();
}
package com.malsolo.orika.test.spring;
import com.malsolo.orika.test.dto.PersonDTO;
public interface PersonRepository {
public PersonDTO obtainPerson();
}
而他们的实现,service将使用Repository接口,而存储库的实现将是一个简单的内存实现
package com.malsolo.orika.test.spring;
import com.google.common.collect.ImmutableList;
import com.malsolo.orika.test.dto.PersonDTO;
import java.util.Random;
import org.springframework.stereotype.Repository;
@Repository
public class PersonRepositoryImpl implements PersonRepository {
Random random = new Random();
@Override
public PersonDTO findPerson() {
return this.createPersonDTO(random.nextLong());
}
private PersonDTO createPersonDTO(long l) {
PersonDTO dto = new PersonDTO();
dto.setId(l);
//More setters here
return dto;
}
}
注意,服务将需要从DTO转换到域对象,所以让我们注入一个mapper。
package com.malsolo.orika.test.spring;
import com.malsolo.orika.test.domain.Person;
import ma.glasnost.orika.MapperFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository personRepository;
private MapperFacade mapper;
@Autowired
public void setMapperFactory(MapperFactory mapperFactory) {
this.mapper = mapperFactory.getMapperFacade();
}
@Override
public Person obtainPerson() {
return mapper.map(this.personRepository.findPerson(), Person.class);
}
}
如您所见,我使用了setter注入(通过自动连接)来提供一个MapperFactory,以便从它获得一个MapperFacade。我使用了将MapperFactory作为singleton的建议,它很容易在Spring中实现。
由于MapperFactory具有特定的实例化方法,所以最好的选择是创建一个用于公开它的工厂,一个FactoryBean:
package com.malsolo.orika.test.spring;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class MapperFactoryFactory implements FactoryBean<MapperFactory> {
@Override
public MapperFactory getObject() throws Exception {
return new DefaultMapperFactory.Builder().build();
}
@Override
public Class<?> getObjectType() {
return MapperFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
这个方法已经被选中,以防我想要使用BoundMapperFacade,但是可以通过创建MapperFacadeFactory直接使用MapperFacade。现在,让我们配置Spring应用程序。
package com.malsolo.orika.test.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class OrikaSpringTest {
public static void main(String... args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(OrikaSpringTest.class);
PersonService personService = context.getBean(PersonService.class);
Person person = personService.obtainPerson();
System.out.printf("%s\n", person.toString());
}
}
运行结果如下
Person{id=0, name=Name 0, surnames=null, address=null}
但是注意,mapper没有工作,因为两个类之间存在差异(两个属性有不同的名称,相同的概念、地址和不同的类型)
将Orika映射器和转换器插入到Spring中。Orika提供了自定义转换器和映射器,所以我们只需要在应用程序上下文中配置它们作为spring bean。让我们看看怎么做。
首先,我们需要一个自定义映射器。我们将它作为组件在Spring中发现(通过组件扫描)。
package com.malsolo.orika.test.spring;
import com.malsolo.orika.test.domain.Address;
import com.malsolo.orika.test.domain.Person;
import com.malsolo.orika.test.dto.PersonDTO;
import ma.glasnost.orika.CustomMapper;
import ma.glasnost.orika.MappingContext;
import org.springframework.stereotype.Component;
@Component
public class PersonDtoToPersonMapper extends CustomMapper<PersonDTO, Person> {
@Override
public void mapAtoB(PersonDTO a, Person b, MappingContext context) {
b.setId(a.getId());
b.setName(a.getName());
b.setSurnames(a.getLastNames());
//如果需要映射某个特定字段,则可以使用受保护的mapperFacade
//this.mapperFacade.map(sourceObject, destinationClass);
Address address = new Address();
address.setStreet(a.getStreetAddress());
address.setCity(a.getCity());
address.setZipCode(a.getPostalCode());
b.setAddress(address);
}
@Override
public void mapBtoA(Person b, PersonDTO a, MappingContext context) {
a.setId(b.getId());
a.setName(b.getName());
a.setLastNames(b.getSurnames());
Address address = b.getAddress();
if (address != null) {
a.setStreetAddress(address.getStreet());
a.setCity(address.getCity());
a.setPostalCode(address.getZipCode());
}
}
}
最后,我们必须将属于Spring application context映射器和转换器插入到MapperFactory中。如果我们扩展了ConfigurableMapper,我们将为我们创建一个MapperFactory,稍后,我们将有机会访问应用程序上下文,以查找自定义的映射器和转换器,它们是Spring组件,以便在MapperFactory中注册它们。
package com.malsolo.orika.test.spring;
import java.util.Map;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.ConfigurableMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class SpringConfigurableMapper extends ConfigurableMapper {
private ApplicationContext applicationContext;
private MapperFactory mapperFactory;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
addSpringMappers();
addSpringConverter();
}
@Override
protected void configure(MapperFactory factory) {
super.configure(factory);
this.mapperFactory = factory;
}
private void addSpringMappers() {
@SuppressWarnings("rawtypes")
final Map<String, Mapper> mappers = applicationContext
.getBeansOfType(Mapper.class);
for (final Mapper<?, ?> mapper : mappers.values()) {
addMapper(mapper);
}
}
private void addMapper(Mapper<?, ?> mapper) {
this.mapperFactory.registerMapper(mapper);
}
private void addSpringConverter() {
@SuppressWarnings("rawtypes")
final Map<String, Converter> converters = applicationContext
.getBeansOfType(Converter.class);
for (final Converter<?, ?> converter : converters.values()) {
addConverter(converter);
}
}
private void addConverter(Converter<?, ?> converter) {
this.mapperFactory.getConverterFactory().registerConverter(converter);
}
}
一些重要的注意事项列一下:
1.我们的 SpringConfigurableMapper 使用@Component注解
当Spring实例化它时,它将调用从ConfigurableMapper继承的构造函数,它调用一个init方法来创建MapperFactory,并允许您通过overriden方法进行配置。
因此,第一个方法是configure(MapperFactory),此外,不要像我上面所做的那样创建自己的MapperFactory,因为它不会是您用来注册映射器和转换器的工具,相反,这个类将创建一个新的MapperFactory
2.我们的SpringConfigurableMapper类 有引入依赖注入的 ApplicationContext @Autowired,这与您实现ApplicationContextAware的过程是一样的,但我宁愿采用这种方法。
此外,它还意味着在创建所有bean之后将调用该方法。既然您已经有了一个MapperFactory,那么您就可以搜索自定义组件和转换器,它们是spring bean,并在MapperFactory注册它们。
3.我们继承的ConfigurableMapper 实现了MapperFacade
因此,您必须稍微更改PersonServiceImpl,最好不要使用MapperFacadeFactory,否则会出现自动连接异常,因为会有两个bean的MapperFacade类
package com.malsolo.orika.test.spring;
import ma.glasnost.orika.MapperFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.malsolo.orika.test.domain.Person;
@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository personRepository;
@Autowired
private MapperFacade mapper;
@Override
public Person obtainPerson() {
return mapper.map(this.personRepository.findPerson(), Person.class);
}
}
现在,如果你运行Orika与Spring主类,OrikaSpringTest,一切将按预期运行:
Person{id=85, name=Name 85, surnames=[S., Surname 85], address=Address{street=My street 85, city=City 85, zipCode=code 85}}
spring boot结合orika
这些就就够让项目运行起来,如果你想要在spring boot中运行的话,也是同样的需要添加spring boot依赖,pom.xml文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version><!-- or latest version -->
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.5</version><!-- or latest version -->
</dependency>
主程序:
@SpringBootApplication
@RestController
public class OpenApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(OpenApplication.class, args);
PersonService personService = context.getBean(PersonService.class);
Person person = personService.obtainPerson();
System.out.println(person);
}
}
自定义ConfigurableMapper
package sun.cloud.core.orika;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.DefaultFieldMapper;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.ConfigurableMapper;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Iterator;
import java.util.Map;
public class OrikaBeanMapper extends ConfigurableMapper implements ApplicationContextAware{
private MapperFactory factory;
private ApplicationContext applicationContext;
public OrikaBeanMapper() {
super(false);
}
@Override
protected void configure(MapperFactory factory) {
this.factory = factory;
this.addAllSpringBeans(this.applicationContext);
}
protected void configureFactoryBuilder(DefaultMapperFactory.Builder factoryBuilder) {
}
private void addMapper(Mapper<?, ?> mapper) {
this.factory.registerMapper(mapper);
}
public void addConverter(Converter<?, ?> converter) {
this.factory.getConverterFactory().registerConverter(converter);
}
private void addAllSpringBeans(ApplicationContext applicationContext) {
Map<String, Mapper> mappers = applicationContext.getBeansOfType(Mapper.class);
Iterator var3 = mappers.values().iterator();
while(var3.hasNext()) {
Mapper mapper = (Mapper)var3.next();
this.addMapper(mapper);
}
Map<String, Converter> converters = applicationContext.getBeansOfType(Converter.class);
Iterator var7 = converters.values().iterator();
while(var7.hasNext()) {
Converter converter = (Converter)var7.next();
this.addConverter(converter);
}
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.init();
}
}
将自定义的Mapper注入到Spring中
package sun.cloud.core.config;
import org.springframework.context.annotation.Configuration;
import sun.cloud.core.orika.OrikaBeanMapper;
import org.springframework.context.annotation.Bean;
@Configuration
public class OrikaConfiguration {
public OrikaConfiguration() {
}
@Bean
public OrikaBeanMapper beanMapper() {
return new OrikaBeanMapper();
}
}
使用OrikaMapper
package sun.cloud.core.biz.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import sun.cloud.core.base.Pagination;
import sun.cloud.core.biz.domain.DictEntryDO;
import sun.cloud.core.biz.domain.DictEntryMapper;
import sun.cloud.core.biz.service.DictEntryManager;
import sun.cloud.core.biz.service.DictItemManager;
import sun.cloud.core.biz.service.bo.DictEntryRemoveBO;
import sun.cloud.core.biz.service.bo.DictEntrySaveBO;
import sun.cloud.core.biz.service.bo.DictEntryUpdateBO;
import sun.cloud.core.biz.service.bo.DictItemRemoveBO;
import sun.cloud.core.biz.service.dto.DictEntryDTO;
import sun.cloud.core.biz.service.query.DictEntryQUERY;
import sun.cloud.core.orika.OrikaBeanMapper;
import java.util.Date;
import java.util.List;
@Service
public class DictEntryManagerImpl implements DictEntryManager {
@Autowired
private DictEntryMapper dictEntryMapper;
@Autowired
private OrikaBeanMapper beanMapper;
@Override
public DictEntryDTO get(Long id){
return beanMapper.map(dictEntryMapper.get(id),DictEntryDTO.class);
}
}
好了,Orika的学习就告一段落了,有什么问题或者是哪里有更好的优化请评论留言,或者是qq:1433900493,我们一起努力!
参考文献:
Orika and Spring Framework = easy bean mapping by Ken Blair
Spring Framework Reference Documentation (Meatier examples)