java signature 性能_Java常见bean mapper的性能及原理分析
背景
在分層的代碼架構(gòu)中,層與層之間的對象避免不了要做很多轉(zhuǎn)換、賦值等操作,這些操作重復且繁瑣,于是乎催生出很多工具來優(yōu)雅,高效地完成這個操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文將講述上面幾個工具的使用、性能對比及原理分析。
性能分析
其實這幾個工具要做的事情很簡單,而且在使用上也是類似的,所以我覺得先給大家看看性能分析的對比結(jié)果,讓大家有一個大概的認識。我是使用JMH來做性能分析的,代碼如下:
要復制的對象比較簡單,包含了一些基本類型;有一次warmup,因為一些工具是需要“預編譯”和做緩存的,這樣做對比才會比較客觀;分別復制1000、10000、100000個對象,這是比較常用數(shù)量級了吧。
@BenchmarkMode(Mode.AverageTime)??@OutputTimeUnit(TimeUnit.MICROSECONDS)??@Fork(1)??@Warmup(iterations?=1)??@State(Scope.Benchmark)publicclassBeanMapperBenchmark{@Param({"1000","10000","100000"})privateinttimes;privateinttime;privatestaticMapperFactory?mapperFactory;privatestaticMapper?mapper;static{??????????mapperFactory?=newDefaultMapperFactory.Builder().build();??????????mapperFactory.classMap(SourceVO.class,?TargetVO.class)??????????????????.byDefault()??????????????????.register();????????????mapper?=?DozerBeanMapperBuilder.create()??????????????????.withMappingBuilder(newBeanMappingBuilder()?{??????????????????????@Overrideprotectedvoidconfigure()?{??????????????????????????mapping(SourceVO.class,?TargetVO.class)??????????????????????????????????.fields("fullName","name")??????????????????????????????????.exclude("in");??????????????????????}??????????????????}).build();??????}publicstaticvoidmain(String[]?args)throws?Exception{??????????Options?options?=newOptionsBuilder()??????????????????.include(BeanMapperBenchmark.class.getName()).measurementIterations(3)??????????????????.build();newRunner(options).run();??????}????????@Setuppublicvoidprepare(){this.time?=?times;??????}????????@BenchmarkpublicvoidspringBeanUtilTest(){??????????SourceVO?sourceVO?=?getSourceVO();for(inti?=0;?i?
在我macbook下運行后的結(jié)果如下:
圖片
Score表示的是平均運行時間,單位是微秒。從執(zhí)行效率來看,可以看出 beanCopier > orika > springBeanUtil > dozer > apacheBeanUtil。這樣的結(jié)果跟它們各自的實現(xiàn)原理有很大的關(guān)系,
下面將詳細每個工具的使用及實現(xiàn)原理。
Spring的BeanUtils
使用
這個工具可能是大家日常使用最多的,因為是Spring自帶的,使用也簡單:BeanUtils.copyProperties(sourceVO, targetVO);
原理
Spring BeanUtils的實現(xiàn)原理也比較簡答,就是通過Java的Introspector獲取到兩個類的PropertyDescriptor,對比兩個屬性具有相同的名字和類型,如果是,則進行賦值(通過ReadMethod獲取值,通過WriteMethod賦值),否則忽略。
為了提高性能Spring對BeanInfo和PropertyDescriptor進行了緩存。
(源碼基于:org.springframework:spring-beans:4.3.9.RELEASE)
/**????*?Copy?the?property?values?of?the?given?source?bean?into?the?given?target?bean.????*
Note:?The?source?and?target?classes?do?not?have?to?match?or?even?be?derived????*?from?each?other,?as?long?as?the?properties?match.?Any?bean?properties?that?the????*?source?bean?exposes?but?the?target?bean?does?not?will?silently?be?ignored.????*@param?source?the?source?bean????*@param?target?the?target?bean????*@param?editable?the?class?(or?interface)?to?restrict?property?setting?to????*@param?ignoreProperties?array?of?property?names?to?ignore????*@throws?BeansException?if?the?copying?failed????*@see?BeanWrapper????*/privatestaticvoidcopyProperties(Objectsource,Objecttarget,?Class?editable,String...?ignoreProperties)?????throws?BeansException?{??????Assert.notNull(source,"Source?must?not?be?null");????Assert.notNull(target,"Target?must?not?be?null");??????Class?actualEditable?=?target.getClass();if(editable?!=null)?{if(!editable.isInstance(target))?{thrownewIllegalArgumentException("Target?class?["+?target.getClass().getName()?+"]?not?assignable?to?Editable?class?["+?editable.getName()?+"]");?????}?????actualEditable?=?editable;????}//獲取target類的屬性(有緩存)??PropertyDescriptor[]?targetPds?=?getPropertyDescriptors(actualEditable);????List?ignoreList?=?(ignoreProperties?!=null??Arrays.asList(ignoreProperties)?:null);for(PropertyDescriptor?targetPd?:?targetPds)?{?????Method?writeMethod?=?targetPd.getWriteMethod();if(writeMethod?!=null&&?(ignoreList?==null||?!ignoreList.contains(targetPd.getName())))?{//獲取source類的屬性(有緩存)??PropertyDescriptor?sourcePd?=?getPropertyDescriptor(source.getClass(),?targetPd.getName());if(sourcePd?!=null)?{???????Method?readMethod?=?sourcePd.getReadMethod();if(readMethod?!=null&&//判斷target的setter方法入?yún)⒑蛃ource的getter方法返回類型是否一致??ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],?readMethod.getReturnType()))?{try{if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))?{??????????readMethod.setAccessible(true);?????????}//獲取源值??Objectvalue?=?readMethod.invoke(source);if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))?{??????????writeMethod.setAccessible(true);?????????}//賦值到target??writeMethod.invoke(target,?value);????????}catch(Throwable?ex)?{thrownewFatalBeanException("Could?not?copy?property?'"+?targetPd.getName()?+"'?from?source?to?target",?ex);????????}???????}??????}?????}????}???}
小結(jié)
Spring BeanUtils的實現(xiàn)就是這么簡潔,這也是它性能比較高的原因。
不過,過于簡潔就失去了靈活性和可擴展性了,Spring BeanUtils的使用限制也比較明顯,要求類屬性的名字和類型一致,這點在使用時要注意。
Apache的BeanUtils
使用
Apache的BeanUtils和Spring的BeanUtils的使用是一樣的:
BeanUtils.copyProperties(targetVO,sourceVO);
要注意,source和target的入?yún)⑽恢貌煌?/p>
原理
Apache的BeanUtils的實現(xiàn)原理跟Spring的BeanUtils一樣,也是主要通過Java的Introspector機制獲取到類的屬性來進行賦值操作,對BeanInfo和PropertyDescriptor同樣有緩存,但是Apache BeanUtils加了一些不那么使用的特性(包括支持Map類型、支持自定義的DynaBean類型、支持屬性名的表達式等等)在里面,使得性能相對Spring的BeanUtils來說有所下降。
(源碼基于:commons-beanutils:commons-beanutils:1.9.3)
publicvoidcopyProperties(finalObjectdest,?finalObjectorig)??????????throws?IllegalAccessException,?InvocationTargetException?{if(dest?==null)?{thrownewIllegalArgumentException??????????????????????("No?destination?bean?specified");??????????}if(orig?==null)?{thrownewIllegalArgumentException("No?origin?bean?specified");??????????}if(log.isDebugEnabled())?{??????????????log.debug("BeanUtils.copyProperties("+?dest?+",?"+????????????????????????orig?+")");??????????}//?Apache?Common自定義的DynaBean??if(originstanceofDynaBean)?{??????????????final?DynaProperty[]?origDescriptors?=??????????????????((DynaBean)?orig).getDynaClass().getDynaProperties();for(DynaProperty?origDescriptor?:?origDescriptors)?{??????????????????finalStringname?=?origDescriptor.getName();//?Need?to?check?isReadable()?for?WrapDynaBean??//?(see?Jira?issue#?BEANUTILS-61)??if(getPropertyUtils().isReadable(orig,?name)?&&??????????????????????getPropertyUtils().isWriteable(dest,?name))?{??????????????????????finalObjectvalue?=?((DynaBean)?orig).get(name);??????????????????????copyProperty(dest,?name,?value);??????????????????}??????????????}//?Map類型??}elseif(originstanceofMap)?{??????????????@SuppressWarnings("unchecked")??????????????final//?Map?properties?are?always?of?type???Map?propMap?=?(Map)?orig;for(finalMap.Entry?entry?:?propMap.entrySet())?{??????????????????finalStringname?=?entry.getKey();if(getPropertyUtils().isWriteable(dest,?name))?{??????????????????????copyProperty(dest,?name,?entry.getValue());??????????????????}??????????????}//?標準的JavaBean??}else{??????????????final?PropertyDescriptor[]?origDescriptors?=//獲取PropertyDescriptor??getPropertyUtils().getPropertyDescriptors(orig);for(PropertyDescriptor?origDescriptor?:?origDescriptors)?{??????????????????finalStringname?=?origDescriptor.getName();if("class".equals(name))?{continue;//?No?point?in?trying?to?set?an?object's?class??}//是否可讀和可寫??if(getPropertyUtils().isReadable(orig,?name)?&&??????????????????????getPropertyUtils().isWriteable(dest,?name))?{try{//獲取源值??finalObjectvalue?=??????????????????????????????getPropertyUtils().getSimpleProperty(orig,?name);//賦值操作??copyProperty(dest,?name,?value);??????????????????????}catch(final?NoSuchMethodException?e)?{//?Should?not?happen??}??????????????????}??????????????}??????????}????????}
小結(jié)
Apache BeanUtils的實現(xiàn)跟Spring BeanUtils總體上類似,但是性能卻低很多,這個可以從上面性能比較看出來。阿里的Java規(guī)范是不建議使用的。
BeanCopier
使用
BeanCopier在cglib包里,它的使用也比較簡單:
@TestpublicvoidbeanCopierSimpleTest(){??????????SourceVO?sourceVO?=?getSourceVO();??????????log.info("source={}",?GsonUtil.toJson(sourceVO));??????????TargetVO?targetVO?=newTargetVO();??????????BeanCopier?bc?=?BeanCopier.create(SourceVO.class,TargetVO.class,false);??????????bc.copy(sourceVO,?targetVO,null);??????????log.info("target={}",?GsonUtil.toJson(targetVO));??????}
只需要預先定義好要轉(zhuǎn)換的source類和target類就好了,可以選擇是否使用Converter,這個下面會說到。
在上面的性能測試中,BeanCopier是所有中表現(xiàn)最好的,那么我們分析一下它的實現(xiàn)原理。
原理
BeanCopier的實現(xiàn)原理跟BeanUtils截然不同,它不是利用反射對屬性進行賦值,而是直接使用cglib來生成帶有的get/set方法的class類,然后執(zhí)行。由于是直接生成字節(jié)碼執(zhí)行,所以BeanCopier的性能接近手寫
get/set。
BeanCopier.create方法
publicstaticBeanCopiercreate(Class?source,?Class?target,booleanuseConverter){??????????Generator?gen?=newGenerator();??????????gen.setSource(source);??????????gen.setTarget(target);??????????gen.setUseConverter(useConverter);returngen.create();??????}publicBeanCopiercreate(){??????????????Object?key?=?KEY_FACTORY.newInstance(source.getName(),?target.getName(),?useConverter);return(BeanCopier)super.create(key);??????????}
這里的意思是用KEY_FACTORY創(chuàng)建一個BeanCopier出來,然后調(diào)用create方法來生成字節(jié)碼。
KEY_FACTORY其實就是用cglib通過BeanCopierKey接口生成出來的一個類
privatestaticfinalBeanCopierKey?KEY_FACTORY?=????????(BeanCopierKey)KeyFactory.create(BeanCopierKey.class);interfaceBeanCopierKey{publicObjectnewInstance(String?source,?String?target,booleanuseConverter);??????}
通過設(shè)置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,?"path");
可以讓cglib輸出生成類的class文件,我們可以反復譯看看里面的代碼
下面是KEY_FACTORY的類
publicclassBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fdextendsKeyFactoryimplementsBeanCopierKey{privatefinalString?FIELD_0;privatefinalString?FIELD_1;privatefinalbooleanFIELD_2;publicBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd()?{??????}publicObjectnewInstance(String?var1,?String?var2,booleanvar3){returnnewBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(var1,?var2,?var3);??????}publicBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(String?var1,?String?var2,booleanvar3)?{this.FIELD_0?=?var1;this.FIELD_1?=?var2;this.FIELD_2?=?var3;??????}//省去hashCode等方法。。。??}
繼續(xù)跟蹤Generator.create方法,由于Generator是繼承AbstractClassGenerator,這個AbstractClassGenerator是cglib用來生成字節(jié)碼的一個模板類,Generator的super.create其實調(diào)用
AbstractClassGenerator的create方法,最終會調(diào)用到Generator的模板方法generateClass方法,我們不去細究AbstractClassGenerator的細節(jié),重點看generateClass。
這個是一個生成java類的方法,理解起來就好像我們平時寫代碼一樣。
publicvoidgenerateClass(ClassVisitor?v)?{??????????????Type?sourceType?=?Type.getType(source);??????????????Type?targetType?=?Type.getType(target);??????????????ClassEmitter?ce?=?new?ClassEmitter(v);//開始“寫”類,這里有修飾符、類名、父類等信息??ce.begin_class(Constants.V1_2,?????????????????????????????Constants.ACC_PUBLIC,?????????????????????????????getClassName(),?????????????????????????????BEAN_COPIER,?????????????????????????????null,?????????????????????????????Constants.SOURCE_FILE);//沒有構(gòu)造方法??EmitUtils.null_constructor(ce);//開始“寫”一個方法,方法名是copy??CodeEmitter?e?=?ce.begin_method(Constants.ACC_PUBLIC,?COPY,?null);//通過Introspector獲取source類和target類的PropertyDescriptor??PropertyDescriptor[]?getters?=?ReflectUtils.getBeanGetters(source);??????????????PropertyDescriptor[]?setters?=?ReflectUtils.getBeanSetters(target);????????????????????????????Map?names?=?new?HashMap();for(inti?=0;?i?
即使沒有使用過cglib也能讀懂生成代碼的流程吧,我們看看沒有使用useConverter的情況下生成的代碼:
publicclassObject$$BeanCopierByCGLIB$$d1d970c8extendsBeanCopier?{publicObject$$BeanCopierByCGLIB$$d1d970c8()?{??????}publicvoidcopy(Objectvar1,Objectvar2,?Converter?var3)?{??????????TargetVO?var10000?=?(TargetVO)var2;??????????SourceVO?var10001?=?(SourceVO)var1;??????????var10000.setDate1(((SourceVO)var1).getDate1());??????????var10000.setIn(var10001.getIn());??????????var10000.setListData(var10001.getListData());??????????var10000.setMapData(var10001.getMapData());??????????var10000.setP1(var10001.getP1());??????????var10000.setP2(var10001.getP2());??????????var10000.setP3(var10001.getP3());??????????var10000.setPattr1(var10001.getPattr1());??????}??}
在對比上面生成代碼的代碼是不是豁然開朗了。
再看看使用useConverter的情況:
public?class?Object$$BeanCopierByCGLIB$$d1d970c7?extends?BeanCopier?{??????private?static?final?Class?CGLIB$load_class$java$2Eutil$2EDate;??????private?static?final?Class?CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner;??????private?static?final?Class?CGLIB$load_class$java$2Eutil$2EList;??????private?static?final?Class?CGLIB$load_class$java$2Eutil$2EMap;??????private?static?final?Class?CGLIB$load_class$java$2Elang$2EInteger;??????private?static?final?Class?CGLIB$load_class$java$2Elang$2ELong;??????private?static?final?Class?CGLIB$load_class$java$2Elang$2EByte;??????private?static?final?Class?CGLIB$load_class$java$2Elang$2EString;????????public?Object$$BeanCopierByCGLIB$$d1d970c7()?{??????}????????public?void?copy(Object?var1,?Object?var2,?Converter?var3)?{??????????TargetVO?var4?=?(TargetVO)var2;??????????SourceVO?var5?=?(SourceVO)var1;??????????var4.setDate1((Date)var3.convert(var5.getDate1(),?CGLIB$load_class$java$2Eutil$2EDate,"setDate1"));??????????var4.setIn((Inner)var3.convert(var5.getIn(),?CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner,"setIn"));??????????var4.setListData((List)var3.convert(var5.getListData(),?CGLIB$load_class$java$2Eutil$2EList,"setListData"));??????????var4.setMapData((Map)var3.convert(var5.getMapData(),?CGLIB$load_class$java$2Eutil$2EMap,"setMapData"));??????????var4.setP1((Integer)var3.convert(var5.getP1(),?CGLIB$load_class$java$2Elang$2EInteger,"setP1"));??????????var4.setP2((Long)var3.convert(var5.getP2(),?CGLIB$load_class$java$2Elang$2ELong,"setP2"));??????????var4.setP3((Byte)var3.convert(var5.getP3(),?CGLIB$load_class$java$2Elang$2EByte,"setP3"));??????????var4.setPattr1((String)var3.convert(var5.getPattr1(),?CGLIB$load_class$java$2Elang$2EString,"setPattr1"));??????????var4.setSeq((Long)var3.convert(var5.getSeq(),?CGLIB$load_class$java$2Elang$2ELong,"setSeq"));??????}????????static?void?CGLIB$STATICHOOK1()?{??????????CGLIB$load_class$java$2Eutil$2EDate?=?Class.forName("java.util.Date");??????????CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner?=?Class.forName("beanmapper_compare.vo.SourceVO$Inner");??????????CGLIB$load_class$java$2Eutil$2EList?=?Class.forName("java.util.List");??????????CGLIB$load_class$java$2Eutil$2EMap?=?Class.forName("java.util.Map");??????????CGLIB$load_class$java$2Elang$2EInteger?=?Class.forName("java.lang.Integer");??????????CGLIB$load_class$java$2Elang$2ELong?=?Class.forName("java.lang.Long");??????????CGLIB$load_class$java$2Elang$2EByte?=?Class.forName("java.lang.Byte");??????????CGLIB$load_class$java$2Elang$2EString?=?Class.forName("java.lang.String");??????}????????static?{??????????CGLIB$STATICHOOK1();??????}??}
小結(jié)
BeanCopier性能確實很高,但從源碼可以看出BeanCopier只會拷貝名稱和類型都相同的屬性,而且如果一旦使用Converter,BeanCopier只使用Converter定義的規(guī)則去拷貝屬性,所以在convert方法中要考慮所有的屬性。
Dozer
使用
上面提到的BeanUtils和BeanCopier都是功能比較簡單的,需要屬性名稱一樣,甚至類型也要一樣。但是在大多數(shù)情況下這個要求就相對苛刻了,要知道有些VO由于各種原因不能修改,有些是外部接口SDK的對象,
有些對象的命名規(guī)則不同,例如有駝峰型的,有下劃線的等等,各種什么情況都有。所以我們更加需要的是更加靈活豐富的功能,甚至可以做到定制化的轉(zhuǎn)換。
Dozer就提供了這些功能,有支持同名隱式映射,支持基本類型互相轉(zhuǎn)換,支持顯示指定映射關(guān)系,支持exclude字段,支持遞歸匹配映射,支持深度匹配,支持Date to String的date-formate,支持自定義轉(zhuǎn)換Converter,支持一次mapping定義多處使用,支持EventListener事件監(jiān)聽等等。不僅如此,Dozer在使用方式上,除了支持API,還支持XML和注解,滿足大家的喜好。更多的功能可以參考這里
由于其功能很豐富,不可能每個都演示,這里只是給個大概認識,更詳細的功能,或者XML和注解的配置,請看官方文檔。
privateMapper?dozerMapper;????????@Beforepublicvoidsetup(){??????????dozerMapper?=?DozerBeanMapperBuilder.create()??????????????????.withMappingBuilder(newBeanMappingBuilder()?{??????????????????????@Overrideprotectedvoidconfigure()?{??????????????????????????mapping(SourceVO.class,?TargetVO.class)??????????????????????????????????.fields("fullName","name")??????????????????????????????????.exclude("in");??????????????????????}??????????????????})??????????????????.withCustomConverter(null)??????????????????.withEventListener(null)??????????????????.build();??????}????????????@TestpublicvoiddozerTest(){??????????SourceVO?sourceVO?=?getSourceVO();log.info("sourceVO={}",?GsonUtil.toJson(sourceVO));??????????TargetVOmap=?dozerMapper.map(sourceVO,?TargetVO.class);log.info("map={}",?GsonUtil.toJson(map));??????}
總結(jié)
以上是生活随笔為你收集整理的java signature 性能_Java常见bean mapper的性能及原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 元日是谁写的呢?
- 下一篇: java socket 包头包体_自定义