【Java】拷贝工具之 Orika 的介绍与演示

前言

在现在流行且广泛应用的各类 Java 分层代码框架中,不同的层定义了不同类型的实体类,如 Entity、DO、DTO、VO 等。层与层之间的对象总是需要进行各种转换和映射,这些操作重复繁琐,于是催生了各种各样的工具,用来快捷、高效地完成这些操作。本文先介绍一款 Java 的拷贝工具 Orika,简单讲解一下它的特性,以及进行一些常用的、基础的代码演示。

更加详细和高级的用法,可以参考其官方文档:http://orika-mapper.github.io/orika-docs/index.html

基本介绍

Orika 使用反射来访问数据对象的属性,它会自动收集类的元数据,生成映射对象。这些映射对象可用于将数据从一个对象递归拷贝到另一个对象。Orika 的整体设计试图在保持相对简单和开放的同时,提供许多方便的功能,从而使用者可以根据自己的需要对其进行扩展和调整。

Orika 是基于 JavaBeans 规范的属性拷贝框架,默认情况下是读取符合 JavaBeans 标准的对象属性。这意味着,具有 getNamesetName 方法的对象将被称为具有名为 name 的属性;setXXX 方法不是必需的,对于布尔返回类型,getXXX 方法可以选择命名为 isXXXOrika 在此基础上更进一步,将 java.lang.Boolean 类型纳入可选的 isXXX 命名中,而且 Orika 还将公共字段识别为可使用的属性。因此 Orika 可以看作支持了我们日常开发工作中大部分常用的数据类型。

实战应用

添加 maven 依赖

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.4</version>
</dependency>

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>org.testng</groupId>
	<artifactId>testng</artifactId>
	<version>7.4.0</version>
	<scope>compile</scope>
</dependency>

主要需要添加的服务依赖是 orika-core,另外添加了 lomboktestng 的依赖用以支持测试用例的编写,作为示例。

定义实体类

这边分别定义两组实体类:一组是字段名称相同的成员信息 MemberInfoDTOMemberInfoVO;另一组是个别字段名不一致的人员信息 PersonSrcInfoPersonDestInfo

@Data
public class MemberInfoDTO {
    private Long id;

    private String name;
    private Integer age;
    private String email;

    private String identity;
    private String group;
}

@Data
public class MemberInfoVO {
    private Long id;

    private String name;
    private Integer age;
    private String email;

    private String identity;
    private String group;
}
@Data
@FieldNameConstants
public class PersonSrcInfo {
    private Long id;

    private String name;
    private Integer age;
    private LocalDate birth;
    private String email;

    // 无用字段,用于演示拷贝时指定排除字段
    private String removeVal;
}

@Data
@FieldNameConstants
public class PersonDesInfo {
    private Long id;

    private String personName;
    private Integer age;
    private LocalDate dateOfBirth;
    private String emailAddress;
}

这里使用了 @FieldNameConstants 注解,可以更加方便地指定实体类中成员变量的名称,用来指定特定字段之间的映射关联。后面具体用到的时候会提到。

简单功能演示

这里先写一个基础的 OrikaDemoTest,显式调用了 Orika 的映射工厂,对指定的类实例进行了数据的拷贝。共有三个演示用例:

  1. 字段名完全相同的两个实例拷贝,DTO 转 VO。
    • 可以使用返回参数的形式 MemberInfoVO vo = facade.map(dto, MemberInfoVO.class);
    • 也可以使用参数内置的形式 facade.map(dto, vo);
  2. 字段名完全相同的两个实例列表拷贝,List<DTO> 转 List<VO>。
    • 使用 List<MemberInfoVO> voList = facade.mapAsList(dtoList, MemberInfoVO.class);
  3. 部分字段名不同、且部分字段不参与拷贝的两个实例拷贝。
    • mapperFactory.classMap指定参与映射的两个类
    • .field() 方法指定映射关联的两个字段。.field(PersonSrcInfo.Fields.name, PersonDesInfo.Fields.personName) 当中的 .Fields 则是通过注解 @FieldNameConstants 直接方法字段名称的方式
    • .exclude(PersonSrcInfo.Fields.removeVal) 指定不参与拷贝的成员变量
    • .byDefault() 剩余成员默认按照名称进行匹配
public class OrikaDemoTest {

    @Test
    void testBasicMap() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        MapperFacade facade = mapperFactory.getMapperFacade();

        MemberInfoDTO dto = initMemberInfo();
        MemberInfoVO vo = facade.map(dto, MemberInfoVO.class);
//        facade.map(dto, vo);
        assertMemberInfoEquals(dto, vo);
    }

    @Test
    void testMapAsList() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        MapperFacade facade = mapperFactory.getMapperFacade();

        MemberInfoDTO dto1 = initMemberInfo();
        MemberInfoDTO dto2 = initMemberInfo2();
        List<MemberInfoDTO> dtoList = Lists.newArrayList(dto1, dto2);
        List<MemberInfoVO> voList = facade.mapAsList(dtoList, MemberInfoVO.class);
        for (int i = 0; i < dtoList.size(); ++i) {
            assertMemberInfoEquals(dtoList.get(i), voList.get(i));
        }
    }

    private MemberInfoDTO initMemberInfo() {
        MemberInfoDTO member = new MemberInfoDTO();
        member.setId(1000100010001L);
        member.setName("Gogo");
        member.setAge(14);
        member.setEmail("gogo@oo.com");
        member.setIdentity("employee");
        member.setGroup("marketing");
        return member;
    }

    private MemberInfoDTO initMemberInfo2() {
        MemberInfoDTO member = new MemberInfoDTO();
        member.setId(1000100010002L);
        member.setName("Pato");
        member.setAge(42);
        member.setEmail("Pato@oo.com");
        member.setIdentity("manager");
        member.setGroup("customer-service");
        return member;
    }

    private void assertMemberInfoEquals(MemberInfoDTO dto, MemberInfoVO vo) {
        assertEquals(dto.getId(), vo.getId());
        assertEquals(dto.getName(), vo.getName());
        assertEquals(dto.getAge(), vo.getAge());
        assertEquals(dto.getGroup(), vo.getGroup());
        assertEquals(dto.getEmail(), vo.getEmail());
        assertEquals(dto.getIdentity(), vo.getIdentity());
    }

    @Test
    void testMapDiffFieldName() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        // 使用注解显式获取变量名,需要指定所有的字段映射关系,剩余通过 byDefault() 指定
        mapperFactory.classMap(PersonSrcInfo.class, PersonDesInfo.class)
                .field(PersonSrcInfo.Fields.name, PersonDesInfo.Fields.personName)
                .field(PersonSrcInfo.Fields.birth, PersonDesInfo.Fields.dateOfBirth)
                .field(PersonSrcInfo.Fields.email, PersonDesInfo.Fields.emailAddress)
                .exclude(PersonSrcInfo.Fields.removeVal)
                .byDefault()
                .register();

        MapperFacade facade = mapperFactory.getMapperFacade();

        PersonSrcInfo src = initPersonInfo();
        PersonDesInfo dest = facade.map(src, PersonDesInfo.class);
        assertPersonInfoEquals(src, dest);
    }

    private PersonSrcInfo initPersonInfo() {
        PersonSrcInfo person = new PersonSrcInfo();
        person.setId(1000100010001L);
        person.setName("Gogo");
        person.setAge(14);
        person.setBirth(LocalDate.of(2004, 6, 19));
        person.setEmail("gogo@oo.com");
        person.setRemoveVal("to be removed!");
        return person;
    }

    private void assertPersonInfoEquals(PersonSrcInfo src, PersonDesInfo dest) {
        assertEquals(src.getId(), dest.getId());
        assertEquals(src.getName(), dest.getPersonName());
        assertEquals(src.getAge(), dest.getAge());
        assertEquals(src.getEmail(), dest.getEmailAddress());
        assertEquals(src.getBirth(), dest.getDateOfBirth());

        assertEquals(src.getRemoveVal(), "to be removed!");
        assertNull(dest.getRemoveVal());

    }
}

整体来看,Orika 的实例拷贝主要是通过 DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();MapperFacade facade = mapperFactory.getMapperFacade(); 这两行代码获取到 mapper ,进而对指定的实体类执行字段值拷贝。当遇到字段名不完全相同的实例映射,则需要额外指定其字段间的映射关联关系。因此,我们可以对其进行封装,定义成一个本地的通用工具类进行使用。

通用工具类实现

这里将 Orika 的拷贝方法封装成工具类,不再需要重复的初始化 MapperFactory 工厂实例,这里可以通过静态成员变量实现其单例,通过复用提升性能。下面部分的代码则是封装了拷贝对象用到的基本方法,只需要传入需要进行映射的两个类或者其实例对象即可,非常简便。

/**
 * orika基础映射工具类
 */
public final class OrikaMapperUtils {

    private static final MapperFacade MAPPER_FACADE;
    static {
        MapperFactory mapperFactory = new DefaultMapperFactory
                .Builder()
                .useAutoMapping(true)
                .mapNulls(true)
                .build();
        MAPPER_FACADE = mapperFactory.getMapperFacade();
    }

    /*************************** 工具方法部分 ***************************/
    public static <S, D> void map(S src, D dest) {
        MAPPER_FACADE.map(src, dest);
    }

    public static <S, D> D map(S src, Class<D> destClazz) {
        return MAPPER_FACADE.map(src, destClazz);
    }

    public static <S, D> List<D> mapAsList(Iterable<S> src, Class<D> destClazz) {
        return MAPPER_FACADE.mapAsList(src, destClazz);
    }
}

同样的,这里通过三组测试用例来演示 Orika 通用映射工具类的功能实现。

public class OrikaMapperUtilsTest {

    @Test
    void testBasicObjectMap() {
        MemberInfoDTO dto = initMemberInfo();
        MemberInfoVO vo = new MemberInfoVO();
        OrikaMapperUtils.map(dto, vo);
        assertMemberInfoEquals(dto, vo);
    }

    @Test
    void testBasicClazzMap() {
        MemberInfoDTO dto = initMemberInfo();
        MemberInfoVO vo = OrikaMapperUtils.map(dto, MemberInfoVO.class);
        assertMemberInfoEquals(dto, vo);
    }

    @Test
    void testMapAsList() {

        MemberInfoDTO dto1 = initMemberInfo();
        MemberInfoDTO dto2 = initMemberInfo2();
        List<MemberInfoDTO> dtoList = Lists.newArrayList(dto1, dto2);
        List<MemberInfoVO> voList = OrikaMapperUtils.mapAsList(dtoList, MemberInfoVO.class);
        for (int i = 0; i < dtoList.size(); ++i) {
            assertMemberInfoEquals(dtoList.get(i), voList.get(i));
        }
    }

    private MemberInfoDTO initMemberInfo() {
        MemberInfoDTO member = new MemberInfoDTO();
        member.setId(1000100010001L);
        member.setName("Gogo");
        member.setAge(14);
        member.setEmail("gogo@oo.com");
        member.setIdentity("employee");
        member.setGroup("marketing");
        return member;
    }

    private MemberInfoDTO initMemberInfo2() {
        MemberInfoDTO member = new MemberInfoDTO();
        member.setId(1000100010002L);
        member.setName("Pato");
        member.setAge(42);
        member.setEmail("Pato@oo.com");
        member.setIdentity("manager");
        member.setGroup("customer-service");
        return member;
    }

    private void assertMemberInfoEquals(MemberInfoDTO dto, MemberInfoVO vo) {
        assertEquals(dto.getId(), vo.getId());
        assertEquals(dto.getName(), vo.getName());
        assertEquals(dto.getAge(), vo.getAge());
        assertEquals(dto.getGroup(), vo.getGroup());
        assertEquals(dto.getEmail(), vo.getEmail());
        assertEquals(dto.getIdentity(), vo.getIdentity());
    }
}

进阶的通用工具类实现

从上面的工具类方法可以看出,上述的代码只是满足字段名完全相同的实例对象的拷贝,如果需要支持多组字段名不完全一致的对象进行拷贝,则还需要维护每组映射关系,否则就只能像最前面的简单功能演示一样,每次映射都预先定义字段的映射关系,这样代码就会变得非常繁琐。即便 MapperFactory 实现了单例,但是每次映射时都需要重新请求获取映射关系对应的 mapperFacade 实例,还是会影响到执行的性能。

下面是拓展后的通用工具类定义,新增一个静态全局的 CACHE_MAPPER 用来缓存映射关系,结合方法 registerclassMap 实现映射关系的写入和查询。

/**
 * orika进阶映射工具类
 * - 映射时指定字段的映射关系
 * - 指定一组两个实体类的字段映射关系可缓存在本地
 */
public final class OrikaAdvancedMapperUtils {

    private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory
            .Builder()
            .useAutoMapping(true)
            .mapNulls(true)
            .build();

    /**
     * 缓存 mapperFacade 实例
     */
    private static final Map<String, MapperFacade> CACHE_MAPPER = new ConcurrentHashMap<>();

    public static Map<String, MapperFacade> getCacheMapper() {
        return CACHE_MAPPER;
    }

    /**
     * 工具类私有方法,结合字段映射关系的缓存表,获取指定映射实体的 mapperFacade 实例,
     *
     * @param src      源实体
     * @param dest     目标实体
     * @param fieldMap 字段映射表
     * @param <S>      源类
     * @param <D>      目标类
     * @return mapperFacade 实例
     */
    private static synchronized <S, D> MapperFacade classMap(Class<S> src, Class<D> dest, Map<String, String> fieldMap) {
        String key = src.getCanonicalName() + ":" + dest.getCanonicalName();
        if (CACHE_MAPPER.containsKey(key)) {
            return CACHE_MAPPER.get(key);
        }

        // 缓存字段映射表
        return register(src, dest, fieldMap);
    }

    /**
     * 注册实体映射,字段映射表可选
     *
     * @param src      源实体
     * @param dest     目标实体
     * @param fieldMap 字段映射表
     * @param <S>      源类
     * @param <D>      目标类
     */
    public static synchronized <S, D> MapperFacade register(Class<S> src, Class<D> dest, Map<String, String> fieldMap) {
        if (CollectionUtils.isEmpty(fieldMap)) {
            MAPPER_FACTORY.classMap(src, dest).byDefault().register();
        } else {
            ClassMapBuilder<S, D> classMapBuilder = MAPPER_FACTORY.classMap(src, dest);
            fieldMap.forEach(classMapBuilder::field);
            classMapBuilder.byDefault().register();
        }
        String key = src.getCanonicalName() + ":" + dest.getCanonicalName();
        MapperFacade mapperFacade = MAPPER_FACTORY.getMapperFacade();
        CACHE_MAPPER.put(key, mapperFacade);
        return mapperFacade;
    }

    /*************************** 工具方法部分 ***************************/

    /**
     * 字段名相同的实体映射
     *
     * @param src      源实体
     * @param dest     目标实体
     * @param <S>      源类
     * @param <D>      目标类
     */
    public static <S, D> void map(S src, D dest) {
        if (src == null) {
            return;
        }
        map(src, dest, null);
    }

    public static <S, D> D map(S src, Class<D> destClazz) {
        if (src == null) {
            return null;
        }
        return map(src, destClazz, null);
    }

    public static <S, D> List<D> mapAsList(List<S> src, Class<D> destClazz) {
        if (src == null) {
            return null;
        }
        return mapAsList(src, destClazz, null);
    }

    /**
     * 携带字段映射关系的实体映射
     *
     * @param src      源实体
     * @param dest     目标实体
     * @param fieldMap 字段映射表
     * @param <S>      源类
     * @param <D>      目标类
     */
    public static <S, D> void map(S src, D dest, Map<String, String> fieldMap) {
        if (src == null) {
            return;
        }
        classMap(src.getClass(), dest.getClass(), fieldMap).map(src, dest);
    }

    public static <S, D> D map(S src, Class<D> destClazz, Map<String, String> fieldMap) {
        if (src == null) {
            return null;
        }
        return classMap(src.getClass(), destClazz, fieldMap).map(src, destClazz);
    }

    public static <S, D> List<D> mapAsList(List<S> src, Class<D> destClazz, Map<String, String> fieldMap) {
        if (src == null) {
            return null;
        }
        return classMap(src.get(0).getClass(), destClazz, fieldMap).mapAsList(src, destClazz);
    }
}

代码的功能都比较容易读懂,这里重点说明一下 synchronized <S, D> MapperFacade classMap(Class<S> src, Class<D> dest, Map<String, String> fieldMap) 方法。这里使用了 synchronized 关键字对方法加锁,配合 CACHE_MAPPERConcurrentHashMap,保证在拷贝方法出现并发请求时,同样实体组合的映射关系在写入时的线程安全。构建 key 时调用的 getCanonicalName 则是获取了类包括其包路径的完成名称,以进行映射的两个类的路径名构建唯一标识,缓存其映射关系表。

classMap 方法的调用是需要调用 map 方法时同时传入其映射关系表 fieldMap,这是一种被动的、懒惰式关系加载。工具类中还对外提供了一个 register 方法,可用于项目启动时主动加载不同实体组合的映射关联表。至于选取哪种方式进行缓存加载,则需要考虑服务的业务以及性能要求。

通过上述的这种本地缓存机制,拷贝操作在执行时就提升获取 mapperFacade 实例的效率,提升整体的性能。

绑定实体类的双向拷贝 BoundMapperFacade

上面我们提及到的映射和拷贝都是单项的,有时候在某些业务场景下,我们可能需要双向的实体映射操作,比如通过接口调用对数据库写入数据,以及从数据库读取数据进行返回,这里就需要实现注入 DTO -> Entity 以及 Entity -> DTO 的转化。此时如果使用上面描述的方法,则需要分别定义正反两个方向的映射关联表。如果使用 BoundMapperFacade 则只需要定义一次即可。

这里同样提供 BoundMapperFacade 的简单功能演示。在 getMapperFacade 接口中来源实体与目标实体的先后顺序不变,使用 BoundMapperFacade 执行拷贝操作时,只要调用相应的 Reverse 方法,即可实现方向的拷贝。同样地,拷贝时可以使用返回参数的形式,也可以使用参数内置的形式。

/**
 * orika实体绑定映射
 */
public class OrikaBoundMapperTest {

    @Test
    void testBasicMap() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        BoundMapperFacade<MemberInfoDTO, MemberInfoVO> facade =
                mapperFactory.getMapperFacade(MemberInfoDTO.class, MemberInfoVO.class);

        MemberInfoDTO dto = initMemberInfo();
        MemberInfoVO vo = facade.map(dto);
//        facade.map(dto, vo);
        assertMemberInfoEquals(dto, vo);
    }

    @Test
    void testBasicReverseMap() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        BoundMapperFacade<MemberInfoVO, MemberInfoDTO> facade =
                mapperFactory.getMapperFacade(MemberInfoVO.class, MemberInfoDTO.class);

        MemberInfoDTO dto = initMemberInfo();
        MemberInfoVO vo = facade.mapReverse(dto);
//        facade.mapReverse(dto, vo);
        assertMemberInfoEquals(dto, vo);
    }

    private MemberInfoDTO initMemberInfo() {
        MemberInfoDTO member = new MemberInfoDTO();
        member.setId(1000100010001L);
        member.setName("Gogo");
        member.setAge(14);
        member.setEmail("gogo@oo.com");
        member.setIdentity("employee");
        member.setGroup("marketing");
        return member;
    }

    private void assertMemberInfoEquals(MemberInfoDTO dto, MemberInfoVO vo) {
        assertEquals(dto.getId(), vo.getId());
        assertEquals(dto.getName(), vo.getName());
        assertEquals(dto.getAge(), vo.getAge());
        assertEquals(dto.getGroup(), vo.getGroup());
        assertEquals(dto.getEmail(), vo.getEmail());
        assertEquals(dto.getIdentity(), vo.getIdentity());
    }

    @Test
    void testMapDiffFieldName() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        // 使用注解显式获取变量名,需要指定所有的字段映射关系,剩余通过 byDefault() 指定
        mapperFactory.classMap(PersonSrcInfo.class, PersonDesInfo.class)
                .field(PersonSrcInfo.Fields.name, PersonDesInfo.Fields.personName)
                .field(PersonSrcInfo.Fields.birth, PersonDesInfo.Fields.dateOfBirth)
                .field(PersonSrcInfo.Fields.email, PersonDesInfo.Fields.emailAddress)
                .byDefault()
                .register();

        BoundMapperFacade<PersonSrcInfo, PersonDesInfo> facade =
                mapperFactory.getMapperFacade(PersonSrcInfo.class, PersonDesInfo.class);

        PersonSrcInfo src = initPersonInfo();
        PersonDesInfo dest = facade.map(src);
        assertPersonInfoEquals(src, dest);
    }

    @Test
    void testReverseMapDiffFieldName() {
        DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        // 使用注解显式获取变量名,需要指定所有的字段映射关系,剩余通过 byDefault() 指定
        mapperFactory.classMap(PersonSrcInfo.class, PersonDesInfo.class)
                .field(PersonSrcInfo.Fields.name, PersonDesInfo.Fields.personName)
                .field(PersonSrcInfo.Fields.birth, PersonDesInfo.Fields.dateOfBirth)
                .field(PersonSrcInfo.Fields.email, PersonDesInfo.Fields.emailAddress)
                .byDefault()
                .register();

        BoundMapperFacade<PersonDesInfo, PersonSrcInfo> facade =
                mapperFactory.getMapperFacade(PersonDesInfo.class, PersonSrcInfo.class);

        PersonSrcInfo src = initPersonInfo();
        PersonDesInfo dest = facade.mapReverse(src);
        assertPersonInfoEquals(src, dest);
    }

    private PersonSrcInfo initPersonInfo() {
        PersonSrcInfo person = new PersonSrcInfo();
        person.setId(1000100010001L);
        person.setName("Gogo");
        person.setAge(14);
        person.setBirth(LocalDate.of(2004, 6, 19));
        person.setEmail("gogo@oo.com");
        return person;
    }

    private void assertPersonInfoEquals(PersonSrcInfo src, PersonDesInfo dest) {
        assertEquals(src.getId(), dest.getId());
        assertEquals(src.getName(), dest.getPersonName());
        assertEquals(src.getAge(), dest.getAge());
        assertEquals(src.getEmail(), dest.getEmailAddress());
        assertEquals(src.getBirth(), dest.getDateOfBirth());
    }
}

性能提升

网上很多文章在比较不同的 Java 拷贝工具时,很多会说 Orika 性能比较好的原因是它在拷贝过程中没有用到内置的反射。这个表达不太准确,严格来说 Orika 在使用 map 方法时,如果传入的是 class 类,内部依然会通过反射机制创建出对应的目标对象,只不过在拷贝成员变量时没有使用反射而已。相比于其他工具全程使用反射进行实例化以及赋值操作,Orika 在赋值时没有使用到反射,具有更优的性能开销。

同时,在其内部实现中也通过不同的策略,减少了映射的开销。其中一部分策略已经在上面的演示中提及到,这里再简单做个整理。

  1. MapperFactory 用作单例

    Orika 中性能开销最大的部分之一就是 MapperFactory 的实例化和初始化,以及从中获得的 MapperFacade。由于它们都是线程安全对象,可以作为单例在代码中安全地共享。具体可参考上面 进阶的通用工具类实现 一节。

  2. 使用 BoundMapperFacade 以避免重复查找映射策略

    为给定的一对类型构建一个 BoundMapperFacade<A,B> 来避免为输入集查找适当映射策略的开销,同时还能避免在递归映射调用中进行额外的查找。

  3. 当对象关系不存在循环时使用自定义 BoundMapperFacade

    映射过程中最昂贵的部分之一是在给定的映射上下文MappingContext 中对已映射过的对象进行哈希查找。如果知道对象关系中不存在循环,就可以避免这种查找,从而大大加快映射过程。通过使用 MapperFactory 上的 getMapperFacade(typeA,TypeB,false) 方法定义一个无循环的实体映射实例:

    BoundMapperFacade<Person,PersonDto> mapper = mapperFactory.getMapperFacade(PersonSrcInfo.class, PersonDesInfo.class, false);
    
    public PersonDesInfo convert(PersonSrcInfo person) {
       return mapper.map(person);
    }
    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值