Java bean常見映射工具分析和比較


1. 概述

日常Java開發項目中,我們經常需要將對象轉換成其他形式的對象,因此我們需要編寫映射代碼將對象中的屬性值從一種類型轉換成另一種類型。

進行這種轉換除了手動編寫大量的get/set代碼,還可以使用一些方便的類庫:

  apache的BeanUtils

  spring的BeanUtils

  cglib的BeanCopier

2.比較

2.1 BeanUtils

BeanUtils一套開發包,Apache公司提供 ,專門進行javabean操作,在web層各種框架中被使用,例如:struts 使用BeanUtils操作JavaBean 。

實例
1、下載BeanUtils的jar :commons-beanutils 、commons-logging,需要同時下載兩個jar包。(BeanUtils依賴Logging的jar包 )
2、將beanutils和logging的 jar包復制 工程/WebContent/WEB-INF/lib

apache的BeanUtils和spring的BeanUtils中拷貝方法的原理都是先用jdk中 java.beans.Introspector類的getBeanInfo()方法獲取對象的屬性信息及屬性get/set方法,接着使用反射(Methodinvoke(Object obj, Object... args))方法進行賦值。apache支持名稱相同但類型不同的屬性的轉換,spring支持忽略某些屬性不進行映射,他們都設置了緩存保存已解析過的BeanInfo信息。

commons.beanutils-1.8.3.jar

spring.beans-4.2.3.RELEASE.jar

<dependency>   <groupId>commons-beanutils</groupId>   <artifactId>commons-beanutils</artifactId>   <version>1.9.3</version> </dependency>

import org.apache.commons.beanutils.BeanUtils;

public class TestBeanUtils extends HttpServlet {
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //1.設置編碼
      req.setCharacterEncoding("utf-8");
     //2.獲取數據
      Map<String, String[]> params = req.getParameterMap();
     //System.out.println(params);
      //3.使用BeanUtils工具類封裝User對象
       Users user = new Users();
try {
      BeanUtils.populate(user, params);
} catch (IllegalAccessException e) {
     e.printStackTrace();
} catch (InvocationTargetException e) {
     e.printStackTrace();
}
System.out.println(user);


}

     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           this.doPost(req, resp);
     }
}

 

2.2 BeanCopier

cglib的BeanCopier采用了不同的方法:它不是利用反射對屬性進行賦值,而是直接使用ASM的MethodVisitor直接編寫各屬性的get/set方法(具體過程可見BeanCopier類的generateClass(ClassVisitor v)方法)生成class文件,然后進行執行。由於是直接生成字節碼執行,所以BeanCopier的性能較采用反射的BeanUtils有較大提高,這一點可在后面的測試中看出。

 

 

2.3 Dozer

使用以上類庫雖然可以不用手動編寫get/set方法,但是他們都不能對不同名稱的對象屬性進行映射。在定制化的屬性映射方面做得比較好的有Dozer,Dozer支持簡單屬性映射、復雜類型映射、雙向映射、隱式映射以及遞歸映射。可使用xml或者注解進行映射的配置,支持自動類型轉換,使用方便。但Dozer底層是使用reflect包下Field類的set(Object obj, Object value)方法進行屬性賦值,執行速度上不是那么理想。

2.4 Orika

那么有沒有特性豐富,速度又快的Bean映射工具呢,這就是下面要介紹的Orika,Orika是近期在github活躍的項目,底層采用了javassist類庫生成Bean映射的字節碼,之后直接加載執行生成的字節碼文件,因此在速度上比使用反射進行賦值會快很多,下面詳細介紹Orika的使用方法。

3. Orika使用

依賴

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

簡單映射

  1. 構造一個MapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 
  1. 注冊字段映射
mapperFactory.classMap(PersonSource.class, PersonDestination.class) .field("firstName", "givenName") .field("lastName", "sirName") .byDefault() .register(); 
  1. 進行映射
MapperFacade mapper = mapperFactory.getMapperFacade(); PersonSource source = new PersonSource(); // set some field values ... // map the fields of 'source' onto a new instance of PersonDest PersonDest destination = mapper.map(source, PersonDest.class); 

在第二步進行的字段映射是雙向的,我們可以從目標類型映射回源類型,byDefault()方法用於注冊名稱相同的屬性(如果所有屬性名稱都相同則可以省略第2步),如果不希望某個字段參與映射,可以使用exclude方法

復雜映射

數組和List的映射

如果在目標類和目的類中分別有下面的屬性

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(); 

類類型的映射

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(); 

自定義轉換器

orika同樣支持自定義轉換器,將指定類型或指定名稱的屬性做映射時添加自定義操作,例如,將String類型的或某個屬性映射后加一個前綴,或者將Integer類型映射后加1等:

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 //example:source + 1; } } 

Date為源類型中要做轉換的屬性數據類型,例如StringInteger等,MyDate為目標類型中要做轉換的屬性數據類型。

如果需要定義全局范圍的轉換:

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

如果僅需要某幾個屬性使用轉換器:

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

其他說明

  1. Orika支持遞歸映射,將映射嵌套類直到用“簡單”類型完成映射。它還包含故障保險,以正確處理正在嘗試映射的對象中的遞歸引用。

  2. 在於spring集成時,可以將MapperFactory設置為單例

各映射工具的性能測試

構造一個包含普通類型及類類型的Bean對象,使用jmh微基准框架進行測試。由於jvm會對熱點代碼進行優化:方法反射調用次數超過閾值時會生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼進行執行。

故測試時每種方法先預熱執行15次,而后再執行100次獲取每次執行的平均時間:

Benchmark                     Mode  Samples   Score  Score error  Units  
o.s.MyBenchmark.apache        avgt      100  25.246        0.535  us/op  
o.s.MyBenchmark.beanCopier    avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.byHand        avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.dozer         avgt      100   5.855        0.260  us/op  
o.s.MyBenchmark.orika         avgt      100   0.353        0.017  us/op  
o.s.MyBenchmark.spring        avgt      100   0.627        0.020  us/op  

統計報告中Units單位為微秒/次,由Score項可以看出,基於ASM的cglib BeanCopier拷貝速度基本和手寫get/set方法的速度無異,其次的就是基於javassist的Orika了,Orika的速度是spring BeanUtils的兩倍,Dozer的20倍,Apache BeanUtils的120倍。

綜上,當屬性名和屬性類型完全相同時使用BeanCopier是最好的選擇,當存在屬性名稱不同或者屬性名稱相同但屬性類型不同的情況時,使用Orika是一種不錯的選擇。如果你對Orika感到不放心,實際應用前可以寫個測試類查看它的轉換結果是否符合預期。



  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM