Orika - 類複製工具

語言: CN / TW / HK

Orika

前言

類複製工具有很多,比較常用的有 mapstruct、Spring BeanUtils、Apache BeanUtils、dozer 等,目前我所在的項目組中使用的是 mapstruct。在性能方面,mapstruct 毫無疑問是最優秀的,因為 mapstruct 是通過 getter、setter 方法來複制屬性值的,而其它框架或多或少使用反射進行復制,這裏也不再贅述。但是,mapstruct 也有它的不足之處,請看下面:

不知道大家使用 mapstruct 時,是否編寫過類似如下的 java 表達式:

@Mapper
public interface SmsTemplateConverter {

    SmsTemplateConverter SMS_TEMPLATE_CONVERTER = Mappers.getMapper(SmsTemplateConverter.class);

    @Mappings({
            // 這裏只能通過全類名來調用靜態方法,否則類無法注入到編譯後的文件
            @Mapping(target = "templateType", expression = "java(org.example.enums.SmsEnum.getTypeByCode(platformTemp.getTemplateType()))")
    })
    SmsCompanyTemplateVO toSmsCompanyTemplateVO(SmsCompanyTemplate companyTemp, SmsPlatformTemplate platformTemp);
}

我們不難發現,一旦這裏的 org.example.enums.SmsEnum 全類名目錄發生改變,此處的代碼就會報錯,因為這裏的 expression 是字符串,在目錄更改時,不能自動更改全類名路徑(因為是字符串,不是真正的 java 代碼,mapstruct 的 java 表達式是由代碼生成器生成的,在編譯後 target 目錄下可以看到),等於是寫死的,後期維護和擴展時會比較困難,因此我們項目中決定放棄 mapsruct。

在調研了眾多類複製工具後,我選擇了 Orika,並通過 demo 驗證確實可行,在瞭解 Orika 前,不妨瞭解一下各個類複製工具的對比,如下圖示:(圖片源於網絡,如有侵權,請聯繫刪除)

使用示例

下面我會以一個基本示例和一個Date屬性轉String屬性的示例來示範 Orika 的使用。

導入依賴

<!-- 類複製工具:orika -->
<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version><!-- or latest version -->
</dependency>

<!-- hu-tool 工具包 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.16</version>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>

定義實體 Person 和 PersonDTO

Person實體:

import lombok.Data;
import java.util.Date;

@Data
public class Person {
    private String name; // 注意這個字段名是與 PersonDTO 相同的
    private String age;
    private Date birth;
}

PersonDTO實體:

import lombok.Data;

@Data
public class PersonDTO {
    private String name;
    private Integer dtoAge;
    private String dtoBirth;
}

基本示例

簡單用法一:

import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.example.entity.Person;
import org.example.entity.PersonDTO;

// MapperFacade
public class MapperFacadeMain {
    public static void main(String[] args) {
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        mapperFactory.classMap(Person.class, PersonDTO.class)
                .field("age", "dtoAge") // 屬性名不同時的處理
                .byDefault() // 未列舉的屬性自動匹配
                .register();
        Person person = new Person();
        person.setAge("123"); // 字符串與數字可以互轉
        person.setName("張三");
        MapperFacade mapper = mapperFactory.getMapperFacade(); // MapperFacade 的性能不如 BoundMapperFacade
        PersonDTO personDTO = mapper.map(person, PersonDTO.class);
        System.out.println(personDTO);
    }
}

// 輸出 PersonDTO(name=張三, dtoAge=123, dtoBirth=null)

簡單用法二:

import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.example.entity.Person;
import org.example.entity.PersonDTO;

public class BoundMapperFacadeMain {
    public static void main(String[] args) {
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        mapperFactory.classMap(Person.class, PersonDTO.class)
                .field("age", "dtoAge")
                .byDefault()
                .register();
        Person person = new Person();
        person.setAge("456");
        person.setName("李四");
        BoundMapperFacade<Person, PersonDTO> boundMapper =
                mapperFactory.getMapperFacade(Person.class, PersonDTO.class);
        PersonDTO personDTO = boundMapper.map(person);
        System.out.println(personDTO);
    }
}

// 輸出 PersonDTO(name=李四, dtoAge=456, dtoBirth=null)

Date 轉 String 示例

定義 converter:

import cn.hutool.core.date.DateTime;
import ma.glasnost.orika.CustomConverter;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.metadata.Type;
import java.util.Date;

public class DateConverter extends CustomConverter<Date,String> {

    @Override
    public String convert(Date date, Type<? extends String> type, MappingContext mappingContext) {
        DateTime time = DateTime.of(date);
        return time.toString("yyyy-MM-dd HH:mm:ss");
    }
}

主函數:

import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.example.converter.DateConverter;
import org.example.entity.Person;
import org.example.entity.PersonDTO;
import java.util.Date;

public class MainB {
    public static void main(String[] args) {
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        ConverterFactory converterFactory = mapperFactory.getConverterFactory(); // 註冊 converter
        converterFactory.registerConverter("DateConverterId", new DateConverter()); // 這裏給 DateConverter 設置一個 id 為 DateConverterId,如果不設置,則為全局註冊
        mapperFactory.classMap(Person.class, PersonDTO.class)
                .field("age", "dtoAge")
                .fieldMap("birth", "dtoBirth").converter("DateConverterId").add()
                .byDefault()
                .register();
        Person person = new Person();
        person.setAge("789");
        person.setName("王五");
        person.setBirth(new Date()); // 設置 Date
        BoundMapperFacade<Person, PersonDTO> boundMapper =
                mapperFactory.getMapperFacade(Person.class, PersonDTO.class);
        PersonDTO personDTO = boundMapper.map(person);
        System.out.println(personDTO);
    }
}

// 輸出 PersonDTO(name=王五, dtoAge=789, dtoBirth=2021-11-29 20:34:21)

此時可以發現,自定義的轉換器已經生效。

小結

不難發現,上面的 MapperFactory 在實際的項目開發中,應該定義為單例,由全局來共享一個 MapperFactory,官方文檔中也有相關説明,感興趣可以查看文檔,以上就是有關 Orika 的分享,歡迎交流,共同進步。

更多用法

更多用法請參考官方文檔:

文檔地址: http://orika-mapper.github.io/orika-docs/index.html

Github: https://github.com/orika-mapper?language=html

筆記下載

此文章系原創,轉載請附上鍊接,抱拳。

此文檔提供 markdown 源文件下載,請去我的碼雲倉庫進行下載。 下載文檔

若本文對你有用,請不要忘記給我的點個 Star 哦!