Juconcurrent 学而不思则罔,思而不学则殆。

java bean 映射器 -- orika

2017-05-19

依赖

<dependency>
   <groupId>ma.glasnost.orika</groupId>
   <artifactId>orika-core</artifactId>
   <version>1.4.2</version><!-- or latest version -->
</dependency>

间接依赖于以下组件

  • javassist
  • slf4j
  • paranamer

入门

MapperFactory

使用MapperFactory进行注册,包括:映射关系、转换器、映射器等。其是线程安全的,可以全局单例。

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

映射关系注册

mapperFactory.classMap(PersonSource.class, PersonDestination.class)
   .field("firstName", "givenName")
   .field("lastName", "sirName")
   .byDefault()
   .register();

MapperFacade

使用MapperFacade来进行具体的映射转换逻辑工作。其是线程安全的,可以全局单例。

MapperFacade mapper = mapperFactory.getMapperFacade(); // 线程安全的

具体使用的时候,使用特殊的格式来进行转换,最常用的莫过于map(objectA, B.class)

PersonSource source = new PersonSource();
PersonDest destination = mapper.map(source, PersonDest.class);

BoundMapperFacade

为了提升性能,减少获取映射关系的时间,我们可以使用BoundMapperFacade来固定关系。其用法和MapperFacade类似。

BoundMapperFacade<PersonSource, PersonDest> boundMapper =
   mapperFactory.getMapperFacade(PersonSource.class, PersonDest.class);

PersonSource source = new PersonSource();
PersonDest destination = boundMapper.map(source);

映射关系的配置,ClassMapBuilder

这个类用于类型和类型间的映射,可以配置映射关系。

classMap(aType, bType)

最基本的用法,用于类型映射。其中aType和bType,可以是Class,也可以是’ma.glasnost.orika.metadata.Type’。

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
    .byDefault()
    .register();

field(“aField”, “bField”),字段映射

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
    .field("name", "fullName")
  	.field("age", "currentAge")
  	.register();

byDefault

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
  	.field("name", "fullName")
  	.field("age", "currentAge")
  	.byDefault()
  	.register();

fieldAToB和fieldBToA

这和field有所不同,field是用于双向映射。而这两个只能单向映射。以下只能从BasicPerson拷贝属性到BasicPersonDto

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
  	.fieldAToB("name", "fullName")
  	...
  	.register();

exclude(name),用于排除字段

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
  	.exclude("name")
  	...
  	.register();

特殊构造方法,使用constructorA(parameterNames…) 和 constructorB(parameterNames…)

拷贝的时候,是先调用其构造方法进行初始构造。

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .constructorA("name","id")
   ...
   .register();

Arrays映射

class BasicPerson {
  private List<String> nameParts;
  // getters/setters omitted
}
class BasicPersonDto {
  private String firstName;
  private String lastName;
  // getters/setters omitted
}

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .field("nameParts[0]", "firstName")
   .field("nameParts[1]", "lastName")
   .register();

Map映射

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .field("nameParts['first']", "firstName")
   .field("nameParts[\"last\"]", "lastName")
   .register();

对象嵌套属性映射

class Name {
   private String first;
   private String last;
   private String fullName;
   // getters/setters
}

class BasicPerson {
  private Name name;
  // getters/setters omitted
}
class BasicPersonDto {
  private String firstName;
  // getters/setters omitted
}

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .field("name.first", "firstName")
   .register();

映射关系的扩展配置

null值是一个比较普遍的存在,在属性映射的时候,对于null值我们可以有很多种策略。

  1. 使用null值覆盖
  2. null值不进行覆盖

在orika中,我们可以使用mapNulls(boolean)来设置。设置的范围又分为全局设置、类级别设置和属性级别设置。

全局设置

我们可以使用DefaultMapperFactory.Builder来进行设置。

mapperFactory = new DefaultMapperFactory.Builder().mapNulls(false).build();

类级别设置

这是在ClassMapBuilder上进行设置。mapNulls是从A到B;mapNullsInReverse是从B到A

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .mapNulls(true).mapNullsInReverse(true) // 设置类级别,其所有后续属性都沿用其设置,如:field1 <-> fieldOne
   .field("field1", "fieldOne")
   .mapNulls(false).mapNullsInReverse(false) // 重置类级别,影响其后属性设置,如:field2 <-> fieldTwo
   .field("field2", "fieldTwo")
   .byDefault()
   .register();

属性级别设置

这是在FieldMap上进行设置的。

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)
   .mapNulls(false).mapNullsInReverse(false)
   .fieldMap("field1", "fieldOne").mapNulls(true).mapNullsInReverse(true).add() // 这儿设置,`fieldMap()`方法用于生成`FieldMap`,并以`add()`为结尾
   .field("field2", "fieldTwo")
   .byDefault()
   .register();

自定义映射器

在已有orika的设置中,有时并不能完全完成我们想要的属性映射,这时我们可以考虑使用自定义的方式,灵活地增加额外的映射功能。 我们使用ClassMapBuildercustomize()方法来做,其需要我们传入一个CustomMapper的对象。

mapperFactory.classMap(Source.class, Destination.class)
	.byDefault()
	.customize(
	   new CustomMapper<Source, Destination> {
	      public void mapAtoB(A a, B b, MappingContext context) {
	         // add your custom mapping code here
	      }
       }
    .register();

自定义ClassMapBuilder

这是全局设置的,我们可以自定义我们自己的类映射构建器,以增强我们需要的功能。尽管这在很多时候是没有必要的。

MapperFactory factory = new DefaultMapperFactory.Builder()
    .classMapBuilderFactory(new ScoringClassMapBuilder.Factory()) // ScoringClassMapBuilder,能够将一个类似值进行映射。
    .build();

单行属性映射

个人认为没什么卵用。

MapperFactory的扩展配置

MapperFactory提供了全局的一些策略和参数的配置。我们可以使用其来完成我们的一些特定的需求。

如何构造MapperFactory

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

构造方法生成策略

我们可以使用constructorResolverStrategy()方法来指定

constructorResolverStrategy(ConstructorResolverStrategy constructorResolverStrategy);

编译器策略

默认使用JavassistCompilerStrategy来作为其策略,但是我们可以使用compilerStrategy()来重新指定。

compilerStrategy(CompilerStrategy compilerStrategy);

属性生成策略

属性生成策略用于生成Property

propertyResolverStrategy(PropertyResolverStrategy propertyResolverStrategy);

ClassMapBuilderFactory自定义

classMapBuilderFactory(ClassMapBuilderFactory classMapBuilderFactory);

mapNulls和useAutoMapping

这是设置开关参数

自定义converter

orika内置了默认的映射器和转换器,在大多数时候都是可行的。但是有一些特殊场景,满足不了我们的需求。这时,我们需要自定义converter来辅助完成。 我们需要实现ma.glasnost.orika.CustomConverter<A,B>来完成单一方法的转换。

public class MyConverter extends CustomConverter<Date,MyDate> {
   public MyDate convert(Date source, Type<? extends MyDate> destinationType) {
      // return a new instance of destinationType with all properties filled
   }
}

双向converter

可以通过继承ma.glasnost.orika.converter.BidirectionalConverter<A,B>来实现。

public class MyConverter extends BidirectionalConverter<Date,MyDate> {

   public MyDate convertTo(Date source, Type<MyDate> destinationType) {
      // convert in one direction
   }

   public Date convertFrom(MyDate source, Type<Date> destinationType) {
      // convert in the other direction
   }
}

converter的全局注册

写好的converter可以在全局进行生效,在此之前需要进行注册。

ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new MyConverter());

converter的属性级别注册

converter可以作用于具体的一个属性,不过在这之前需要进行全局注册。

ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter("myConverterIdValue", new MyConverter());
mapperFactory.classMap( Source.class, Destination.class )
   .fieldMap("sourceField1", "sourceField2").converter("myConverterIdValue").add()
   ...
   .register();

对象工厂

orika在实例化一个类的时候,是使用默认的方式。有时这是不能满足我们的需求的,这时需要我们自定义我们的实例化代码,以便更精确地实现。

对象工厂,用于解决这种问题。需要我们实现ma.glasnost.orika.ObjectFactory<T>

public class PersonFactory implements ObjectFactory<Person> {

   public Person create(Object source, Type<Person> destinationType) {
      Person person = new Person();
      // set the default address
      person.setAdress(new Address("Morocco", "Casablanca"));
      return person;
   }
}

使用之前需要进行注册。

mapperFactory.registerObjectFactory(new PersonFactory(), Person.class);

filter

filter允许对映射的过程进行更为灵活的把控。使用filter必须继承或实现ma.glasnost.orika.Filterma.glasnost.orika.CustomFilter

ma.glasnost.orika.NullFilter是一个特殊的filter,它不做任何额外的事情。

Converter / Mapper / ObjectFactory, 区别与联系

这3个扩展,都是为了完成对象的映射,但是其侧重点不同。总的来说有以下概括:

  1. ObjectFactory,主要用于实例化类,不做属性映射
  2. Mapper,将一个对象的属性,拷贝到另一个对象
  3. Converter,ObjectFactory和Mapper的合集

至于什么时候应该用什么扩展,需要具体的场景具体分析。

性能调优

orika尽管在很多地方已经做得足够好了,但是我们可以注意一些额外的事情,来减少其开销。

使用MapperFactory作为单例

orika经验告诉我们,在全局范围内,我们只需要构造并初始化一次MapperFactoryMapperFacade是从MapperFactory中获取。 在orika中,MapperFactoryMapperFacade线程安全的,所以我们可以使用单例来实现。 在spring环境我们可以使用@Component来声明,而在非spring环境,我们可以使用单例设计模式来实现。

缓存Type<?> class

Type指的是ma.glasnost.orika.metadata.Type,用于泛型类型的映射。构造Type需要额外的开销,为了减少这部分开销,我们可以将Type<?>缓存起来,以便重复使用。

使用BoundMapperFacade避免重复的上下文检索

如果我们知道了两个了类的类型,我们可以将映射关系以BoundMapperFacade保存下来,要map的时候直接使用,从而避免了从上下文的map中检索策略。

BoundMapperFacade<Person,PersonDto> mapper = mapperFactory.getMapperFacade(Person.class, PersonDto.class);

public PersonDto toDto(Person person) {
   return mapper.map(person);
}

非环形依赖下,使用getMapperFacade(typeA, TypeB, false)

所谓环形依赖,指的是A依赖B,B依赖C,C依赖A,这种情况。在设计pojo的时候,我们应尽可能地避免这种情况。同时生成的不带环形依赖的BoundMapperFacade,以增强其性能。


Content