常规方式,不再赘余。。。
详细使用案例:使用CGlib实现Bean拷贝(BeanCopier)
注意:
注意:原始类型(int,short,char)和其包装类型,在这里都被当成了不同类型,因此不会被拷贝
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanCopier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description: 基于BeanCopier的属性拷贝
* 凡是和反射相关的操作,基本都是低性能的。凡是和字节码相关的操作,基本都是高性能的
*/
@Slf4j
public class BeanCopyUtils {
//创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能
private static final Map<String, BeanCopier> BEAN_COPIERS = new ConcurrentHashMap<>();
/**
* 该方法没有自定义Converter,只简单进行常规属性拷贝
*
* @param srcObj 源对象
* @param destObj 目标对象
*/
public static void copy(Object srcObj, Object destObj) {
String key = genKey(srcObj.getClass(), destObj.getClass());
BeanCopier copier;
if (!BEAN_COPIERS.containsKey(key)) {
copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
BEAN_COPIERS.put(key, copier);
} else {
copier = BEAN_COPIERS.get(key);
}
copier.copy(srcObj, destObj, null);
}
private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
return srcClazz.getName() + destClazz.getName();
}
}
BeanCopier 性能比 Spring BeanUtils,Apache BeanUtils 和 PropertyUtils 好很多
public class TestSpringBeanUtils {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
//下面只是用于单独测试
PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
PersonDest personDest = new PersonDest();
BeanUtils.copyProperties(personSource,personDest);
System.out.println("persondest: "+personDest);
}
}
必须保证同名的两个成员变量类型相同
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable 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;
}
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()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && 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);
}
writeMethod.invoke(target, value);
} catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
public void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException {
// Validate existence of the specified beans
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 + ")");
}
// Copy the properties, converting as necessary
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);
}
}
} else if (orig instanceof Map) {
@SuppressWarnings("unchecked")
final 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());
}
}
} else {
final PropertyDescriptor[] origDescriptors = 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
}
}
}
}
}
注意:BeanUtils 为浅拷贝
官网文档:MapStruct 1.3.1.Final Reference Guide
<properties>
<org.mapstruct.version>1.3.1.Finalorg.mapstruct.version>
properties>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
@Mapper
:只有在接口加上这个注解, MapStruct 才会去实现该接口default
:默认,可以通过 Mappers.getMapper(Class)
方式获取实例对象spring
:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入@Mapping
:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
source
:源属性target
:目标属性dateFormat
:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式ignore
: 忽略这个字段@Mappings
:配置多个 @Mapping@MappingTarget
:用于更新已有对象@InheritConfiguration
:用于继承配置具体 copy 实现可继承or实现该接口
public interface BasicObjectMapping<SOURCE, TARGET> {
@InheritConfiguration
TARGET to(SOURCE source);
@InheritConfiguration
List<TARGET> to(Collection<SOURCE> sources);
@InheritInverseConfiguration
SOURCE from(TARGET target);
@InheritInverseConfiguration
List<SOURCE> from(Collection<TARGET> targets);
}
注:配合 spring,要具体实现加注解 @Mapper(componentModel = "spring")
@Mappings({
@Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"),
})
UserVO3 toConvertVO3(User source);
上面 expression 指定的表达式内容如下:
public class DateTransform {
public static LocalDateTime strToDate(String str){
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss");
return LocalDateTime.parse("2018-01-12 17:07:05",df);
}
}
当字段类型不一致时,以下的类型之间是 mapstruct 自动进行类型转换的:
除此之外的类型转换需要通过定义表达式来进行指定转换
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "name", target = "userName")
})
UserVO4 toConvertVO(User source);
案例一:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEnum {
private Integer id;
private String name;
private UserTypeEnum userTypeEnum;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO5 {
private Integer id;
private String name;
private String type;
}
@Getter
@AllArgsConstructor
public enum UserTypeEnum {
Java("000", "Java开发工程师"),
DB("001", "数据库管理员"),
LINUX("002", "Linux运维员");
private String value;
private String title;
}
@Mapping(source = "userTypeEnum", target = "type")
UserVO5 toConvertVO5(UserEnum source);
案例二:
public class User {
public enum Grade{
AVERAGE,BRONZE,GOLD,DIAMOND;
}
private Grade grade;
// setters, getters, toString()
}
public class UserDao {
public enum Level{
AVERAGE,BRONZE,GOLD,DIAMOND;
}
private Level level;
// setters, getters, toString()
}
public class UserDojo {
public enum RANK{
NORMAL,JUNIOR,SENIOR,MASTER;
}
private RANK rank;
// setters, getters, toString()
}
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mappings({
@Mapping(source="grade", target="rank")
})
UserDojo userToUserDojo(User user);
@ValueMappings({
@ValueMapping(source="AVERAGE",target="NORMAL"),
@ValueMapping(source="BRONZE",target="JUNIOR"),
@ValueMapping(source="GOLD",target="SENIOR"),
@ValueMapping(source="DIAMOND",target="MASTER"),
@ValueMapping(source=MappingConstants.ANY_UNMAPPED, target=MappingConstants.NULL)
})
RANK customConveter(Grade grade);
}