Giter Club home page Giter Club logo

easy-mapper's Introduction

Easy-mapper

Build Status Coverage Status Maven Central Hex.pm

Easy-mapper is a simple, light-weighted, high-performance java bean mapping framework. By leveraging Javassist, easy mapper can generate mapping byte-code at runtime and load them into JVM for later invocations to reduce some of the overhead.

Easy-mapper not only provides a relatively high-performance mapping solution, but also enables the caller to do mapping in a more flexible and extensible way. Fluent interface style API and Java8 lambda API, these modern techniques can be fully used to customize your own mapping strategy.

Here, easy-mapper uses by-reference field mapping strategy most of the time except for some immutable types like primitive, wrapper, String and BigDecimal, etc. When you don’t need to copy and clone field, by-reference mapping is capable to process your business logic and avoid the overhead of performance loss. On the other hand, easy-mapper respects immutability and do not mean to offense, just provide an alternative solution.

For performance test result, please refer to the benchmark section.

中文手册请点这里

1. Easy-mapper in a nutshell

1.1 Where to get easy mapper

Maven:

<dependency>
    <groupId>com.baidu.unbiz</groupId>
    <artifactId>easy-mapper</artifactId>
    <version>1.0.4</version>
</dependency>

Gradle:

compile 'com.baidu.unbiz:easy-mapper:1.0.4'

1.2 Develop Java bean

POJO:

public class Person {
    private String firstName;
    private String lastName;
    private List<String> jobTitles;
    private long salary;
    // getter and setter...
}

DTO(Data Transfer Object)

public class PersonDto {
    private String firstName;
    private String lastName;
    private List<String> jobTitles;
    private long salary;
    // getter and setter...
}

1.3 Start mapping

From POJO to DTO:

Person p = new Person();
p.setFirstName("NEO");
p.setLastName("jason");
p.setJobTitles(Lists.newArrayList("abc", "dfegg", "iii"));
p.setSalary(1000L);
PersonDto dto = MapperFactory.getCopyByRefMapper()
                .mapClass(Person.class, PersonDto.class)
                .registerAndMap(p, PersonDto.class);
System.out.println(dto);

2. Dig into easy-mapper

2.1 Register and map

There are two separate steps to do mapping and you can combine them.

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                .register()
                .map(p, PersonDto.class);
PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                .register()
Mapper mapper = MapperFactory.getCopyByRefMapper();
PersonDto dto = mapper.map(p, PersonDto.class);
PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                .register()
Mapper mapper = MapperFactory.getCopyByRefMapper().map(p, PersonDto.class);

2.2 Specify field name

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                .field("salary", "salary")
                .register()
                .map(p, PersonDto.class);

2.3 Ignore fields from source object

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                .exclude("lastName")
                .register()
                .map(p, PersonDto.class);

2.4 Customize filed mapping

PersonDto6 dto = new PersonDto6();
MapperFactory.getCopyByRefMapper().mapClass(Person6.class, PersonDto6.class)
        .field("jobTitles", "jobTitles", new Transformer<List<String>, List<Integer>>() {
            @Override
            public List<Integer> transform(List<String> source) {
                return Lists.newArrayList(1, 2, 3, 4);
            }
        })
        .register()
        .map(p, dto);

Java8 lambda:

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                    .field("firstName", "firstName", (String s) -> s.toLowerCase())
                    .register()
                    .map(p, PersonDto.class);

Java8 streaming:

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                    .field("jobTitles", "jobTitleLetterCounts",
                            (List<String> s) -> s.stream().map(String::length).toArray(Integer[]::new))
                    .register()
                    .map(p, PersonDto.class);

Type inference:

MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                    .field("firstName", "firstName", String.class, String.class, s -> s.toLowerCase())
                    .register()
                    .map(p, PersonDto.class);

2.5 Customize object mapping

PersonDto6 dto = new PersonDto6();
MapperFactory.getCopyByRefMapper().mapClass(Person6.class, PersonDto6.class)
        .customMapping((a, b) -> b.setLastName(a.getLastName().toUpperCase()))
        .register()
        .map(p, dto);

2.6 New object then mapping

PersonDto dto = new PersonDto();
MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class).registerAndMap(p, dto);

2.7 Map on null

PersonDto dto = MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class)
                    .mapOnNull(true)
                    .register()
                    .map(p, PersonDto.class);

2.8 Cascade mapping

Easy-mapper can map recursively. If Person has-a Address, Address mapping should be done beforehand.

MapperFactory.getCopyByRefMapper().mapClass(Address.class, Address2.class).register();
Person p = getPerson(); 
p.setAddress(new Address("beverly hill", 10086));
PersonDto dto = MapperFactory.getCopyByRefMapper()
                    .mapClass(Person.class, PersonDto.class)
                    .register()
                    .map(p, PersonDto.class);

Otherwise there will result a exception like below:

com.baidu.unbiz.easymapper.exception.MappingException: No class map found for (Address, Address2), make sure type or nested type is registered beforehand

2.9 Output generated source code for debugging

Specify the following args:

-Dcom.baidu.unbiz.easymapper.enableWriteSourceFile=true 
-Dcom.baidu.unbiz.easymapper.writeSourceFileAbsolutePath="..."
-Dcom.baidu.unbiz.easymapper.enableWriteClassFile=true 
-Dcom.baidu.unbiz.easymapper.writeClassFileAbsolutePath="..."

3. Mapping rules

Rules prioritizes as below:

  1. Custom transformer goes with highest priority.

  2. Field type is the same, copy by reference. For primitive and wrapper type, equal operator is used for assigning.

  3. If target field is String but source is not, assign target with source.toString().

  4. If target field type can be assigned from source field type, then copy by reference.

  5. Any other cases, use object graph or so called cascade mapping to map.

At last, if none of the mapping handlers work, there will end up with the following exception:

com.baidu.unbiz.easymapper.exception.MappingCodeGenerationException: No appropriate mapping strategy found for FieldMap[jobTitles(List<string>)-->jobTitles(List<integer>)]
...
com.baidu.unbiz.easymapper.exception.MappingException: Generating mapping code failed for ClassMap([A]:Person6, [B]:PersonDto6), this should not happen, probably the framework could not handle mapping correctly based on your bean.

For target class creation, it is OK without non-args default constructor, but you have to make sure every parameter should be primitive or else easy-mapper cannot create an instance of the target class.

Easy-mapper prefers default constructor to create the target class when multiple constructors appearing.

4. Initialization

Sometimes the following exception will occur and program will never recover:

Caused by: com.baidu.unbiz.easymapper.exception.MappingException: No class map found for (String, String), make sure type or nested type is registered beforehand

This is because during program startup, their might be concurrent calls, and easy-mapper relies on SPI Service Provider which is not thread-safe, so it fails the first time and never recover. In the long run, SPI Service Provider will be replaced. But currently what you can do is to paste the following code in your startup.

ServiceLoaderHelper.getInstance();

For example, in Spring context startup.

public class CustomContextLoaderListener extends ContextLoaderListener {
    static {
        ServiceLoaderHelper.getInstance();
    }
}

5. Dependencies

+- org.slf4j:slf4j-api:jar:1.7.7:compile
+- org.slf4j:slf4j-log4j12:jar:1.7.7:compile
|  \- log4j:log4j:jar:1.2.17:compile
+- org.javassist:javassist:jar:3.18.1-GA:compile

6. Benchmark

Based on Oracal Hotspot JVM:

java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

JVM args:

-Xmx512m -Xms512m -XX:MetaspaceSize=256m

Hardware configuration:

CPU: Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
MEM: 8G

Please refer to testing source code.

-------------------------------------
| Create object number:   10000      |
-------------------------------------
|     Framework     |    time cost   |
-------------------------------------
|      Pure get/set |      11ms      |
|       Easy mapper |      44ms      |
|  Cglib beancopier |       7ms      |
|         BeanUtils |     248ms      |
|     PropertyUtils |     129ms      |
|  Spring BeanUtils |      95ms      |
|             Dozer |     772ms      |
-------------------------------------
-------------------------------------
| Create object number:  100000      |
-------------------------------------
|     Framework     |    time cost   |
-------------------------------------
|      Pure get/set |      56ms      |
|       Easy mapper |     165ms      |
|  Cglib beancopier |      30ms      |
|         BeanUtils |     921ms      |
|     PropertyUtils |     358ms      |
|  Spring BeanUtils |     152ms      |
|             Dozer |    1224ms      |
-------------------------------------
-------------------------------------
| Create object number: 1000000      |
-------------------------------------
|     Framework     |    time cost   |
-------------------------------------
|      Pure get/set |     189ms      |
|       Easy mapper |     554ms      |
|  Cglib beancopier |      48ms      |
|         BeanUtils |    4210ms      |
|     PropertyUtils |    4386ms      |
|  Spring BeanUtils |     367ms      |
|             Dozer |    6319ms      |
-------------------------------------

Conclusion:

Easy-mapper is way faster than traditional framework like Apache BeanUtils, PropertyUtils and dozer but cannot beat Cglib Beancopier which manipulating byte code using ASM directly.

For Spring BeanUtils, when invocation number exceeds certain threshold, Spring BeanUtils is faster than Easy-mapper. That is because Spring BeanUtils is so simple that it just execute Method.invoke(..), and this reflection work can be improved by JIT compiler at runtime and does not invoke native method.

By thinking of the benefits that easy-mapper brings to you, this tradeoff can be accepted.

7. Working together with High-order function

7.1 With guava

MapperFactory.getCopyByRefMapper().mapClass(Address.class, Address2.class).register();
MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class).register();
List<Person> personList = getPersonList();
Collection<PersonDto> personDtoList = Collections2.transform(personList,
        p -> MapperFactory.getCopyByRefMapper().map(p, PersonDto.class));
System.out.println(personDtoList);

7.2 With functional java

MapperFactory.getCopyByRefMapper().mapClass(Address.class, Address2.class).register();
MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class).register();
List<Person> personList = getPersonList();
fj.data.List<PersonDto> personDtoList = fj.data.List.fromIterator(personList.iterator()).map(
        person -> MapperFactory.getCopyByRefMapper().map(person, PersonDto.class));
personDtoList.forEach(e -> System.out.println(e));

7.3 With Java8 stream

MapperFactory.getCopyByRefMapper().mapClass(Address.class, Address2.class).register();
MapperFactory.getCopyByRefMapper().mapClass(Person.class, PersonDto.class).register();
List<Person> personList = getPersonList();
List<PersonDto> personDtoList = personList.stream().map(p -> MapperFactory.getCopyByRefMapper().map(p,
        PersonDto.class)).collect(Collectors.toList());

7.4 With Scala

object EasyMapperTest {
 
  def main(args: Array[String]) {
    MapperFactory.getCopyByRefMapper.mapClass(classOf[Person], classOf[PersonDto]).register
    val personList = List(
      new Person("neo1", 100),
      new Person("neo2", 200),
      new Person("neo3", 300)
    )
    val personDtoList = personList.map(p => MapperFactory.getCopyByRefMapper.map(p, classOf[PersonDto]))
    personDtoList.foreach(println)
  }
}

8. Acknowledgment

The development of easy-mapper is inspired by Orika. Easy-mapper with Apache2.0 Open Source License retains all copyright, trademark, author’s information from Orika.

easy-mapper's People

Contributors

neoremind avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

easy-mapper's Issues

If init failed, then all mapping in container wont work

The exception looks like:

2017-07-01 00:02:51.338 | ERROR in c.h.a.a.e.e.AbstractExceptionMapper.toResponse(25) [|Jetty-Server-67] els-ass-160 user_id= hulu_request_id=a3441bf4-2996-443c-a561-1a90dc2fdaef path=GET_deletion_uid - Generating mapping code failed for ClassMap([A]:DeletionUid, [B]:DeletionUidDto), this should not happen, probably the framework could not handle mapping correctly based on your bean [java]  [els-ass-160]
com.baidu.unbiz.easymapper.exception.MappingException: Generating mapping code failed for ClassMap([A]:DeletionUid, [B]:DeletionUidDto), this should not happen, probably the framework could not handle mapping correctly based on your bean [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:169) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:116) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.ClassMapBuilder.registerAndMap(ClassMapBuilder.java:241) ~[app.jar:na] [java]  [els-ass-160]
        at com.hulu.ap.meta.api.DeletionUidApi$1.apply(DeletionUidApi.java:119) ~[app.jar:na] [java]  [els-ass-160]
        at com.hulu.ap.meta.api.DeletionUidApi$1.apply(DeletionUidApi.java:115) ~[app.jar:na] [java]  [els-ass-160]
        at com.hulu.ap.meta.api.DeletionUidApi.getLatest(DeletionUidApi.java:80) ~[app.jar:na] [java]  [els-ass-160]

and it keeps occurring if the following exception happens when application startup.

2017-07-01 00:02:51.141 | ERROR in c.b.u.e.codegen.MappingCodeGenerator.build(79) [|Jetty-Server-67] els-ass-160 user_id= hulu_request_id=a3441bf4-2996-443c-a561-1a90dc2fdaef path=GET_deletion_uid - Generating mapping code with error: null [java]  [els-ass-160]
java.util.NoSuchElementException: null [java]  [els-ass-160]
        at sun.misc.CompoundEnumeration.nextElement(CompoundEnumeration.java:59) ~[na:1.8.0_92] [java]  [els-ass-160]
        at java.util.ServiceLoader$LazyIterator.hasNextService(ServiceLoader.java:357) ~[na:1.8.0_92] [java]  [els-ass-160]
        at java.util.ServiceLoader$LazyIterator.hasNext(ServiceLoader.java:393) ~[na:1.8.0_92] [java]  [els-ass-160]
        at java.util.ServiceLoader$1.hasNext(ServiceLoader.java:474) ~[na:1.8.0_92] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.mapping.DefaultMappingStrategy.generateMappingCode(DefaultMappingStrategy.java:43) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.codegen.MappingCodeGenerator.generateMappingCode(MappingCodeGenerator.java:141) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.codegen.MappingCodeGenerator.addMapMethod(MappingCodeGenerator.java:106) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.codegen.MappingCodeGenerator.build(MappingCodeGenerator.java:68) ~[app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.CopyByRefMapper$2.compute(CopyByRefMapper.java:165) [app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.CopyByRefMapper$2.compute(CopyByRefMapper.java:162) [app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.util.Memoizer$1.call(Memoizer.java:67) [app.jar:na] [java]  [els-ass-160]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_92] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.util.Memoizer.compute(Memoizer.java:73) [app.jar:na] [java]  [els-ass-160]
        at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:160) [app.jar:na] [java]  [els-ass-160]

What should I do if I want to copy `Array` and `Interface` ?

What should I do if I want to copy Array and Interface( like List ) ?
There is a low-level way to solve the second question. Hope you have a better one.

Mapper register = MapperFactory.getCopyByRefMapper().mapClass(UserForm.class, UserView.class)
                .field("persons", "persons", (List list) -> Lists.newArrayList(list))
                .register();
UserView view = register.map(form, UserView.class);

What should I do if I want to copy `Array` and `Interface` ?

What should I do if I want to copy Array and Interface( like List ) ?
There is a low-level way to solve the second question. Hope you have a better one.

Mapper register = MapperFactory.getCopyByRefMapper().mapClass(UserForm.class, UserView.class)
                .field("persons", "persons", (List list) -> Lists.newArrayList(list))
                .register();
UserView view = register.map(form, UserView.class);

好像不支持子类的映射?

Class A 和 Class B extend A的两个对象之间进行映射,好像不能直接用。this should not happen, probably the framework could not handle mapping correctly based on your bean

但我又不能直接写一个AtoBMapping的接口,因为Class B是javassist动态生成的类。

Remove the dependency of log4j

It seems that it's more reasonable to include the log API but not log IMPLEMETATION in most toolkits.The user may choose the implementation by himself/herself.
BTW,the feature of auto-registering may make this toolkit more convenient.

Orika 中类似 Transformer 的功能

偶然看到该项目的灵感来自于 Orika,看了 Orika 的介绍没找到 EasyMapper 中 Transformer 的功能,即创建 ClassMap 时建立不同类型之间的关联关系,请问您知道 Orika 中支持这种操作吗?

逆向拷贝时报错

当已经存在一个A to B的映射,再映射B to A,拷贝B对象到A对象时会报转换异常ClassCastException

UserEntity target = MapperFactory.getCopyByRefMapper().mapClass(User.class, UserEntity.class).register()
                .map(source, UserEntity.class);
System.out.println(target);

User secondTarget = MapperFactory.getCopyByRefMapper().mapClass(UserEntity.class, User.class).register()
                .map(target, User.class);
System.out.println(secondTarget);

容器启动时,偶尔会报错,重启可能会解决问题

我创建了一个静态的 Mapper 对象,然后在其它地方使用该对象,看起来似乎没问题,但是在外部容器启动时,项目偶尔会报错,而且错误会一直持续,只能重启,请帮忙看看是什么原因?这个问题很久之前就有,一直没有找到原因,看更新日志 1.0.3 版本貌似解决了,但是我更新后还是会报错……

private static final Mapper mapper = MapperFactory.getCopyByRefMapper().mapClass(Prospect.class, ProspectVo.class).register();
mapper.map(source, ProspectVo.class);
Caused by: com.baidu.unbiz.easymapper.exception.MappingException: No class map found for (String, String), make sure type or nested type is registered beforehand
	at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:157)
	at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:116)
	at com.baidu.unbiz.generated.EasyMapper_Prospect_TO_ProspectVo_Mapper63926513617948576$0.map(EasyMapper_Prospect_TO_ProspectVo_Mapper63926513617948576$0.java)
	at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:176)
	at com.baidu.unbiz.easymapper.CopyByRefMapper.map(CopyByRefMapper.java:116)
	at com.github.trang.copiers.mapper.MapperCopier.copy(MapperCopier.java:40)
	... 156 common frames omitted

支持表达式?

你好,非常感谢这个项目,想问下是否考虑后期支持属性的表达式计算。例如:我vo里面的自断是po里面的多个自断拼接的结果。能否提供表达式?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.