關於BeanCopier的一些思考

在作業務的時候,咱們有時爲了隔離變化,會將DAO查詢出來的Entity,和對外提供的DTO隔離開來。大概90%的時候,它們的結構都是相似的,可是咱們很不喜歡寫不少冗長的b.setF1(a.getF1())這樣的代碼,因而咱們須要BeanCopier來幫助咱們。html

BeanCopier其實已經有不少開源版本,例如DozerMapperApache BeanUtilsSpringJodd BeanUtils甚至是Cglib都提供了這樣的功能。在比較這些工具以前,我想先提提我對BeanCopier的一些要求。java

1. 性能

BeanCopier是一個很經常使用的操做,若是是一個批量的請求,就更加明顯了。使用效率過低的庫不太划算,我對這些工具作了一個對比:Copy一個簡單Bean 1,000,000次,計算總耗時(測試代碼在這裏)。比較結果以下:git

1,000,000 round
jdk set/get takes 17ms
cglib takes 117ms
jodd takes 5309ms
dozer mapper takes 2336ms
apche beanutils takes 6264ms

其中jdk的直接寫set/get是最快的,因此在性能要求高的場景下卻是不妨本身寫。另外這樣寫也是對重構比較友好,這是其餘幾個工具都作不到的。github

其次是用了字節碼生成的cglib,而後將其餘的庫遠遠甩在後面。其餘的庫性能相差不大,大約1000次拷貝會消耗數毫秒時間,對於性能敏感的應用,特別是一些批量請求,消耗仍是比較大的。spring

2. 內聚性

其實Bean Copy能夠擴展到更通常的狀況:咱們須要對兩個相似的Bean作轉換,輸入是一個Bean,輸出是另一個相似的Bean。這種邏輯裏,除了簡單的字段拷貝,可能也會有一些計算邏輯,甚至還會依賴一些外部數據源,而咱們還但願最好把轉換的邏輯都放在一塊兒,同時也起到規範業務的做用。數據庫

DozerMapper在這條路上走的很遠。它經過XML/API/Annotation的方式,支持簡單形式的轉換、映射,從而更好的處理一些字段不同的狀況,用意就是一個Mapper搞定一切。例以下面的例子,能夠將不一樣名稱的字段進行映射。apache

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">
          
  <mapping> 
    <class-a>org.dozer.vo.TestObject</class-a>
    <class-b>org.dozer.vo.TestObjectPrime</class-b>   
    <field>
      <a>one</a>
      <b>onePrime</b>
    </field>
  </mapping>  

  <mapping wildcard="false"> 
    <class-a>org.dozer.vo.TestObjectFoo</class-a>
    <class-b>org.dozer.vo.TestObjectFooPrime</class-b>   
      <field>
        <a>oneFoo</a>
        <b>oneFooPrime</b>
      </field>
  </mapping>  

</mappings>

可是,假設咱們的場景不是須要整合不少項目,而是本身制定規範和數據模型,這時咱們真的須要這樣的轉換麼?我認爲一開始就應該把相同的字段給予相同的名字,這樣不管是對於理解、後續維護都會方便不少。即便這種不一樣名的狀況存在,咱們也不該該提倡。因此花這麼大的力氣去作字段的映射,增長了複雜度,我認爲並不划算。這個時候,咱們須要的是僅僅對同名字段進行拷貝,其餘屬性交由手動處理app

至此,一個BeanCopier就大致成型了:ide

<!-- lang: java -->
public class BeanCopier<F,T> {

private net.sf.cglib.beans.BeanCopier beanCopier;

protected net.sf.cglib.beans.BeanCopier getBeanCopier() {
	return beanCopier;
}

protected void init(){
	this.beanCopier = net.sf.cglib.beans.BeanCopier.create(sourceClass, targetClass, false);
}

private Class<T> targetClass;

private Class<F> sourceClass;

protected Class<T> getTargetClass() {
	return targetClass;
}

protected Class<F> getSourceClass() {
	return sourceClass;
}

public void setTargetClass(Class<T> targetClass) {
	this.targetClass = targetClass;
}

public void setSourceClass(Class<F> sourceClass) {
	this.sourceClass = sourceClass;
}

public T afterCopy(F source, T target){
	return target;
}

public T copy(F input) {
	try {
		T o = targetClass.newInstance();
		beanCopier.copy(input, o, null);
		return afterCopy(input, o);
	} catch (Exception e) {
		throw new RuntimeException("create object fail, class:" + targetClass.getName() + " ", e);
	}
}

@Override
public T apply(F input) {
	return copy(input);
}

}工具

另外,不少狀況下,咱們不止是對字段值進行拷貝,還會有一些數據轉換的須要。例如:將Entity的瘦模型中關聯的一些數據,從簡單的數據庫關聯外鍵變爲一個完整的Entity,最後再整合成一個DTO。

這種狀況下,咱們的BeanCopyier還須要一些外部數據。在Spring中,咱們會但願它去依賴DAO或者外部Service之類的Bean。因而咱們還能夠用Spring來配置它。

@Service
public class A2BBeanCopier extends BeanCopier<A,B> {

	@PostConstruct
	public void init(){
		setSourceClass(A.class);
		setTargetClass(B.class);
		super.init();
	}

	@Override
	public B afterCopy(A source, B target) {
		target.setF5("aaa");
		//Call some service
		return target;
	}
}

最後,項目我放到了oscgit上:http://git.oschina.net/flashsword20/abc

相關文章
相關標籤/搜索