來源:https://albenw.github.io/posts/f6a7daea/
背景
在分層的代碼架構中,層與層之間的對象避免不了要做很多轉換、賦值等操作,這些操作重復且繁瑣,於是乎催生出很多工具來優雅,高效地完成這個操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文將講述上面幾個工具的使用、性能對比及原理分析。
性能分析
其實這幾個工具要做的事情很簡單,而且在使用上也是類似的,所以我覺得先給大家看看性能分析的對比結果,讓大家有一個大概的認識。我是使用JMH
來做性能分析的,代碼如下:
要復制的對象比較簡單,包含了一些基本類型;有一次warmup,因為一些工具是需要“預編譯”和做緩存的,這樣做對比才會比較客觀;分別復制1000、10000、100000個對象,這是比較常用數量級了吧。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(1)
@Warmup(iterations = 1)
@State(Scope.Benchmark)
public class BeanMapperBenchmark {
@Param({"1000", "10000", "100000"})
private int times;
private int time;
private static MapperFactory mapperFactory;
private static Mapper mapper;
static {
mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(SourceVO.class, TargetVO.class)
.byDefault()
.register();
mapper = DozerBeanMapperBuilder.create()
.withMappingBuilder(new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(SourceVO.class, TargetVO.class)
.fields("fullName", "name")
.exclude("in");
}
}).build();
}
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder()
.include(BeanMapperBenchmark.class.getName()).measurementIterations(3)
.build();
new Runner(options).run();
}
@Setup
public void prepare() {
this.time = times;
}
@Benchmark
public void springBeanUtilTest(){
SourceVO sourceVO = getSourceVO();
for(int i = 0; i < time; i++){
TargetVO targetVO = new TargetVO();
BeanUtils.copyProperties(sourceVO, targetVO);
}
}
@Benchmark
public void apacheBeanUtilTest() throws Exception{
SourceVO sourceVO = getSourceVO();
for(int i = 0; i < time; i++){
TargetVO targetVO = new TargetVO();
org.apache.commons.beanutils.BeanUtils.copyProperties(targetVO, sourceVO);
}
}
@Benchmark
public void beanCopierTest(){
SourceVO sourceVO = getSourceVO();
for(int i = 0; i < time; i++){
TargetVO targetVO = new TargetVO();
BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);
bc.copy(sourceVO, targetVO, null);
}
}
@Benchmark
public void dozerTest(){
SourceVO sourceVO = getSourceVO();
for(int i = 0; i < time; i++){
TargetVO map = mapper.map(sourceVO, TargetVO.class);
}
}
@Benchmark
public void orikaTest(){
SourceVO sourceVO = getSourceVO();
for(int i = 0; i < time; i++){
MapperFacade mapper = mapperFactory.getMapperFacade();
TargetVO map = mapper.map(sourceVO, TargetVO.class);
}
}
private SourceVO getSourceVO(){
SourceVO sourceVO = new SourceVO();
sourceVO.setP1(1);
sourceVO.setP2(2L);
sourceVO.setP3(new Integer(3).byteValue());
sourceVO.setDate1(new Date());
sourceVO.setPattr1("1");
sourceVO.setIn(new SourceVO.Inner(1));
sourceVO.setFullName("alben");
return sourceVO;
}
}
在我macbook下運行后的結果如下:
Score表示的是平均運行時間,單位是微秒。從執行效率來看,可以看出 beanCopier > orika > springBeanUtil > dozer > apacheBeanUtil。這樣的結果跟它們各自的實現原理有很大的關系,
下面將詳細每個工具的使用及實現原理。
Spring的BeanUtils
使用
這個工具可能是大家日常使用最多的,因為是Spring
自帶的,使用也簡單:BeanUtils.copyProperties(sourceVO, targetVO);
原理
Spring BeanUtils的實現原理也比較簡答,就是通過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.
* <p>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
*/
private static void copyProperties(Object source, Object target, 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)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
//獲取target類的屬性(有緩存)
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> 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方法入參和source的getter方法返回類型是否一致
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
//獲取源值
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
//賦值到target
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
小結
Spring BeanUtils的實現就是這么簡潔,這也是它性能比較高的原因。
不過,過於簡潔就失去了靈活性和可擴展性了,Spring BeanUtils的使用限制也比較明顯,要求類屬性的名字和類型一致,這點在使用時要注意。
Apache的BeanUtils
使用
Apache的BeanUtils和Spring的BeanUtils的使用是一樣的:
BeanUtils.copyProperties(targetVO, sourceVO);
要注意,source和target的入參位置不同。
原理
Apache的BeanUtils的實現原理跟Spring的BeanUtils一樣,也是主要通過Java的Introspector
機制獲取到類的屬性來進行賦值操作,對BeanInfo和PropertyDescriptor同樣有緩存,但是Apache BeanUtils加了一些不那么使用的特性(包括支持Map類型、支持自定義的DynaBean類型、支持屬性名的表達式等等)在里面,使得性能相對Spring的BeanUtils來說有所下降。
(源碼基於:commons-beanutils:commons-beanutils:1.9.3)
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
if (dest == null) {
throw new IllegalArgumentException
("No destination bean specified");
}
if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
}
if (log.isDebugEnabled()) {
log.debug("BeanUtils.copyProperties(" + dest + ", " +
orig + ")");
}
// Apache Common自定義的DynaBean
if (orig instanceof DynaBean) {
final DynaProperty[] origDescriptors =
((DynaBean) orig).getDynaClass().getDynaProperties();
for (DynaProperty origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
// Need to check isReadable() for WrapDynaBean
// (see Jira issue# BEANUTILS-61)
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
final Object value = ((DynaBean) orig).get(name);
copyProperty(dest, name, value);
}
}
// Map類型
} else if (orig instanceof Map) {
@SuppressWarnings("unchecked")
final
// Map properties are always of type <String, Object>
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
final String name = 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) {
final String name = 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 {
//獲取源值
final Object value =
getPropertyUtils().getSimpleProperty(orig, name);
//賦值操作
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
}
}
小結
Apache BeanUtils的實現跟Spring BeanUtils總體上類似,但是性能卻低很多,這個可以從上面性能比較看出來。阿里的Java規范是不建議使用的。
BeanCopier
使用
BeanCopier在cglib包里,它的使用也比較簡單:
@Test
public void beanCopierSimpleTest() {
SourceVO sourceVO = getSourceVO();
log.info("source={}", GsonUtil.toJson(sourceVO));
TargetVO targetVO = new TargetVO();
BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);
bc.copy(sourceVO, targetVO, null);
log.info("target={}", GsonUtil.toJson(targetVO));
}
只需要預先定義好要轉換的source類和target類就好了,可以選擇是否使用Converter
,這個下面會說到。
在上面的性能測試中,BeanCopier是所有中表現最好的,那么我們分析一下它的實現原理。
原理
BeanCopier的實現原理跟BeanUtils截然不同,它不是利用反射對屬性進行賦值,而是直接使用cglib來生成帶有的get/set方法的class類,然后執行。由於是直接生成字節碼執行,所以BeanCopier的性能接近手寫
get/set。
BeanCopier.create方法
public static BeanCopier create(Class source, Class target, boolean useConverter) {
Generator gen = new Generator();
gen.setSource(source);
gen.setTarget(target);
gen.setUseConverter(useConverter);
return gen.create();
}
public BeanCopier create() {
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
return (BeanCopier)super.create(key);
}
這里的意思是用KEY_FACTORY創建一個BeanCopier出來,然后調用create方法來生成字節碼。
KEY_FACTORY其實就是用cglib通過BeanCopierKey接口生成出來的一個類
private static final BeanCopierKey KEY_FACTORY = (BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
interface BeanCopierKey {
public Object newInstance(String source, String target, boolean useConverter);
}
通過設置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "path");
可以讓cglib輸出生成類的class文件,我們可以反編譯看看里面的代碼
下面是KEY_FACTORY的類
public class BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd extends KeyFactory implements BeanCopierKey {
private final String FIELD_0;
private final String FIELD_1;
private final boolean FIELD_2;
public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd() {
}
public Object newInstance(String var1, String var2, boolean var3) {
return new BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(var1, var2, var3);
}
public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(String var1, String var2, boolean var3) {
this.FIELD_0 = var1;
this.FIELD_1 = var2;
this.FIELD_2 = var3;
}
//省去hashCode等方法。。。
}
繼續跟蹤Generator.create方法,由於Generator是繼承AbstractClassGenerator,這個AbstractClassGenerator是cglib用來生成字節碼的一個模板類,Generator的super.create其實調用
AbstractClassGenerator的create方法,最終會調用到Generator的模板方法generateClass
方法,我們不去細究AbstractClassGenerator的細節,重點看generateClass。
這個是一個生成java類的方法,理解起來就好像我們平時寫代碼一樣。
public void generateClass(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);
//沒有構造方法
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 (int i = 0; i < getters.length; i++) {
names.put(getters[i].getName(), getters[i]);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
//通過屬性名來生成轉換的代碼
//以setter作為遍歷
for (int i = 0; i < setters.length; i++) {
PropertyDescriptor setter = setters[i];
//根據setter的name獲取getter
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
//獲取讀寫方法
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
//如果用了useConverter,則進行下面的拼裝代碼方式
if (useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0];
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(CONVERTER, CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
//compatible用來判斷getter和setter是否類型一致
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions?
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
即使沒有使用過cglib也能讀懂生成代碼的流程吧,我們看看沒有使用useConverter的情況下生成的代碼:
public class Object$$BeanCopierByCGLIB$$d1d970c8 extends BeanCopier {
public Object$$BeanCopierByCGLIB$$d1d970c8() {
}
public void copy(Object var1, Object var2, 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();
}
}
小結
BeanCopier性能確實很高,但從源碼可以看出BeanCopier只會拷貝名稱和類型都相同的屬性,而且如果一旦使用Converter,BeanCopier只使用Converter定義的規則去拷貝屬性,所以在convert方法中要考慮所有的屬性。
Dozer
使用
上面提到的BeanUtils和BeanCopier都是功能比較簡單的,需要屬性名稱一樣,甚至類型也要一樣。但是在大多數情況下這個要求就相對苛刻了,要知道有些VO由於各種原因不能修改,有些是外部接口SDK的對象,
有些對象的命名規則不同,例如有駝峰型的,有下划線的等等,各種什么情況都有。所以我們更加需要的是更加靈活豐富的功能,甚至可以做到定制化的轉換。
Dozer就提供了這些功能,有支持同名隱式映射,支持基本類型互相轉換,支持顯示指定映射關系,支持exclude字段,支持遞歸匹配映射,支持深度匹配,支持Date to String的date-formate,支持自定義轉換Converter,支持一次mapping定義多處使用,支持EventListener事件監聽等等。不僅如此,Dozer在使用方式上,除了支持API,還支持XML和注解,滿足大家的喜好。更多的功能可以參考這里
由於其功能很豐富,不可能每個都演示,這里只是給個大概認識,更詳細的功能,或者XML和注解的配置,請看官方文檔。
private Mapper dozerMapper;
@Before
public void setup(){
dozerMapper = DozerBeanMapperBuilder.create()
.withMappingBuilder(new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(SourceVO.class, TargetVO.class)
.fields("fullName", "name")
.exclude("in");
}
})
.withCustomConverter(null)
.withEventListener(null)
.build();
}
@Test
public void dozerTest(){
SourceVO sourceVO = getSourceVO();
log.info("sourceVO={}", GsonUtil.toJson(sourceVO));
TargetVO map = dozerMapper.map(sourceVO, TargetVO.class);
log.info("map={}", GsonUtil.toJson(map));
}
原理
Dozer的實現原理本質上還是用反射/Introspector那套,但是其豐富的功能,以及支持多種實現方式(API、XML、注解)使得代碼看上去有點復雜,在翻閱代碼時,我們大可不必理會這些類,只需要知道它們大體的作用就行了,重點關注核心流程和代碼的實現。下面我們重點看看構建mapper的build
方法和實現映射的map
方法。
build方法很簡單,它是一個初始化的動作,就是通過用戶的配置來構建出一系列后面要用到的配置對象、上下文對象,或其他封裝對象,我們不必深究這些對象是怎么實現的,從名字上我們大概能猜出這些對象是干嘛,負責什么就可以了。
DozerBeanMapper(List<String> mappingFiles,
BeanContainer beanContainer,
DestBeanCreator destBeanCreator,
DestBeanBuilderCreator destBeanBuilderCreator,
BeanMappingGenerator beanMappingGenerator,
PropertyDescriptorFactory propertyDescriptorFactory,
List<CustomConverter> customConverters,
List<MappingFileData> mappingsFileData,
List<EventListener> eventListeners,
CustomFieldMapper customFieldMapper,
Map<String, CustomConverter> customConvertersWithId,
ClassMappings customMappings,
Configuration globalConfiguration,
CacheManager cacheManager) {
this.beanContainer = beanContainer;
this.destBeanCreator = destBeanCreator;
this.destBeanBuilderCreator = destBeanBuilderCreator;
this.beanMappingGenerator = beanMappingGenerator;
this.propertyDescriptorFactory = propertyDescriptorFactory;
this.customConverters = new ArrayList<>(customConverters);
this.eventListeners = new ArrayList<>(eventListeners);
this.mappingFiles = new ArrayList<>(mappingFiles);
this.customFieldMapper = customFieldMapper;
this.customConvertersWithId = new HashMap<>(customConvertersWithId);
this.eventManager = new DefaultEventManager(eventListeners);
this.customMappings = customMappings;
this.globalConfiguration = globalConfiguration;
this.cacheManager = cacheManager;
}
map方法是映射對象的過程,其入口是MappingProcessor的mapGeneral方法
private <T> T mapGeneral(Object srcObj, final Class<T> destClass, final T destObj, final String mapId) {
srcObj = MappingUtils.deProxy(srcObj, beanContainer);
Class<T> destType;
T result;
if (destClass == null) {
destType = (Class<T>)destObj.getClass();
result = destObj;
} else {
destType = destClass;
result = null;
}
ClassMap classMap = null;
try {
//構建ClassMap
//ClassMap是包括src類和dest類和其他配置的一個封裝
classMap = getClassMap(srcObj.getClass(), destType, mapId);
//注冊事件
eventManager.on(new DefaultEvent(EventTypes.MAPPING_STARTED, classMap, null, srcObj, result, null));
//看看有沒有自定義converter
Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcObj
.getClass(), destType);
if (destObj == null) {
// If this is a nested MapperAware conversion this mapping can be already processed
// but we can do this optimization only in case of no destObject, instead we must copy to the dest object
Object alreadyMappedValue = mappedFields.getMappedValue(srcObj, destType, mapId);
if (alreadyMappedValue != null) {
return (T)alreadyMappedValue;
}
}
//優先使用自定義converter進行映射
if (converterClass != null) {
return (T)mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);
}
//也是對配置進行了封裝
BeanCreationDirective creationDirective =
new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType,
classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod(),
classMap.getDestClass().isSkipConstructor());
//繼續進行映射
result = createByCreationDirectiveAndMap(creationDirective, classMap, srcObj, result, false, null);
} catch (Throwable e) {
MappingUtils.throwMappingException(e);
}
eventManager.on(new DefaultEvent(EventTypes.MAPPING_FINISHED, classMap, null, srcObj, result, null));
return result;
}
一般情況下createByCreationDirectiveAndMap
方法會一直調用到mapFromFieldMap方法,而在沒有自定義converter的情況下會調用mapOrRecurseObject
方法
大多數情況下字段的映射會在這個方法做一般的解析
private Object mapOrRecurseObject(Object srcObj, Object srcFieldValue, Class<?> destFieldType, FieldMap fieldMap, Object destObj) {
Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMap.getSrcFieldType(srcObj.getClass());
Class<?> converterClass = MappingUtils.determineCustomConverter(fieldMap, converterByDestTypeCache, fieldMap.getClassMap()
.getCustomConverters(), srcFieldClass, destFieldType);
//自定義converter的處理
if (converterClass != null) {
return mapUsingCustomConverter(converterClass, srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMap, false);
}
if (srcFieldValue == null) {
return null;
}
String srcFieldName = fieldMap.getSrcFieldName();
String destFieldName = fieldMap.getDestFieldName();
if (!(DozerConstants.SELF_KEYWORD.equals(srcFieldName) && DozerConstants.SELF_KEYWORD.equals(destFieldName))) {
Object alreadyMappedValue = mappedFields.getMappedValue(srcFieldValue, destFieldType, fieldMap.getMapId());
if (alreadyMappedValue != null) {
return alreadyMappedValue;
}
}
//如果只是淺拷貝則直接返回(可配置)
if (fieldMap.isCopyByReference()) {
// just get the src and return it, no transformation.
return srcFieldValue;
}
//對Map類型的處理
boolean isSrcFieldClassSupportedMap = MappingUtils.isSupportedMap(srcFieldClass);
boolean isDestFieldTypeSupportedMap = MappingUtils.isSupportedMap(destFieldType);
if (isSrcFieldClassSupportedMap && isDestFieldTypeSupportedMap) {
return mapMap(srcObj, (Map<?, ?>)srcFieldValue, fieldMap, destObj);
}
if (fieldMap instanceof MapFieldMap && destFieldType.equals(Object.class)) {
destFieldType = fieldMap.getDestHintContainer() != null ? fieldMap.getDestHintContainer().getHint() : srcFieldClass;
}
//對基本類型的映射處理
//PrimitiveOrWrapperConverter類支持兼容了基本類型之間的互相轉換
if (primitiveConverter.accepts(srcFieldClass) || primitiveConverter.accepts(destFieldType)) {
// Primitive or Wrapper conversion
if (fieldMap.getDestHintContainer() != null) {
Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
// if the destType is null this means that there was more than one hint.
// we must have already set the destType then.
if (destHintType != null) {
destFieldType = destHintType;
}
}
//#1841448 - if trim-strings=true, then use a trimmed src string value when converting to dest value
Object convertSrcFieldValue = srcFieldValue;
if (fieldMap.isTrimStrings() && srcFieldValue.getClass().equals(String.class)) {
convertSrcFieldValue = ((String)srcFieldValue).trim();
}
DateFormatContainer dfContainer = new DateFormatContainer(fieldMap.getDateFormat());
if (fieldMap instanceof MapFieldMap && !primitiveConverter.accepts(destFieldType)) {
return primitiveConverter.convert(convertSrcFieldValue, convertSrcFieldValue.getClass(), dfContainer);
} else {
return primitiveConverter.convert(convertSrcFieldValue, destFieldType, dfContainer, destFieldName, destObj);
}
}
//對集合類型的映射處理
if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {
return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);
}
//對枚舉類型的映射處理
if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {
return mapEnum((Enum)srcFieldValue, (Class<Enum>)destFieldType);
}
if (fieldMap.getDestDeepIndexHintContainer() != null) {
destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();
}
//其他復雜對象類型的處理
return mapCustomObject(fieldMap, destObj, destFieldType, destFieldName, srcFieldValue);
}
mapCustomObject方法。其實你會發現這個方法最重要的一點就是做遞歸處理,無論是最后調用createByCreationDirectiveAndMap還是mapToDestObject方法。
private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, String destFieldName, Object srcFieldValue) {
srcFieldValue = MappingUtils.deProxy(srcFieldValue, beanContainer);
// Custom java bean. Need to make sure that the destination object is not
// already instantiated.
Object result = null;
// in case of iterate feature new objects are created in any case
if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {
result = getExistingValue(fieldMap, destObj, destFieldType);
}
// if the field is not null than we don't want a new instance
if (result == null) {
// first check to see if this plain old field map has hints to the actual
// type.
if (fieldMap.getDestHintContainer() != null) {
Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
// if the destType is null this means that there was more than one hint.
// we must have already set the destType then.
if (destHintType != null) {
destFieldType = destHintType;
}
}
// Check to see if explicit map-id has been specified for the field
// mapping
String mapId = fieldMap.getMapId();
Class<?> targetClass;
if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {
targetClass = fieldMap.getDestHintContainer().getHint();
} else {
targetClass = destFieldType;
}
ClassMap classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);
BeanCreationDirective creationDirective = new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),
destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),
fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() :
classMap.getDestClassCreateMethod(),
classMap.getDestClass().isSkipConstructor(), destObj, destFieldName);
result = createByCreationDirectiveAndMap(creationDirective, classMap, srcFieldValue, null, false, fieldMap.getMapId());
} else {
mapToDestObject(null, srcFieldValue, result, false, fieldMap.getMapId());
}
return result;
}
小結
Dozer功能強大,但底層還是用反射那套,所以在性能測試中它的表現一般,僅次於Apache的BeanUtils。如果不追求性能的話,可以使用。
Orika
Orika可以說是幾乎集成了上述幾個工具的優點,不僅具有豐富的功能,底層使用Javassist
生成字節碼,運行 效率很高的。
使用
Orika基本支持了Dozer支持的功能,這里我也是簡單介紹一下Orika的使用,具體更詳細的API可以參考User Guide。
private MapperFactory mapperFactory;
@Before
public void setup() {
mapperFactory = new DefaultMapperFactory.Builder().build();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new TypeConverter());
mapperFactory.classMap(SourceVO.class, TargetVO.class)
.field("fullName", "name")
.field("type", "enumType")
.exclude("in")
.byDefault()
.register();
}
@Test
public void main() {
MapperFacade mapper = mapperFactory.getMapperFacade();
SourceVO sourceVO = getSourceVO();
log.info("sourceVO={}", GsonUtil.toJson(sourceVO));
TargetVO map = mapper.map(sourceVO, TargetVO.class);
log.info("map={}", GsonUtil.toJson(map));
}
原理
在講解實現原理時,我們先看看Orika在背后干了什么事情。
通過增加以下配置,我們可以看到Orika在做映射過程中生成mapper的源碼和字節碼。
System.setProperty("ma.glasnost.orika.writeSourceFiles", "true");
System.setProperty("ma.glasnost.orika.writeClassFiles", "true");
System.setProperty("ma.glasnost.orika.writeSourceFilesToPath", "path");
System.setProperty("ma.glasnost.orika.writeClassFilesToPath", "path");
用上面的例子,我們看看Orika生成的java代碼:
package ma.glasnost.orika.generated;
public class Orika_TargetVO_SourceVO_Mapper947163525829122$0 extends ma.glasnost.orika.impl.GeneratedMapperBase {
public void mapAtoB(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {
super.mapAtoB(a, b, mappingContext);
// sourceType: SourceVO
beanmapper_compare.vo.SourceVO source = ((beanmapper_compare.vo.SourceVO)a);
// destinationType: TargetVO
beanmapper_compare.vo.TargetVO destination = ((beanmapper_compare.vo.TargetVO)b);
destination.setName(((java.lang.String)source.getFullName()));
if ( !(((java.lang.Integer)source.getType()) == null)){
destination.setEnumType(((beanmapper_compare.vo.TargetVO.EnumType)((ma.glasnost.orika.Converter)usedConverters[0]).convert(((java.lang.Integer)source.getType()), ((ma.glasnost.orika.metadata.Type)usedTypes[0]), mappingContext)));
} else {
destination.setEnumType(null);
}
if ( !(((java.util.Date)source.getDate1()) == null)){
destination.setDate1(((java.util.Date)((ma.glasnost.orika.Converter)usedConverters[1]).convert(((java.util.Date)source.getDate1()), ((ma.glasnost.orika.metadata.Type)usedTypes[1]), mappingContext)));
} else {
destination.setDate1(null);
}if ( !(((java.util.List)source.getListData()) == null)) {
java.util.List new_listData = ((java.util.List)new java.util.ArrayList());
new_listData.addAll(mapperFacade.mapAsList(((java.util.List)source.getListData()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext));
destination.setListData(new_listData);
} else {
if ( !(((java.util.List)destination.getListData()) == null)) {
destination.setListData(null);
};
}if ( !(((java.util.Map)source.getMapData()) == null)){
java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());
for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {
java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());
java.lang.Integer newMapDataKey = null;
java.util.List newMapDataVal = null;
if ( !(((java.lang.Long)sourceMapDataEntry.getKey()) == null)){
newMapDataKey = ((java.lang.Integer)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Long)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext));
} else {
newMapDataKey = null;
}
if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {
java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());
new_newMapDataVal.addAll(mapperFacade.mapAsList(((java.util.List)sourceMapDataEntry.getValue()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), ((ma.glasnost.orika.metadata.Type)usedTypes[4]), mappingContext));
newMapDataVal = new_newMapDataVal;
} else {
if ( !(newMapDataVal == null)) {
newMapDataVal = null;
};
}
new_mapData.put(newMapDataKey, newMapDataVal);
}
destination.setMapData(new_mapData);
} else {
destination.setMapData(null);
}
destination.setP1(((java.lang.Integer)source.getP1()));
destination.setP2(((java.lang.Long)source.getP2()));
destination.setP3(((java.lang.Byte)source.getP3()));
destination.setPattr1(((java.lang.String)source.getPattr1()));
if ( !(((java.lang.String)source.getSeq()) == null)){
destination.setSeq(((java.lang.Long)((ma.glasnost.orika.Converter)usedConverters[3]).convert(((java.lang.String)source.getSeq()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext)));
} else {
destination.setSeq(null);
}
if(customMapper != null) {
customMapper.mapAtoB(source, destination, mappingContext);
}
}
public void mapBtoA(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {
super.mapBtoA(a, b, mappingContext);
// sourceType: TargetVO
beanmapper_compare.vo.TargetVO source = ((beanmapper_compare.vo.TargetVO)a);
// destinationType: SourceVO
beanmapper_compare.vo.SourceVO destination = ((beanmapper_compare.vo.SourceVO)b);
destination.setFullName(((java.lang.String)source.getName()));
if ( !(((beanmapper_compare.vo.TargetVO.EnumType)source.getEnumType()) == null)){
destination.setType(((java.lang.Integer)((ma.glasnost.orika.Converter)usedConverters[0]).convert(((beanmapper_compare.vo.TargetVO.EnumType)source.getEnumType()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext)));
} else {
destination.setType(null);
}
if ( !(((java.util.Date)source.getDate1()) == null)){
destination.setDate1(((java.util.Date)((ma.glasnost.orika.Converter)usedConverters[1]).convert(((java.util.Date)source.getDate1()), ((ma.glasnost.orika.metadata.Type)usedTypes[1]), mappingContext)));
} else {
destination.setDate1(null);
}if ( !(((java.util.List)source.getListData()) == null)) {
java.util.List new_listData = ((java.util.List)new java.util.ArrayList());
new_listData.addAll(mapperFacade.mapAsList(((java.util.List)source.getListData()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
destination.setListData(new_listData);
} else {
if ( !(((java.util.List)destination.getListData()) == null)) {
destination.setListData(null);
};
}if ( !(((java.util.Map)source.getMapData()) == null)){
java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());
for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {
java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());
java.lang.Long newMapDataKey = null;
java.util.List newMapDataVal = null;
if ( !(((java.lang.Integer)sourceMapDataEntry.getKey()) == null)){
newMapDataKey = ((java.lang.Long)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Integer)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
} else {
newMapDataKey = null;
}
if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {
java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());
new_newMapDataVal.addAll(mapperFacade.mapAsList(((java.util.List)sourceMapDataEntry.getValue()), ((ma.glasnost.orika.metadata.Type)usedTypes[4]), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
newMapDataVal = new_newMapDataVal;
} else {
if ( !(newMapDataVal == null)) {
newMapDataVal = null;
};
}
new_mapData.put(newMapDataKey, newMapDataVal);
}
destination.setMapData(new_mapData);
} else {
destination.setMapData(null);
}
destination.setP1(((java.lang.Integer)source.getP1()));
destination.setP2(((java.lang.Long)source.getP2()));
destination.setP3(((java.lang.Byte)source.getP3()));
destination.setPattr1(((java.lang.String)source.getPattr1()));
if ( !(((java.lang.Long)source.getSeq()) == null)){
destination.setSeq(((java.lang.String)((ma.glasnost.orika.Converter)usedConverters[4]).convert(((java.lang.Long)source.getSeq()), ((ma.glasnost.orika.metadata.Type)usedTypes[5]), mappingContext)));
} else {
destination.setSeq(null);
}
if(customMapper != null) {
customMapper.mapBtoA(source, destination, mappingContext);
}
}
}
這個mapper類就兩個方法mapAtoB和mapBtoA,從名字看猜到前者是負責src -> dest的映射,后者是負責dest -> src的映射。
好,我們們看看實現的過程。
Orika的使用跟Dozer的類似,首先通過配置生成一個MapperFactory
,再用MapperFacade
來作為映射的統一入口,這里MapperFactory
和MapperFacade
都是單例的。mapperFactory在做配置類映射時,只是注冊了ClassMap,還沒有真正的生成mapper的字節碼,是在第一次調用getMapperFacade方法時才初始化mapper。下面看看getMapperFacade。
(源碼基於 ma.glasnost.orika:orika-core:1.5.4)
public MapperFacade getMapperFacade() {
if (!isBuilt) {
synchronized (mapperFacade) {
if (!isBuilt) {
build();
}
}
}
return mapperFacade;
}
利用注冊的ClassMap信息和MappingContext上下文信息來構造mapper
public synchronized void build() {
if (!isBuilding && !isBuilt) {
isBuilding = true;
MappingContext context = contextFactory.getContext();
try {
if (useBuiltinConverters) {
BuiltinConverters.register(converterFactory);
}
converterFactory.setMapperFacade(mapperFacade);
for (Map.Entry<MapperKey, ClassMap<Object, Object>> classMapEntry : classMapRegistry.entrySet()) {
ClassMap<Object, Object> classMap = classMapEntry.getValue();
if (classMap.getUsedMappers().isEmpty()) {
classMapEntry.setValue(classMap.copyWithUsedMappers(discoverUsedMappers(classMap)));
}
}
buildClassMapRegistry();
Map<ClassMap<?, ?>, GeneratedMapperBase> generatedMappers = new HashMap<ClassMap<?, ?>, GeneratedMapperBase>();
//重點看這里
//在使用mapperFactory配置classMap時,會存放在classMapRegistry里
for (ClassMap<?, ?> classMap : classMapRegistry.values()) {
//對每個classMap生成一個mapper,重點看buildMapper方法
generatedMappers.put(classMap, buildMapper(classMap, false, context));
}
Set<Entry<ClassMap<?, ?>, GeneratedMapperBase>> generatedMapperEntries = generatedMappers.entrySet();
for (Entry<ClassMap<?, ?>, GeneratedMapperBase> generatedMapperEntry : generatedMapperEntries) {
buildObjectFactories(generatedMapperEntry.getKey(), context);
initializeUsedMappers(generatedMapperEntry.getValue(), generatedMapperEntry.getKey(), context);
}
} finally {
contextFactory.release(context);
}
isBuilt = true;
isBuilding = false;
}
}
public Set<ClassMap<Object, Object>> lookupUsedClassMap(MapperKey mapperKey) {
Set<ClassMap<Object, Object>> usedClassMapSet = usedMapperMetadataRegistry.get(mapperKey);
if (usedClassMapSet == null) {
usedClassMapSet = Collections.emptySet();
}
return usedClassMapSet;
}
跟蹤buildMapper方法
private GeneratedMapperBase buildMapper(ClassMap<?, ?> classMap, boolean isAutoGenerated, MappingContext context) {
register(classMap.getAType(), classMap.getBType(), isAutoGenerated);
register(classMap.getBType(), classMap.getAType(), isAutoGenerated);
final MapperKey mapperKey = new MapperKey(classMap.getAType(), classMap.getBType());
//調用mapperGenerator的build方法生成mapper
final GeneratedMapperBase mapper = mapperGenerator.build(classMap, context);
mapper.setMapperFacade(mapperFacade);
mapper.setFromAutoMapping(isAutoGenerated);
if (classMap.getCustomizedMapper() != null) {
final Mapper<Object, Object> customizedMapper = (Mapper<Object, Object>) classMap.getCustomizedMapper();
mapper.setCustomMapper(customizedMapper);
}
mappersRegistry.remove(mapper);
//生成的mapper存放到mappersRegistry
mappersRegistry.add(mapper);
classMapRegistry.put(mapperKey, (ClassMap<Object, Object>) classMap);
return mapper;
}
MapperGenerator的build方法
public GeneratedMapperBase build(ClassMap<?, ?> classMap, MappingContext context) {
StringBuilder logDetails = null;
try {
compilerStrategy.assureTypeIsAccessible(classMap.getAType().getRawType());
compilerStrategy.assureTypeIsAccessible(classMap.getBType().getRawType());
if (LOGGER.isDebugEnabled()) {
logDetails = new StringBuilder();
String srcName = TypeFactory.nameOf(classMap.getAType(), classMap.getBType());
String dstName = TypeFactory.nameOf(classMap.getBType(), classMap.getAType());
logDetails.append("Generating new mapper for (" + srcName + ", " + dstName + ")");
}
//構建用來生成源碼及字節碼的上下文
final SourceCodeContext mapperCode = new SourceCodeContext(classMap.getMapperClassName(), GeneratedMapperBase.class, context,
logDetails);
Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>();
//增加mapAtoB方法
mappedFields.addAll(addMapMethod(mapperCode, true, classMap, logDetails));
//增加mapBtoA方法
//addMapMethod方法基本就是手寫代碼的過程,有興趣的讀者可以看看
mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails));
//生成一個mapper實例
GeneratedMapperBase instance = mapperCode.getInstance();
instance.setAType(classMap.getAType());
instance.setBType(classMap.getBType());
instance.setFavorsExtension(classMap.favorsExtension());
if (logDetails != null) {
LOGGER.debug(logDetails.toString());
logDetails = null;
}
classMap = classMap.copy(mappedFields);
context.registerMapperGeneration(classMap);
return instance;
} catch (final Exception e) {
if (logDetails != null) {
logDetails.append("\n<---- ERROR occurred here");
LOGGER.debug(logDetails.toString());
}
throw new MappingException(e);
}
生成mapper實例
T instance = (T) compileClass().newInstance();
protected Class<?> compileClass() throws SourceCodeGenerationException {
try {
return compilerStrategy.compileClass(this);
} catch (SourceCodeGenerationException e) {
throw e;
}
}
這里的compilerStrategy的默認是用Javassist(你也可以自定義生成字節碼的策略)
JavassistCompilerStrategy的compileClass方法
這基本上就是一個使用Javassist的過程,經過前面的各種鋪墊(通過配置信息、上下文信息、拼裝java源代碼等等),終於來到這一步
public Class<?> compileClass(SourceCodeContext sourceCode) throws SourceCodeGenerationException {
StringBuilder className = new StringBuilder(sourceCode.getClassName());
CtClass byteCodeClass = null;
int attempts = 0;
Random rand = RANDOM;
while (byteCodeClass == null) {
try {
//創建一個類
byteCodeClass = classPool.makeClass(className.toString());
} catch (RuntimeException e) {
if (attempts < 5) {
className.append(Integer.toHexString(rand.nextInt()));
} else {
// No longer likely to be accidental name collision;
// propagate the error
throw e;
}
}
}
CtClass abstractMapperClass;
Class<?> compiledClass;
try {
//把源碼寫到磁盤(通過上面提到的配置)
writeSourceFile(sourceCode);
Boolean existing = superClasses.put(sourceCode.getSuperClass(), true);
if (existing == null || !existing) {
classPool.insertClassPath(new ClassClassPath(sourceCode.getSuperClass()));
}
if (registerClassLoader(Thread.currentThread().getContextClassLoader())) {
classPool.insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
}
abstractMapperClass = classPool.get(sourceCode.getSuperClass().getCanonicalName());
byteCodeClass.setSuperclass(abstractMapperClass);
//增加字段
for (String fieldDef : sourceCode.getFields()) {
try {
byteCodeClass.addField(CtField.make(fieldDef, byteCodeClass));
} catch (CannotCompileException e) {
LOG.error("An exception occurred while compiling: " + fieldDef + " for " + sourceCode.getClassName(), e);
throw e;
}
}
//增加方法,這里主要就是mapAtoB和mapBtoA方法
//直接用源碼通過Javassist往類“加”方法
for (String methodDef : sourceCode.getMethods()) {
try {
byteCodeClass.addMethod(CtNewMethod.make(methodDef, byteCodeClass));
} catch (CannotCompileException e) {
LOG.error(
"An exception occured while compiling the following method:\n\n " + methodDef + "\n\n for "
+ sourceCode.getClassName() + "\n", e);
throw e;
}
}
//生成類
compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());
//字節碼文件寫磁盤
writeClassFile(sourceCode, byteCodeClass);
} catch (NotFoundException e) {
throw new SourceCodeGenerationException(e);
} catch (CannotCompileException e) {
throw new SourceCodeGenerationException("Error compiling " + sourceCode.getClassName(), e);
} catch (IOException e) {
throw new SourceCodeGenerationException("Could not write files for " + sourceCode.getClassName(), e);
}
return compiledClass;
}
好,mapper類生成了,現在就看在調用MapperFacade的map方法是如何使用這個mapper類的。
其實很簡單,還記得生成的mapper是放到mappersRegistry嗎,跟蹤代碼,在resolveMappingStrategy方法根據typeA和typeB在mappersRegistry找到mapper,在調用mapper的mapAtoB或mapBtoA方法即可。
小結
總體來說,Orika是一個功能強大的而且性能很高的工具,推薦使用。
總結
通過對BeanUtils、BeanCopier、Dozer、Orika這幾個工具的對比,我們得知了它們的性能以及實現原理。在使用時,我們可以根據自己的實際情況選擇,推薦使用Orika。
近期熱文推薦:
1.600+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式發布,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!