├── TODO.md ├── .travis.yml ├── src ├── main │ └── java │ │ └── com │ │ └── github │ │ └── trang │ │ └── copiers │ │ ├── base │ │ ├── Copier.java │ │ ├── BeanCopier.java │ │ ├── SetCopier.java │ │ ├── ListCopier.java │ │ └── ArrayCopier.java │ │ ├── util │ │ ├── Preconditions.java │ │ ├── ReflectionUtil.java │ │ └── ClassUtil.java │ │ ├── exception │ │ └── CopierException.java │ │ ├── MapCopiers.java │ │ ├── AbstractCopier.java │ │ ├── orika │ │ ├── OrikaMapperFactory.java │ │ ├── converter │ │ │ ├── BooleanConverters.java │ │ │ └── ListConverters.java │ │ ├── OrikaMapper.java │ │ └── OrikaCopier.java │ │ ├── cache │ │ └── MapperKey.java │ │ ├── cglib │ │ ├── MapToBeanCopier.java │ │ ├── BeanToMapCopier.java │ │ └── CglibCopier.java │ │ ├── Copiers.java │ │ └── CopierFactory.java └── test │ ├── resources │ └── logback-test.xml │ └── java │ └── com │ └── github │ └── trang │ └── copiers │ └── test │ ├── plugin │ └── TestConverter.java │ ├── bean │ ├── SimpleSource.java │ ├── SimpleTarget.java │ ├── TeacherVO.java │ ├── Student.java │ ├── StudentEntity.java │ ├── Teacher.java │ └── TeacherEntity.java │ ├── coveralls │ ├── MapperKeyTest.java │ ├── MapCopiersTest.java │ ├── OrikaCopierTest.java │ └── CglibCopierTest.java │ ├── framework │ ├── CglibTest.java │ └── OrikaTest.java │ ├── util │ └── MockUtils.java │ └── benchmark │ ├── Java8Test.java │ └── BenchmarkTest.java ├── Changelog.md ├── pom.xml ├── .gitignore ├── README.md └── LICENSE /TODO.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | ~~1. 支持由 Copiers 创建数组的拷贝,参考 ArrayList#toArray(T[] a) 方法~~ 4 | 2. 补全文档和单元测试 5 | 3. orika 分别在使用无参合全参构造时的压力测试 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | sudo: false 5 | install: false 6 | cache: 7 | directories: 8 | - $HOME/.m2/repository 9 | after_success: 10 | - mvn clean test -Pcoveralls jacoco:report coveralls:report -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/base/Copier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.base; 2 | 3 | /** 4 | * 拷贝聚合接口 5 | * 6 | * @author trang 7 | */ 8 | public interface Copier extends BeanCopier, ArrayCopier, ListCopier, SetCopier { 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/base/BeanCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.base; 2 | 3 | /** 4 | * Bean 拷贝底层接口 5 | * 6 | * @author trang 7 | */ 8 | public interface BeanCopier { 9 | 10 | /** 11 | * 将 source 对象拷贝到新对象 12 | * 13 | * @param source 源对象 14 | * @return 目标对象 15 | */ 16 | T copy(F source); 17 | 18 | /** 19 | * 将 source 对象拷贝到 target 对象 20 | * 21 | * @param source 源对象 22 | * @param target 目标对象 23 | */ 24 | void copy(F source, T target); 25 | 26 | } -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{yyyy-MM-dd HH:mm:ss.SSS} %5p --- [%15.15t] %-40.40logger{39} : %m%n 7 | UTF8 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/plugin/TestConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.plugin; 2 | 3 | import net.sf.cglib.core.Converter; 4 | 5 | /** 6 | * #{@link net.sf.cglib.beans.BeanCopier} 的转换器示例 7 | * 8 | * @author trang 9 | */ 10 | public class TestConverter implements Converter { 11 | 12 | /** 13 | * 重写 convert 方法,每一个 set 方法都会走一次 convert 14 | * 15 | * @param value 源属性值 16 | * @param target 目标属性类 java.lang.Lang 17 | * @param context 目标属性 setter 方法名 18 | */ 19 | @Override 20 | public Object convert(Object value, Class target, Object context) { 21 | if (target == Double.class || target == double.class) { 22 | value = Math.random(); 23 | } 24 | return value; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/util/Preconditions.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.util; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 参数校验工具 7 | * 8 | * @author trang 9 | */ 10 | public final class Preconditions { 11 | 12 | private Preconditions() { 13 | throw new UnsupportedOperationException(); 14 | } 15 | 16 | public static void checkNotNull(Object o, String msg) { 17 | if (o == null) { 18 | throw new NullPointerException(msg); 19 | } 20 | } 21 | 22 | public static void checkNotNull(Map map, String msg) { 23 | if (map == null) { 24 | throw new NullPointerException(msg); 25 | } else if (map.isEmpty()) { 26 | throw new IllegalArgumentException(msg); 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/exception/CopierException.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.exception; 2 | 3 | /** 4 | * CopierException 5 | * 6 | * @author trang 7 | */ 8 | public class CopierException extends RuntimeException { 9 | 10 | public CopierException() { 11 | super(); 12 | } 13 | 14 | public CopierException(String message) { 15 | super(message); 16 | } 17 | 18 | public CopierException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public CopierException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | public CopierException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/SimpleSource.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | /** 11 | * @author trang 12 | */ 13 | @Getter 14 | @Setter 15 | @ToString 16 | public class SimpleSource { 17 | 18 | private Integer id; 19 | private Long time; 20 | private List statusList; 21 | private List typeList; 22 | private Map map; 23 | private String same; 24 | 25 | // public SimpleSource() { 26 | // } 27 | 28 | public SimpleSource(Integer id) { 29 | this.id = id; 30 | } 31 | 32 | public SimpleSource(Integer id, Long time) { 33 | this.id = id; 34 | this.time = time; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/SimpleTarget.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.util.Date; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * @author trang 11 | */ 12 | @Getter 13 | @Setter 14 | @ToString 15 | public class SimpleTarget { 16 | 17 | private String id; 18 | private Date time; 19 | private String statuses; 20 | private String types; 21 | private String name; 22 | private String same; 23 | 24 | // public SimpleTarget() { 25 | // } 26 | 27 | public SimpleTarget(String id) { 28 | this.id = id; 29 | } 30 | 31 | public SimpleTarget(Date time) { 32 | this.time = time; 33 | } 34 | 35 | public SimpleTarget(String id, Date time) { 36 | this.id = id; 37 | this.time = time; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/MapCopiers.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers; 2 | 3 | import com.github.trang.copiers.base.Copier; 4 | import com.github.trang.copiers.cglib.BeanToMapCopier; 5 | import com.github.trang.copiers.cglib.MapToBeanCopier; 6 | 7 | /** 8 | * MapCopiers 工具类,通过工厂方法创建 #{@link Copier} 对象 9 | * 10 | * @author trang 11 | */ 12 | public final class MapCopiers { 13 | 14 | private MapCopiers() { 15 | throw new UnsupportedOperationException(); 16 | } 17 | 18 | /** 19 | * JavaBean 转换为 Map 20 | * 21 | * @param 源对象范型 22 | * @return copier 23 | */ 24 | public static BeanToMapCopier createBeanToMap() { 25 | return new BeanToMapCopier<>(); 26 | } 27 | 28 | /** 29 | * Map 转换为 JavaBean 30 | * 31 | * @param targetClass 目标对象类型 32 | * @param 目标对象范型 33 | * @return copier 34 | */ 35 | public static MapToBeanCopier createMapToBean(Class targetClass) { 36 | return new MapToBeanCopier<>(targetClass); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/AbstractCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import com.github.trang.copiers.base.Copier; 6 | 7 | import lombok.Getter; 8 | 9 | /** 10 | * #{@link Copier} 适配器,可继承该类实现具体的拷贝过程,也可直接实现 #{@link Copier} 接口 11 | * 12 | * @author trang 13 | */ 14 | @Getter 15 | public abstract class AbstractCopier implements Copier { 16 | 17 | /** 实际执行拷贝的对象 */ 18 | protected final C copier; 19 | /** 源对象的类型 */ 20 | protected final Class sourceClass; 21 | /** 目标对象的类型 */ 22 | protected final Class targetClass; 23 | 24 | protected AbstractCopier(Class sourceClass, Class targetClass, C copier) { 25 | checkNotNull(sourceClass, "source class cannot be null!"); 26 | checkNotNull(targetClass, "target class cannot be null!"); 27 | checkNotNull(copier, "copier cannot be null!"); 28 | this.sourceClass = sourceClass; 29 | this.targetClass = targetClass; 30 | this.copier = copier; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/orika/OrikaMapperFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.orika; 2 | 3 | import ma.glasnost.orika.MapperFactory; 4 | 5 | /** 6 | * 创建 MapperFactory 的单例,提供默认配置和自定义配置两种方式 7 | * 8 | * @author trang 9 | */ 10 | public final class OrikaMapperFactory { 11 | 12 | private static volatile MapperFactory INSTANCE = null; 13 | 14 | public static MapperFactory getMapperFactory(OrikaMapper orikaMapper) { 15 | if (INSTANCE == null) { 16 | synchronized (OrikaMapperFactory.class) { 17 | if (INSTANCE == null) { 18 | INSTANCE = orikaMapper.getFactory(); 19 | } 20 | } 21 | } 22 | return INSTANCE; 23 | } 24 | 25 | public static MapperFactory getMapperFactory() { 26 | if (INSTANCE == null) { 27 | synchronized (OrikaMapperFactory.class) { 28 | if (INSTANCE == null) { 29 | INSTANCE = new OrikaMapper().getFactory(); 30 | } 31 | } 32 | } 33 | return INSTANCE; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/TeacherVO.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.google.common.base.MoreObjects; 6 | 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | 12 | /** 13 | * 教师信息 VO 14 | * 与 #{@link Teacher} 区别: 15 | * 1. weight: Float -> Integer 16 | * 2. 删除了 n 个字段 17 | *

18 | * Write the code. Change the world. 19 | * 20 | * @author trang 21 | * @date 2018/6/21 22 | */ 23 | @Data 24 | @Builder 25 | @NoArgsConstructor 26 | @AllArgsConstructor 27 | public class TeacherVO implements Serializable { 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | private String name; 32 | private Integer age; 33 | private Byte sex; 34 | private Double height; 35 | private Integer weight; 36 | 37 | @Override 38 | public String toString() { 39 | return MoreObjects.toStringHelper("Teacher").omitNullValues() 40 | .add("name", name) 41 | .add("age", age) 42 | .add("sex", sex) 43 | .add("height", height) 44 | .add("weight", weight) 45 | .toString(); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/base/SetCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.base; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | import static java.util.Collections.emptySet; 5 | import static java.util.stream.Collectors.toCollection; 6 | 7 | import java.util.HashSet; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | import java.util.function.Supplier; 11 | 12 | /** 13 | * Set 拷贝底层接口 14 | * 15 | * @author trang 16 | */ 17 | public interface SetCopier extends BeanCopier { 18 | 19 | /** 20 | * 将 sourceSet 拷贝到新集合,使用 HashSet 21 | * 22 | * @param sourceSet 源对象集合 23 | * @return 目标对象集合 24 | */ 25 | default Set copySet(Set sourceSet) { 26 | return copySet(sourceSet, HashSet::new); 27 | } 28 | 29 | /** 30 | * 将 sourceSet 拷贝到新集合,使用自定义 Set 31 | * 32 | * @param sourceSet 源对象集合 33 | * @param setFactory 目标对象集合工厂,用于自定义收集器类型 34 | * @return 目标对象集合 35 | */ 36 | default Set copySet(Set sourceSet, Supplier> setFactory) { 37 | checkNotNull(setFactory, "set factory cannot be null!"); 38 | return Optional.ofNullable(sourceSet) 39 | .filter(set -> !set.isEmpty()) 40 | .map(set -> set.stream().map(this::copy).collect(toCollection(setFactory))) 41 | .orElse(emptySet()); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/coveralls/MapperKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.coveralls; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | 6 | import org.junit.Test; 7 | 8 | import com.github.trang.copiers.cache.MapperKey; 9 | import com.github.trang.copiers.test.bean.SimpleSource; 10 | import com.github.trang.copiers.test.bean.SimpleTarget; 11 | 12 | /** 13 | * MapperKeyTest 14 | *

15 | * Write the code. Change the world. 16 | * 17 | * @author trang 18 | * @date 2018/6/21 19 | */ 20 | public class MapperKeyTest { 21 | 22 | @Test 23 | public void basicTest() { 24 | MapperKey key1 = new MapperKey<>("cglib", SimpleSource.class, SimpleTarget.class); 25 | MapperKey key2 = new MapperKey<>("cglib", SimpleSource.class, SimpleTarget.class); 26 | 27 | assertEquals(key1, key2); 28 | } 29 | 30 | @Test 31 | public void extraTest() { 32 | MapperKey key1 = new MapperKey<>("cglib", SimpleSource.class, SimpleTarget.class, "extra", 0); 33 | MapperKey key2 = new MapperKey<>("cglib", SimpleSource.class, SimpleTarget.class, "extra", 0); 34 | MapperKey key3 = new MapperKey<>("cglib", SimpleSource.class, SimpleTarget.class, "extra", 1); 35 | 36 | assertEquals(key1, key2); 37 | assertNotEquals(key1, key3); 38 | assertNotEquals(key2, key3); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/base/ListCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.base; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | import static java.util.Collections.emptyList; 5 | import static java.util.stream.Collectors.toCollection; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.function.Supplier; 11 | 12 | /** 13 | * List 拷贝底层接口 14 | * 15 | * @author trang 16 | */ 17 | public interface ListCopier extends BeanCopier { 18 | 19 | /** 20 | * 将 sourceList 拷贝到新集合,使用 ArrayList 21 | * 22 | * @param sourceList 源对象集合 23 | * @return 目标对象集合 24 | */ 25 | default List copyList(List sourceList) { 26 | return copyList(sourceList, ArrayList::new); 27 | } 28 | 29 | /** 30 | * 将 sourceList 拷贝到新集合,使用自定义 List 31 | * 32 | * @param sourceList 源对象集合 33 | * @param listFactory 目标对象集合工厂,用于自定义收集器类型 34 | * @return 目标对象集合 35 | */ 36 | default List copyList(List sourceList, Supplier> listFactory) { 37 | checkNotNull(listFactory, "list factory cannot be null!"); 38 | return Optional.ofNullable(sourceList) 39 | .filter(list -> !list.isEmpty()) 40 | .map(list -> list.stream().map(this::copy).collect(toCollection(listFactory))) 41 | .orElse(emptyList()); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/Student.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import com.google.common.base.MoreObjects; 7 | 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | 12 | /** 13 | * 学生信息 14 | *

15 | * Write the code. Change the world. 16 | * 17 | * @author trang 18 | * @date 2018/6/21 19 | */ 20 | @Data 21 | @Builder 22 | @AllArgsConstructor 23 | public class Student implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | private String name; 28 | private int age; 29 | private byte sex; 30 | private double height; 31 | private float weight; 32 | private boolean handsome; 33 | private List hobbits; 34 | 35 | public Student() { } 36 | 37 | public Student(int age, String name) { 38 | this.age = age; 39 | this.name = name; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return MoreObjects.toStringHelper("Student").omitNullValues() 45 | .add("name", name) 46 | .add("age", age) 47 | .add("sex", sex) 48 | .add("height", height) 49 | .add("weight", weight) 50 | .add("handsome", handsome) 51 | .add("hobbits", hobbits) 52 | .toString(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/base/ArrayCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.base; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import java.util.Arrays; 6 | import java.util.Optional; 7 | import java.util.function.IntFunction; 8 | 9 | /** 10 | * 数组拷贝底层接口 11 | * 12 | * @author trang 13 | */ 14 | public interface ArrayCopier extends BeanCopier { 15 | 16 | Object[] EMPTY = new Object[0]; 17 | 18 | /** 19 | * 将 sourceArray 拷贝到新数组 20 | * 21 | * @param sourceArray 源对象数组 22 | * @return Object 数组 23 | */ 24 | default Object[] copyArray(F[] sourceArray) { 25 | return Optional.ofNullable(sourceArray) 26 | .filter(arr -> arr.length > 0) 27 | .map(arr -> Arrays.stream(arr).map(this::copy).toArray()) 28 | .orElse(EMPTY); 29 | } 30 | 31 | /** 32 | * 将 sourceArray 拷贝到新数组 33 | * 34 | * @param sourceArray 源对象数组 35 | * @param generator 目标对象数组生成器 36 | * @return 目标对象数组 37 | */ 38 | @SuppressWarnings("unchecked") 39 | default T[] copyArray(F[] sourceArray, IntFunction generator) { 40 | checkNotNull(generator, "array generator cannot be null!"); 41 | return Optional.ofNullable(sourceArray) 42 | .filter(arr -> arr.length > 0) 43 | .map(arr -> Arrays.stream(arr).map(this::copy).toArray(generator)) 44 | .orElse((T[]) EMPTY); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/StudentEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import com.google.common.base.MoreObjects; 7 | 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | 12 | /** 13 | * 学生信息,与 #{@link Student} 完全相同 14 | *

15 | * Write the code. Change the world. 16 | * 17 | * @author trang 18 | * @date 2018/6/21 19 | */ 20 | @Data 21 | @Builder 22 | @AllArgsConstructor 23 | public class StudentEntity implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | private String name; 28 | private int age; 29 | private byte sex; 30 | private double height; 31 | private float weight; 32 | private boolean handsome; 33 | private List hobbits; 34 | 35 | public StudentEntity() { } 36 | 37 | public StudentEntity(int age, String name) { 38 | this.age = age; 39 | this.name = name; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return MoreObjects.toStringHelper("Student").omitNullValues() 45 | .add("name", name) 46 | .add("age", age) 47 | .add("sex", sex) 48 | .add("height", height) 49 | .add("weight", weight) 50 | .add("handsome", handsome) 51 | .add("hobbits", hobbits) 52 | .toString(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/framework/CglibTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.framework; 2 | 3 | import java.util.Date; 4 | 5 | import org.junit.Test; 6 | 7 | import com.github.trang.copiers.test.bean.SimpleSource; 8 | import com.github.trang.copiers.test.bean.SimpleTarget; 9 | 10 | import net.sf.cglib.beans.BeanCopier; 11 | import net.sf.cglib.core.Converter; 12 | 13 | /** 14 | * @author trang 15 | */ 16 | public class CglibTest { 17 | 18 | @Test 19 | public void test1() { 20 | SimpleSource source = new SimpleSource(1, System.currentTimeMillis()); 21 | source.setSame("same"); 22 | SimpleTarget target = new SimpleTarget("A"); 23 | BeanCopier copier = BeanCopier.create(SimpleSource.class, SimpleTarget.class, false); 24 | copier.copy(source, target, null); 25 | System.out.println(target); 26 | 27 | BeanCopier copier2 = BeanCopier.create(SimpleSource.class, SimpleTarget.class, true); 28 | copier2.copy(source, target, new Converter() { 29 | @Override 30 | public Object convert(Object value, Class target, Object context) { 31 | System.out.println("value:" + value + ", target:" + target + ", context:" + context); 32 | if (String.class.equals(target)) { 33 | return value.toString(); 34 | } 35 | if (Date.class.equals(target)) { 36 | return new Date((long) value); 37 | } 38 | return value; 39 | } 40 | }); 41 | System.out.println(target); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/Teacher.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.google.common.base.MoreObjects; 8 | 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | 13 | /** 14 | * 教师信息 15 | *

16 | * Write the code. Change the world. 17 | * 18 | * @author trang 19 | * @date 2018/6/21 20 | */ 21 | @Data 22 | @Builder 23 | @AllArgsConstructor 24 | public class Teacher implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | private String name; 29 | private Integer age; 30 | private Byte sex; 31 | private Double height; 32 | private Float weight; 33 | private Boolean handsome; 34 | private Map house; 35 | private Teacher lover; 36 | private List students; 37 | 38 | public Teacher() { } 39 | 40 | public Teacher(String name, Integer age) { 41 | this.name = name; 42 | this.age = age; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return MoreObjects.toStringHelper("Teacher").omitNullValues() 48 | .add("name", name) 49 | .add("age", age) 50 | .add("sex", sex) 51 | .add("height", height) 52 | .add("weight", weight) 53 | .add("handsome", handsome) 54 | .add("house", house) 55 | .add("lover", lover) 56 | .add("students", students) 57 | .toString(); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.5.4 4 | 1. 升级 parent 为 1.3.2 5 | 2. 初步完善单元测试 6 | 7 | ## 2.5.3 8 | 1. 升级 parent 为 1.2.2 9 | 10 | ## 2.5.2 11 | 1. 升级 parent 为 1.2.1 12 | 13 | ## 2.5.0 14 | 1. 使用 default 方法重构拷贝接口 15 | 2. 拷贝集合的方法由 `map()` 改为语义更明确的名字,如:`copyArray()`、`copyList()` 等 16 | 3. 拷贝集合的接口中增加了支持自定义集合类型的方法 17 | 4. 重构了 MapCopiers 18 | 5. pom 继承自 parent,并更新依赖 19 | 20 | ## 1.4.2 & 2.4.2 21 | 1. OrikaMapper 增加初始化标识 22 | 23 | ## 1.4.0 & 2.4.0 24 | 1. **去掉并行和有序功能** 25 | 2. **更改包结构** 26 | 3. 支持自定义 Orika,并添加了额外的 Converters,使用起来更加便利 27 | 28 | ## 1.3.0 & 2.3.0 29 | 1. 将 EasyMapper 全面替换为 Orika,避免初始化异常 30 | 2. 通过享元模式为创建好的 Copier 对象增加缓存,避免大量对象被创建 31 | 32 | ## 2.2.1 33 | 1. 将 Copier 分离成职责更加单一的接口 34 | 2. 新增并行流拷贝,开启并行时可额外开启顺序拷贝 35 | 3. 新增拷贝 Set、Array 36 | 4. 拷贝集合的时候最大限度的返回传入的对象类,如传入 LinkedList 则返回 LinkedList 37 | 38 | ## 1.2.1 39 | 1. 将 Copier 分离成职责更加单一的接口 40 | 2. 新增拷贝 Set、Array 41 | 3. 拷贝集合的时候最大限度的返回传入的对象类,如传入 LinkedList 则返回 LinkedList 42 | 43 | ## 1.2.0 44 | 1. 更新 EasyMapper 版本到 1.0.4 45 | 2. CglibCopier 新增支持 Converter 的方法 46 | 3. MapperCopierSupport 移动到 MapperCopier.Builder 47 | 4. Bean 与 Map 转换的功能移动到 MapCopiers 48 | 5. 新增 CopierException,代替之前的 RuntimeException 49 | 6. 完善 ReadMe 与单元测试 50 | 51 | ## 1.1.1 52 | 1. 完善注释和单元测试 53 | 54 | ## 1.1.0 2017-03-16 55 | 1. 去除 Guava 依赖 56 | 2. 增加 Bean 与 Map 互相转换的工具 57 | 3. 发布到 Maven 中央仓库 58 | 59 | ## 1.0.3 2016-10-30 60 | 1. CopierAdapter 去掉不实用的 `reverse()` 方法 61 | 2. Copier 接口新增转换 List 的方法 `map()` 62 | 3. 新增 EasyMapper 新特性,抽象到 MapperCopierSupport 类 63 | 64 | ## 1.0.2 2016-10-28 65 | 1. 修复 EasyMapper 不能逆向拷贝的bug 66 | 2. 更改 Cglib 包为可选,需要使用 BeanCopier 时可自行添加依赖 67 | 3. 更新字节码依赖为最新版本 68 | 69 | ## 1.0.1 2016-10-19 70 | 1. 增加 EasyMapper 的几个常用特性 71 | 72 | ## 1.0.0 2016-10-17 73 | 1. 将 Copier 抽象出来,实现基于 Cglib 和 EasyMapper 的拷贝工具 74 | 2. 添加 CopierAdapter,代替直接实现 Copier 接口 -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/cache/MapperKey.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.cache; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.Setter; 9 | 10 | /** 11 | * 用作对象缓存的 Key 12 | * 13 | * @param 源对象类型 14 | * @param 目标对象类型 15 | * @author trang 16 | */ 17 | @Getter 18 | @Setter 19 | @RequiredArgsConstructor 20 | public class MapperKey { 21 | 22 | private final String source; 23 | private final Class sourceClass; 24 | private final Class targetClass; 25 | private Object[] extras; 26 | 27 | public MapperKey(String source, Class sourceClass, Class targetClass, Object... extras) { 28 | this.source = source; 29 | this.sourceClass = sourceClass; 30 | this.targetClass = targetClass; 31 | this.extras = extras; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | MapperKey mapperKey = (MapperKey) o; 39 | return Objects.equals(source, mapperKey.source) && 40 | Objects.equals(sourceClass, mapperKey.sourceClass) && 41 | Objects.equals(targetClass, mapperKey.targetClass) && 42 | Arrays.deepEquals(extras, mapperKey.extras); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | int result = Objects.hash(source, sourceClass, targetClass); 48 | result = 31 * result + Arrays.deepHashCode(extras); 49 | return result; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "(" + source + ", " + sourceClass + ", " + targetClass + ", " + Arrays.deepToString(extras) + ")"; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/cglib/MapToBeanCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.cglib; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import java.util.Map; 6 | 7 | import com.github.trang.copiers.base.Copier; 8 | import com.github.trang.copiers.exception.CopierException; 9 | import com.github.trang.copiers.util.ReflectionUtil; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | import net.sf.cglib.beans.BeanMap; 13 | 14 | /** 15 | * Map 转换为 JavaBean 16 | * 17 | * @author trang 18 | */ 19 | @Slf4j(topic = "copiers") 20 | public class MapToBeanCopier implements Copier, T> { 21 | 22 | private final Class targetClass; 23 | 24 | public MapToBeanCopier(Class targetClass) { 25 | checkNotNull(targetClass, "target class cannot be null!"); 26 | this.targetClass = targetClass; 27 | } 28 | 29 | @Override 30 | public T copy(Map source) { 31 | checkNotNull(source, "source map cannot be null!"); 32 | try { 33 | T bean = ReflectionUtil.newInstance(targetClass); 34 | BeanMap beanMap = BeanMap.create(bean); 35 | beanMap.putAll(source); 36 | return bean; 37 | } catch (Exception e) { 38 | throw new CopierException("create object fail, class: " + targetClass.getName(), e); 39 | } 40 | } 41 | 42 | @Override 43 | public void copy(Map source, T bean) { 44 | checkNotNull(source, "source map cannot be null!"); 45 | checkNotNull(bean, "target bean cannot be null!"); 46 | try { 47 | BeanMap beanMap = BeanMap.create(bean); 48 | beanMap.putAll(source); 49 | } catch (Exception e) { 50 | throw new CopierException("create object fail, class: " + bean.getClass().getName(), e); 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/bean/TeacherEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.bean; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.google.common.base.MoreObjects; 8 | 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | 13 | /** 14 | * 教师信息 15 | * 与 #{@link Teacher} 区别: 16 | * 1. name -> username 17 | * 2. 增加 hobbits 18 | * 3. weight: Float -> Long 19 | *

20 | * Write the code. Change the world. 21 | * 22 | * @author trang 23 | * @date 2018/6/21 24 | */ 25 | @Data 26 | @Builder 27 | @AllArgsConstructor 28 | public class TeacherEntity implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private String username; 33 | private Integer age; 34 | private Byte sex; 35 | private Double height; 36 | private Long weight; 37 | private Boolean handsome; 38 | private List hobbits; 39 | private Map house; 40 | private TeacherEntity lover; 41 | private List students; 42 | 43 | public TeacherEntity() { } 44 | 45 | public TeacherEntity(String name, Integer age) { 46 | this.username = name; 47 | this.age = age; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return MoreObjects.toStringHelper("Teacher").omitNullValues() 53 | .add("username", username) 54 | .add("age", age) 55 | .add("sex", sex) 56 | .add("height", height) 57 | .add("weight", weight) 58 | .add("handsome", handsome) 59 | .add("hobbits", hobbits) 60 | .add("house", house) 61 | .add("lover", lover) 62 | .add("students", students) 63 | .toString(); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/cglib/BeanToMapCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.cglib; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.function.Supplier; 8 | 9 | import com.github.trang.copiers.base.Copier; 10 | import com.github.trang.copiers.exception.CopierException; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | import net.sf.cglib.beans.BeanMap; 14 | 15 | /** 16 | * JavaBean 转换为 Map 17 | * 18 | * @author trang 19 | */ 20 | @Slf4j(topic = "copiers") 21 | public class BeanToMapCopier implements Copier> { 22 | 23 | @Override 24 | public Map copy(F bean) { 25 | return copy(bean, HashMap::new); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public Map copy(F bean, Supplier> mapFactory) { 30 | checkNotNull(bean, "source bean cannot be null!"); 31 | checkNotNull(mapFactory, "map factory cannot be null!"); 32 | try { 33 | BeanMap beanMap = BeanMap.create(bean); 34 | Map map = mapFactory.get(); 35 | map.putAll(beanMap); 36 | return map; 37 | } catch (Exception e) { 38 | throw new CopierException("create object fail, class: " + bean.getClass().getName(), e); 39 | } 40 | } 41 | 42 | @Override 43 | @SuppressWarnings("unchecked") 44 | public void copy(F bean, Map target) { 45 | checkNotNull(bean, "source bean cannot be null!"); 46 | checkNotNull(target, "target map cannot be null!"); 47 | try { 48 | BeanMap beanMap = BeanMap.create(bean); 49 | target.putAll(beanMap); 50 | } catch (Exception e) { 51 | throw new CopierException("create object fail, class: " + bean.getClass().getName(), e); 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/Copiers.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers; 2 | 3 | import com.github.trang.copiers.base.Copier; 4 | import com.github.trang.copiers.orika.OrikaCopier; 5 | 6 | import net.sf.cglib.core.Converter; 7 | 8 | /** 9 | * Copiers 工具类,通过工厂方法创建 #{@link Copier} 对象 10 | * 11 | * 目前有两种实现:Cglib & Orika 12 | * 13 | * @author trang 14 | */ 15 | public final class Copiers { 16 | 17 | private Copiers() { 18 | throw new UnsupportedOperationException(); 19 | } 20 | 21 | /** 22 | * 基于 Orika 实现的简单拷贝,满足基本需求 23 | * 24 | * @param sourceClass 源对象类型 25 | * @param targetClass 目标对象类型 26 | * @return copier 27 | */ 28 | public static Copier create(Class sourceClass, Class targetClass) { 29 | return CopierFactory.getOrCreateOrikaCopier(sourceClass, targetClass); 30 | } 31 | 32 | /** 33 | * 基于 Orika 实现的高级拷贝,满足复杂需求 34 | * 35 | * @param sourceClass 源对象类型 36 | * @param targetClass 目标对象类型 37 | * @return copier 38 | */ 39 | public static OrikaCopier.Builder createOrika(Class sourceClass, Class targetClass) { 40 | return new OrikaCopier.Builder<>(sourceClass, targetClass); 41 | } 42 | 43 | /** 44 | * 基于 Cglib 实现的拷贝,不支持 Converter 45 | * 46 | * @param sourceClass 源对象类型 47 | * @param targetClass 目标对象类型 48 | * @return copier 49 | */ 50 | public static Copier createCglib(Class sourceClass, Class targetClass) { 51 | return CopierFactory.getOrCreateCglibCopier(sourceClass, targetClass); 52 | } 53 | 54 | /** 55 | * 基于 Cglib 实现的拷贝,支持 Converter 56 | * 57 | * @param sourceClass 源对象类型 58 | * @param targetClass 目标对象类型 59 | * @param converter 转换器 60 | * @return copier 61 | */ 62 | public static Copier createCglib(Class sourceClass, Class targetClass, Converter converter) { 63 | return CopierFactory.getOrCreateCglibCopier(sourceClass, targetClass, converter); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/framework/OrikaTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.framework; 2 | 3 | import static com.google.common.collect.Lists.newArrayList; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | 10 | import com.github.trang.copiers.test.bean.SimpleSource; 11 | import com.github.trang.copiers.test.bean.SimpleTarget; 12 | import com.google.common.base.Joiner; 13 | 14 | import ma.glasnost.orika.BoundMapperFacade; 15 | import ma.glasnost.orika.CustomConverter; 16 | import ma.glasnost.orika.MapperFactory; 17 | import ma.glasnost.orika.MappingContext; 18 | import ma.glasnost.orika.impl.DefaultMapperFactory; 19 | import ma.glasnost.orika.metadata.Type; 20 | 21 | /** 22 | * @author trang 23 | */ 24 | public class OrikaTest { 25 | 26 | @Test 27 | public void aaa() { 28 | MapperFactory mapperFactory = new DefaultMapperFactory.Builder().mapNulls(false).build(); 29 | mapperFactory.getConverterFactory().registerConverter("list2String", new CustomConverter() { 30 | @Override 31 | public String convert(List source, Type destinationType, MappingContext mappingContext) { 32 | return Joiner.on(",").join(source); 33 | } 34 | }); 35 | mapperFactory.classMap(SimpleSource.class, SimpleTarget.class) 36 | // .exclude("time") 37 | .exclude("id") 38 | .fieldMap("statusList", "statuses").converter("list2String").add() 39 | .constructorB("time") 40 | .byDefault() 41 | .register(); 42 | SimpleSource source = new SimpleSource(1, System.currentTimeMillis()); 43 | source.setStatusList(newArrayList(1,2,3)); 44 | source.setMap(new HashMap<>()); 45 | BoundMapperFacade mapper = mapperFactory.getMapperFacade(SimpleSource.class, SimpleTarget.class); 46 | SimpleTarget target = mapper.map(source); 47 | System.out.println(target); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/cglib/CglibCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.cglib; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import com.github.trang.copiers.AbstractCopier; 6 | import com.github.trang.copiers.base.Copier; 7 | import com.github.trang.copiers.exception.CopierException; 8 | import com.github.trang.copiers.util.ReflectionUtil; 9 | 10 | import lombok.Getter; 11 | import lombok.extern.slf4j.Slf4j; 12 | import net.sf.cglib.beans.BeanCopier; 13 | import net.sf.cglib.core.Converter; 14 | 15 | /** 16 | * 基于 Cglib #{@link BeanCopier} 的 #{@link Copier} 实现 17 | * 使用时需注意: 18 | * #{@link BeanCopier#create(Class, Class, boolean)} 每次都会做 Class 之间的映射,为了避免资源浪费, 19 | * 我们可以新建一个静态容器保存常用的 BeanCopier,使用时直接从容器中取即可。 20 | * 21 | * @author trang 22 | */ 23 | @Getter 24 | @Slf4j(topic = "copiers") 25 | public class CglibCopier extends AbstractCopier { 26 | 27 | /** 自定义转换器,只有在 useConverter 为 true 时生效 */ 28 | private Converter converter; 29 | 30 | public CglibCopier(Class sourceClass, Class targetClass) { 31 | // 创建 BeanCopier 对象,不使用转换器 32 | super(sourceClass, targetClass, BeanCopier.create(sourceClass, targetClass, false)); 33 | } 34 | 35 | public CglibCopier(Class sourceClass, Class targetClass, Converter converter) { 36 | // 创建 BeanCopier 对象,使用转换器 37 | super(sourceClass, targetClass, BeanCopier.create(sourceClass, targetClass, true)); 38 | this.converter = converter; 39 | } 40 | 41 | @Override 42 | public T copy(F source) { 43 | checkNotNull(source, "source bean cannot be null!"); 44 | try { 45 | T target = ReflectionUtil.newInstance(targetClass); 46 | copier.copy(source, target, converter); 47 | return target; 48 | } catch (Exception e) { 49 | throw new CopierException("create object fail, class: " + targetClass.getName(), e); 50 | } 51 | } 52 | 53 | @Override 54 | public void copy(F source, T target) { 55 | checkNotNull(source, "source bean cannot be null!"); 56 | checkNotNull(target, "target bean cannot be null!"); 57 | try { 58 | copier.copy(source, target, converter); 59 | } catch (Exception e) { 60 | throw new CopierException("create object fail, class: " + targetClass.getName(), e); 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/util/ReflectionUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.util; 2 | 3 | import java.lang.reflect.AccessibleObject; 4 | import java.lang.reflect.Constructor; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * 反射工具类 10 | * 11 | * @author zhangxu 12 | */ 13 | @Slf4j 14 | public final class ReflectionUtil { 15 | 16 | private ReflectionUtil() { 17 | 18 | } 19 | 20 | /** 21 | * 使用反射新建一个对象,尽全力去新建,如果没有默认构造方法也支持 22 | * 23 | * @param clazz 类型 24 | * @param T 25 | * @return 对象 26 | */ 27 | public static T newInstance(final Class clazz) { 28 | Constructor[] constructors = getAllConstructorsOfClass(clazz, true); 29 | if (constructors == null || constructors.length == 0) { 30 | return null; 31 | } 32 | Object[] initParameters = getInitParameters(constructors[0].getParameterTypes()); 33 | try { 34 | @SuppressWarnings("unchecked") 35 | T instance = (T) constructors[0].newInstance(initParameters); 36 | return instance; 37 | } catch (Exception e) { 38 | log.error("newInstance", e); 39 | return null; 40 | } 41 | } 42 | 43 | /** 44 | * 获取某个类型的所有构造方法 45 | * 46 | * @param clazz 类型 47 | * @param accessible 是否可以访问 48 | * @return 构造方法数组 49 | */ 50 | public static Constructor[] getAllConstructorsOfClass(final Class clazz, boolean accessible) { 51 | if (clazz == null) { 52 | return null; 53 | } 54 | Constructor[] constructors = clazz.getDeclaredConstructors(); 55 | if (constructors != null && constructors.length > 0) { 56 | AccessibleObject.setAccessible(constructors, accessible); 57 | } 58 | return constructors; 59 | } 60 | 61 | /** 62 | * 获取默认参数 63 | * 64 | * @param parameterTypes 参数类型数组 65 | * @return 参数值数组 66 | */ 67 | private static Object[] getInitParameters(Class[] parameterTypes) { 68 | int length = parameterTypes.length; 69 | Object[] result = new Object[length]; 70 | for (int i = 0; i < length; i++) { 71 | if (parameterTypes[i].isPrimitive()) { 72 | Object init = ClassUtil.getPrimitiveDefaultValue(parameterTypes[i]); 73 | result[i] = init; 74 | continue; 75 | } 76 | result[i] = null; 77 | } 78 | return result; 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/CopierFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.ConcurrentMap; 5 | 6 | import com.github.trang.copiers.cache.MapperKey; 7 | import com.github.trang.copiers.cglib.CglibCopier; 8 | import com.github.trang.copiers.orika.OrikaCopier; 9 | 10 | import lombok.Getter; 11 | import net.sf.cglib.core.Converter; 12 | 13 | /** 14 | * Copier 对象工厂 15 | * 16 | * @author trang 17 | */ 18 | public final class CopierFactory { 19 | 20 | private CopierFactory() { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | private static final String ORIKA = "orika"; 25 | private static final String CGLIB = "cglib"; 26 | 27 | public static OrikaCopier getOrCreateOrikaCopier(Class sourceClass, Class targetClass) { 28 | ConcurrentMap, OrikaCopier> orikaCache = CopierCache.getInstance().getOrikaCache(); 29 | MapperKey mapperKey = new MapperKey<>(ORIKA, sourceClass, targetClass); 30 | return orikaCache.computeIfAbsent(mapperKey, key -> new OrikaCopier.Builder<>(key.getSourceClass(), key.getTargetClass()).register()); 31 | } 32 | 33 | public static CglibCopier getOrCreateCglibCopier(Class sourceClass, Class targetClass) { 34 | ConcurrentMap, CglibCopier> cglibCache = CopierCache.getInstance().getCglibCache(); 35 | MapperKey mapperKey = new MapperKey<>(CGLIB, sourceClass, targetClass); 36 | return cglibCache.computeIfAbsent(mapperKey, key -> new CglibCopier<>(key.getSourceClass(), key.getTargetClass())); 37 | } 38 | 39 | public static CglibCopier getOrCreateCglibCopier(Class sourceClass, Class targetClass, Converter converter) { 40 | ConcurrentMap, CglibCopier> cglibCache = CopierCache.getInstance().getCglibCache(); 41 | MapperKey mapperKey = new MapperKey<>(CGLIB, sourceClass, targetClass, converter); 42 | return cglibCache.computeIfAbsent(mapperKey, key -> new CglibCopier<>(key.getSourceClass(), key.getTargetClass(), converter)); 43 | } 44 | 45 | public static class CopierCache { 46 | 47 | private static volatile CopierCache INSTANCE; 48 | 49 | @SuppressWarnings("unchecked") 50 | public static CopierCache getInstance() { 51 | if (INSTANCE == null) { 52 | synchronized (CopierCache.class) { 53 | if (INSTANCE == null) { 54 | init(); 55 | } 56 | } 57 | } 58 | return (CopierCache) INSTANCE; 59 | } 60 | 61 | private static void init() { 62 | INSTANCE = new CopierCache(); 63 | CopierCache instance = INSTANCE; 64 | instance.cglibCache = new ConcurrentHashMap<>(1024, 0.75f); 65 | instance.orikaCache = new ConcurrentHashMap<>(1024, 0.75f); 66 | } 67 | 68 | @Getter 69 | private ConcurrentMap, CglibCopier> cglibCache; 70 | @Getter 71 | private ConcurrentMap, OrikaCopier> orikaCache; 72 | 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/util/MockUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.util; 2 | 3 | import static com.google.common.collect.Lists.newArrayList; 4 | 5 | import com.github.trang.copiers.test.bean.Student; 6 | import com.github.trang.copiers.test.bean.StudentEntity; 7 | import com.github.trang.copiers.test.bean.Teacher; 8 | import com.github.trang.copiers.test.bean.TeacherEntity; 9 | import com.google.common.collect.ImmutableMap; 10 | 11 | /** 12 | * Mock 工具类 13 | *

14 | * Write the code. Change the world. 15 | * 16 | * @author trang 17 | * @date 2018/6/21 18 | */ 19 | public class MockUtils { 20 | 21 | public static Teacher newTeacher() { 22 | Teacher teacher = new Teacher(); 23 | teacher.setName("teacher"); 24 | teacher.setAge(36); 25 | teacher.setSex((byte) 1); 26 | teacher.setHeight(188.0); 27 | teacher.setWeight(120F); 28 | teacher.setHandsome(true); 29 | teacher.setHouse(ImmutableMap.of("home", "bj")); 30 | teacher.setLover(new Teacher("lover", 36)); 31 | teacher.setStudents(newArrayList(newMaleStudent(), newFemaleStudent())); 32 | return teacher; 33 | } 34 | 35 | public static Student newMaleStudent() { 36 | Student student = new Student(); 37 | student.setName("male"); 38 | student.setAge(10); 39 | student.setSex((byte) 1); 40 | student.setHeight(100.0); 41 | student.setWeight(50F); 42 | student.setHandsome(true); 43 | student.setHobbits(newArrayList("吃", "喝", "玩")); 44 | return student; 45 | } 46 | 47 | public static Student newFemaleStudent() { 48 | Student student = new Student(); 49 | student.setName("female"); 50 | student.setAge(10); 51 | student.setSex((byte) 2); 52 | student.setHeight(100.0); 53 | student.setWeight(50F); 54 | student.setHandsome(false); 55 | student.setHobbits(newArrayList("吃", "喝", "玩")); 56 | return student; 57 | } 58 | 59 | public static TeacherEntity newTeacherEntity() { 60 | TeacherEntity teacher = new TeacherEntity(); 61 | teacher.setUsername("teacher"); 62 | teacher.setAge(36); 63 | teacher.setSex((byte) 1); 64 | teacher.setHeight(188.0); 65 | teacher.setWeight(120L); 66 | teacher.setHandsome(true); 67 | teacher.setHobbits(newArrayList("说", "学")); 68 | teacher.setHouse(ImmutableMap.of("home", "bj")); 69 | teacher.setLover(new TeacherEntity("lover", 36)); 70 | teacher.setStudents(newArrayList(newMaleStudentEntity(), newFemaleStudentEntity())); 71 | return teacher; 72 | } 73 | 74 | public static StudentEntity newMaleStudentEntity() { 75 | StudentEntity student = new StudentEntity(); 76 | student.setName("male"); 77 | student.setAge(10); 78 | student.setSex((byte) 1); 79 | student.setHeight(100.0); 80 | student.setWeight(50F); 81 | student.setHandsome(true); 82 | student.setHobbits(newArrayList("吃", "喝", "玩")); 83 | return student; 84 | } 85 | 86 | public static StudentEntity newFemaleStudentEntity() { 87 | StudentEntity student = new StudentEntity(); 88 | student.setName("female"); 89 | student.setAge(10); 90 | student.setSex((byte) 2); 91 | student.setHeight(100.0); 92 | student.setWeight(50F); 93 | student.setHandsome(false); 94 | student.setHobbits(newArrayList("吃", "喝", "玩")); 95 | return student; 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.drtrang 8 | parent2 9 | 1.3.2 10 | 11 | 12 | com.github.drtrang 13 | copiers 14 | 2.5.4 15 | 16 | Copiers 17 | A Friendly Bean Copier Packaging. 18 | https://github.com/drtrang/Copiers 19 | 20 | 2016 21 | 22 | 23 | 24 | trang 25 | trang 26 | donghao.l@hotmail.com 27 | +8 28 | http://blog.trang.space/ 29 | 30 | 31 | 32 | 33 | 34 | The Apache Software License, Version 2.0 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | repo 37 | 38 | 39 | 40 | 41 | scm:git:https://github.com/drtrang/Copiers.git 42 | scm:git:git@github.com:drtrang/Copiers.git 43 | https://github.com/drtrang/Copiers 44 | HEAD 45 | 46 | 47 | 48 | github 49 | https://github.com/drtrang/Copiers/issues 50 | 51 | 52 | 53 | 54 | 55 | org.slf4j 56 | slf4j-api 57 | 58 | 59 | org.javassist 60 | javassist 61 | 62 | 63 | cglib 64 | cglib 65 | 66 | 67 | ma.glasnost.orika 68 | orika-core 69 | 70 | 71 | 72 | org.projectlombok 73 | lombok 74 | provided 75 | true 76 | 77 | 78 | 79 | ch.qos.logback 80 | logback-classic 81 | test 82 | 83 | 84 | com.google.guava 85 | guava 86 | test 87 | 88 | 89 | junit 90 | junit 91 | test 92 | 93 | 94 | org.assertj 95 | assertj-core 96 | test 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Extra 2 | rebel*.xml 3 | 4 | ### Maven template 5 | target/ 6 | pom.xml.tag 7 | pom.xml.releaseBackup 8 | pom.xml.versionsBackup 9 | pom.xml.next 10 | release.properties 11 | dependency-reduced-pom.xml 12 | buildNumber.properties 13 | .mvn/timing.properties 14 | 15 | # Exclude maven wrapper 16 | !/.mvn/wrapper/maven-wrapper.jar 17 | ### macOS template 18 | *.DS_Store 19 | .AppleDouble 20 | .LSOverride 21 | 22 | # Icon must end with two \r 23 | Icon 24 | 25 | 26 | # Thumbnails 27 | ._* 28 | 29 | # Files that might appear in the root of a volume 30 | .DocumentRevisions-V100 31 | .fseventsd 32 | .Spotlight-V100 33 | .TemporaryItems 34 | .Trashes 35 | .VolumeIcon.icns 36 | .com.apple.timemachine.donotpresent 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | ### Example user template template 45 | ### Example user template 46 | 47 | # IntelliJ project files 48 | .idea 49 | *.iml 50 | out 51 | gen### Java template 52 | *.class 53 | 54 | # BlueJ files 55 | *.ctxt 56 | 57 | # Mobile Tools for Java (J2ME) 58 | .mtj.tmp/ 59 | 60 | # Package Files # 61 | *.jar 62 | *.war 63 | *.ear 64 | 65 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 66 | hs_err_pid* 67 | ### JDeveloper template 68 | # default application storage directory used by the IDE Performance Cache feature 69 | .data/ 70 | 71 | # used for ADF styles caching 72 | temp/ 73 | 74 | # default output directories 75 | classes/ 76 | deploy/ 77 | javadoc/ 78 | 79 | # lock file, a part of Oracle Credential Store Framework 80 | cwallet.sso.lck### Eclipse template 81 | 82 | .metadata 83 | bin/ 84 | tmp/ 85 | *.tmp 86 | *.bak 87 | *.swp 88 | *~.nib 89 | local.properties 90 | .settings/ 91 | .loadpath 92 | .recommenders 93 | 94 | # Eclipse Core 95 | .project 96 | 97 | # External tool builders 98 | .externalToolBuilders/ 99 | 100 | # Locally stored "Eclipse launch configurations" 101 | *.launch 102 | 103 | # PyDev specific (Python IDE for Eclipse) 104 | *.pydevproject 105 | 106 | # CDT-specific (C/C++ Development Tooling) 107 | .cproject 108 | 109 | # JDT-specific (Eclipse Java Development Tools) 110 | .classpath 111 | 112 | # Java annotation processor (APT) 113 | .factorypath 114 | 115 | # PDT-specific (PHP Development Tools) 116 | .buildpath 117 | 118 | # sbteclipse plugin 119 | .target 120 | 121 | # Tern plugin 122 | .tern-project 123 | 124 | # TeXlipse plugin 125 | .texlipse 126 | 127 | # STS (Spring Tool Suite) 128 | .springBeans 129 | 130 | # Code Recommenders 131 | .recommenders/ 132 | ### JetBrains template 133 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 134 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 135 | 136 | # User-specific stuff: 137 | .idea/workspace.xml 138 | .idea/tasks.xml 139 | 140 | # Sensitive or high-churn files: 141 | .idea/dataSources/ 142 | .idea/dataSources.ids 143 | .idea/dataSources.xml 144 | .idea/dataSources.local.xml 145 | .idea/sqlDataSources.xml 146 | .idea/dynamic.xml 147 | .idea/uiDesigner.xml 148 | 149 | # Gradle: 150 | .idea/gradle.xml 151 | .idea/libraries 152 | 153 | # Mongo Explorer plugin: 154 | .idea/mongoSettings.xml 155 | 156 | ## File-based project format: 157 | *.iws 158 | 159 | ## Plugin-specific files: 160 | 161 | # IntelliJ 162 | /out/ 163 | 164 | # mpeltonen/sbt-idea plugin 165 | .idea_modules/ 166 | 167 | # JIRA plugin 168 | atlassian-ide-plugin.xml 169 | 170 | # Crashlytics plugin (for Android Studio and IntelliJ) 171 | com_crashlytics_export_strings.xml 172 | crashlytics.properties 173 | crashlytics-build.properties 174 | fabric.properties 175 | ### Windows template 176 | # Windows image file caches 177 | Thumbs.db 178 | ehthumbs.db 179 | 180 | # Folder configuration file 181 | Desktop.ini 182 | 183 | # Recycle Bin used on file shares 184 | $RECYCLE.BIN/ 185 | 186 | # Windows Installer files 187 | *.cab 188 | *.msi 189 | *.msm 190 | *.msp 191 | 192 | # Windows shortcuts 193 | *.lnk -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/orika/converter/BooleanConverters.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.orika.converter; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import ma.glasnost.orika.MappingContext; 6 | import ma.glasnost.orika.converter.BidirectionalConverter; 7 | import ma.glasnost.orika.metadata.Type; 8 | 9 | /** 10 | * Boolean 转换器,用于布尔型与常用类型之间的互相转换,默认会注册到 Orika 11 | * 12 | * @author trang 13 | */ 14 | public class BooleanConverters { 15 | 16 | public static class BooleanToByteConverter extends BidirectionalConverter { 17 | @Override 18 | public Byte convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 19 | return source ? (byte) 1 : (byte) 0; 20 | } 21 | 22 | @Override 23 | public Boolean convertFrom(Byte source, Type destinationType, MappingContext mappingContext) { 24 | return source != 0 ? Boolean.TRUE : Boolean.FALSE; 25 | } 26 | } 27 | 28 | public static class BooleanToShortConverter extends BidirectionalConverter { 29 | @Override 30 | public Short convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 31 | return source ? (short) 1 : (short) 0; 32 | } 33 | 34 | @Override 35 | public Boolean convertFrom(Short source, Type destinationType, MappingContext mappingContext) { 36 | return source != 0 ? Boolean.TRUE : Boolean.FALSE; 37 | } 38 | } 39 | 40 | public static class BooleanToIntegerConverter extends BidirectionalConverter { 41 | @Override 42 | public Integer convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 43 | return source ? 1 : 0; 44 | } 45 | 46 | @Override 47 | public Boolean convertFrom(Integer source, Type destinationType, MappingContext mappingContext) { 48 | return source != 0 ? Boolean.TRUE : Boolean.FALSE; 49 | } 50 | } 51 | 52 | public static class BooleanToLongConverter extends BidirectionalConverter { 53 | @Override 54 | public Long convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 55 | return source ? 1L : 0L; 56 | } 57 | 58 | @Override 59 | public Boolean convertFrom(Long source, Type destinationType, MappingContext mappingContext) { 60 | return source != 0 ? Boolean.TRUE : Boolean.FALSE; 61 | } 62 | } 63 | 64 | public static class BooleanToDoubleConverter extends BidirectionalConverter { 65 | @Override 66 | public Double convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 67 | return source ? 1.0 : 0.0; 68 | } 69 | 70 | @Override 71 | public Boolean convertFrom(Double source, Type destinationType, MappingContext mappingContext) { 72 | return source != 0 ? Boolean.TRUE : Boolean.FALSE; 73 | } 74 | } 75 | 76 | public static class BooleanToFloatConverter extends BidirectionalConverter { 77 | @Override 78 | public Float convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 79 | return source ? 1.0f : 0.0f; 80 | } 81 | 82 | @Override 83 | public Boolean convertFrom(Float source, Type destinationType, MappingContext mappingContext) { 84 | return source != 0f ? Boolean.TRUE : Boolean.FALSE; 85 | } 86 | } 87 | 88 | public static class BooleanToBigDecimalConverter extends BidirectionalConverter { 89 | @Override 90 | public BigDecimal convertTo(Boolean source, Type destinationType, MappingContext mappingContext) { 91 | return source ? BigDecimal.ONE : BigDecimal.ZERO; 92 | } 93 | 94 | @Override 95 | public Boolean convertFrom(BigDecimal source, Type destinationType, MappingContext mappingContext) { 96 | return source.compareTo(BigDecimal.ONE) >= 0 ? Boolean.TRUE : Boolean.FALSE; 97 | } 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/util/ClassUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | /** 8 | * 类工具 9 | * 10 | * @author zhangxu 11 | */ 12 | public final class ClassUtil { 13 | 14 | private static final Set> PRIMITIVE_WRAPPER_TYPES = getWrapperTypes(); 15 | 16 | private ClassUtil() { 17 | 18 | } 19 | 20 | private static Set> getWrapperTypes() { 21 | return new HashSet<>(Arrays.asList(Byte.class, Short.class, Integer.class, 22 | Long.class, Boolean.class, Character.class, Float.class, Double.class)); 23 | } 24 | 25 | /** 26 | * Verifies whether a given type is one of the wrapper classes for a primitive type. 27 | * 28 | * @param type type 29 | * @return boolean 30 | */ 31 | public static boolean isPrimitiveWrapper(Class type) { 32 | return PRIMITIVE_WRAPPER_TYPES.contains(type); 33 | } 34 | 35 | /** 36 | * Returns the corresponding wrapper type for the given primitive, 37 | * or null if the type is not primitive. 38 | * 39 | * @param primitiveType primitiveType 40 | * @return Class 41 | */ 42 | public static Class getWrapperType(Class primitiveType) { 43 | if (boolean.class.equals(primitiveType)) { 44 | return Boolean.class; 45 | } else if (byte.class.equals(primitiveType)) { 46 | return Byte.class; 47 | } else if (char.class.equals(primitiveType)) { 48 | return Character.class; 49 | } else if (short.class.equals(primitiveType)) { 50 | return Short.class; 51 | } else if (int.class.equals(primitiveType)) { 52 | return Integer.class; 53 | } else if (long.class.equals(primitiveType)) { 54 | return Long.class; 55 | } else if (float.class.equals(primitiveType)) { 56 | return Float.class; 57 | } else if (double.class.equals(primitiveType)) { 58 | return Double.class; 59 | } else { 60 | return null; 61 | } 62 | } 63 | 64 | /** 65 | * Returns the corresponding primitive type for the given primitive wrapper, 66 | * or null if the type is not a primitive wrapper. 67 | * 68 | * @param wrapperType wrapperType 69 | * @return the corresponding primitive type 70 | */ 71 | public static Class getPrimitiveType(Class wrapperType) { 72 | if (Boolean.class.equals(wrapperType)) { 73 | return Boolean.TYPE; 74 | } else if (Byte.class.equals(wrapperType)) { 75 | return Byte.TYPE; 76 | } else if (Character.class.equals(wrapperType)) { 77 | return Character.TYPE; 78 | } else if (Short.class.equals(wrapperType)) { 79 | return Short.TYPE; 80 | } else if (Integer.class.equals(wrapperType)) { 81 | return Integer.TYPE; 82 | } else if (Long.class.equals(wrapperType)) { 83 | return Long.TYPE; 84 | } else if (Float.class.equals(wrapperType)) { 85 | return Float.TYPE; 86 | } else if (Double.class.equals(wrapperType)) { 87 | return Double.TYPE; 88 | } else { 89 | return null; 90 | } 91 | } 92 | 93 | /** 94 | * 取得primitive类型的默认值。 95 | * 96 | * @param primitiveType 基本类型 97 | * @return 默认值 98 | */ 99 | public static Object getPrimitiveDefaultValue(Class primitiveType) { 100 | if (boolean.class.equals(primitiveType)) { 101 | return false; 102 | } else if (byte.class.equals(primitiveType)) { 103 | return (byte) 0; 104 | } else if (char.class.equals(primitiveType)) { 105 | return '\0'; 106 | } else if (short.class.equals(primitiveType)) { 107 | return (short) 0; 108 | } else if (int.class.equals(primitiveType)) { 109 | return 0; 110 | } else if (long.class.equals(primitiveType)) { 111 | return 0L; 112 | } else if (float.class.equals(primitiveType)) { 113 | return 0F; 114 | } else if (double.class.equals(primitiveType)) { 115 | return 0D; 116 | } else { 117 | return null; 118 | } 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/benchmark/Java8Test.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.benchmark; 2 | 3 | import static java.util.stream.Collectors.joining; 4 | import static java.util.stream.Collectors.toList; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.stream.IntStream; 14 | 15 | import org.junit.Test; 16 | 17 | import com.github.trang.copiers.Copiers; 18 | import com.github.trang.copiers.base.Copier; 19 | import com.github.trang.copiers.test.bean.SimpleSource; 20 | import com.github.trang.copiers.test.bean.SimpleTarget; 21 | import com.google.common.base.Stopwatch; 22 | 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | /** 26 | * Java8 测试 27 | * 28 | * @author trang 29 | */ 30 | @Slf4j 31 | public class Java8Test { 32 | 33 | @Test 34 | public void array() { 35 | Copier copier = Copiers.create(SimpleSource.class, SimpleTarget.class); 36 | int size = 500000; 37 | SimpleSource[] sourceArray = IntStream.range(0, size).mapToObj(SimpleSource::new).toArray(SimpleSource[]::new); 38 | 39 | Stopwatch stopwatch = Stopwatch.createUnstarted(); 40 | 41 | stopwatch.reset().start(); 42 | SimpleTarget[] targetArray2 = Arrays.stream(sourceArray).parallel().map(copier::copy).toArray(SimpleTarget[]::new); 43 | log.info("多线程耗时: {}ms, 大小: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), targetArray2.length); 44 | 45 | stopwatch.reset().start(); 46 | SimpleTarget[] targetArray3 = copier.copyArray(sourceArray, SimpleTarget[]::new); 47 | log.info("单线程耗时: {}ms, 大小: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), targetArray3.length); 48 | } 49 | 50 | @Test 51 | public void list() { 52 | Copier copier = Copiers.create(SimpleSource.class, SimpleTarget.class); 53 | int size = 500000; 54 | List sourceList = IntStream.range(0, size).mapToObj(SimpleSource::new).collect(toList()); 55 | 56 | Stopwatch stopwatch = Stopwatch.createUnstarted(); 57 | 58 | stopwatch.start(); 59 | List targetList1 = copier.copyList(sourceList); 60 | log.info("单线程耗时: {}ms, 大小: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), targetList1.size()); 61 | 62 | List targetList2 = new ArrayList<>(sourceList.size()); 63 | stopwatch.reset().start(); 64 | sourceList.stream().parallel().map(copier::copy).forEach(targetList2::add); 65 | log.info("多线程耗时: {}ms, 大小: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), targetList2.size()); 66 | 67 | List targetList3 = new ArrayList<>(sourceList.size()); 68 | stopwatch.reset().start(); 69 | sourceList.stream().parallel().map(copier::copy).forEachOrdered(targetList3::add); 70 | log.info("多线程 Ordered 耗时: {}ms, 大小: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), targetList3.size()); 71 | } 72 | 73 | @Test 74 | public void concurrentMap() { 75 | ConcurrentHashMap map = new ConcurrentHashMap<>(); 76 | map.computeIfAbsent("A", key -> value()); 77 | map.computeIfAbsent("A", key -> value()); 78 | map.computeIfAbsent("B", key -> value()); 79 | System.out.println(map.size()); 80 | } 81 | 82 | private Object value() { 83 | System.out.println("---"); 84 | return "VALUE"; 85 | } 86 | 87 | @Test 88 | public void from() { 89 | Optional.of(Arrays.asList(null,1.1,2.22,3.333,4.4444,5.55555)) 90 | .filter(list -> !list.isEmpty()) 91 | .map(list -> list.stream() 92 | .filter(Objects::nonNull) 93 | .map(Objects::toString) 94 | .collect(joining(","))) 95 | .ifPresent(System.out::println); 96 | } 97 | 98 | @Test 99 | public void to() { 100 | Optional.ofNullable("1.1,2.22,3.333,4.4444,5.55555") 101 | .map(s -> s.split(",")) 102 | .map(arr -> Arrays.stream(arr).filter(Objects::nonNull).map(Double::parseDouble).collect(toList())) 103 | .ifPresent(System.out::println); 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/benchmark/BenchmarkTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.benchmark; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.junit.Test; 7 | 8 | import com.github.trang.copiers.Copiers; 9 | import com.github.trang.copiers.base.Copier; 10 | import com.github.trang.copiers.test.bean.Teacher; 11 | import com.github.trang.copiers.test.bean.TeacherEntity; 12 | import com.github.trang.copiers.test.util.MockUtils; 13 | import com.google.common.base.Stopwatch; 14 | import com.google.common.collect.ImmutableList; 15 | 16 | /** 17 | * 性能测试 18 | * 19 | * @author trang 20 | */ 21 | public class BenchmarkTest { 22 | 23 | // source object 24 | private final Teacher source = MockUtils.newTeacher(); 25 | // a thousand ~ a hundred million 26 | private final List timesList = ImmutableList.of(1_000, 10_000, 100_000, 1_000_000, 10_000_000/*, 100_000_000*/); 27 | 28 | /** 29 | * 传入对象 30 | */ 31 | @Test 32 | public void test2() { 33 | // cglib 34 | Copier cglibCopier = Copiers.createCglib(Teacher.class, TeacherEntity.class); 35 | Stopwatch cglibWatch = Stopwatch.createStarted(); 36 | for (Integer times : timesList) { 37 | long start = cglibWatch.elapsed(TimeUnit.MILLISECONDS); 38 | for (int i = 0; i < times; i++) { 39 | TeacherEntity target = new TeacherEntity(); 40 | // Copiers.createCglib(Teacher.class, TeacherEntity.class).copy(source, target); 41 | cglibCopier.copy(source, target); 42 | } 43 | long end = cglibWatch.elapsed(TimeUnit.MILLISECONDS); 44 | System.out.println("copier-2: cglib, " + "times:" + times + ", time:" + (end - start)); 45 | } 46 | 47 | // orika 48 | Copier orikaCopier = 49 | Copiers.createOrika(Teacher.class, TeacherEntity.class) 50 | .skip("sub") 51 | .field("name", "username") 52 | .register(); 53 | Stopwatch mapperWatch = Stopwatch.createStarted(); 54 | for (Integer times : timesList) { 55 | long start = mapperWatch.elapsed(TimeUnit.MILLISECONDS); 56 | for (int i = 0; i < times; i++) { 57 | TeacherEntity target = new TeacherEntity(); 58 | // Copiers.createOrika(Teacher.class, TeacherEntity.class) 59 | // .skip("sub") 60 | // .field("name", "username") 61 | // .register().copy(source, target); 62 | // Copiers.create(Teacher.class, TeacherEntity.class).copy(source, target); 63 | orikaCopier.copy(source, target); 64 | } 65 | long end = mapperWatch.elapsed(TimeUnit.MILLISECONDS); 66 | System.out.println("copier-2: orika, " + "times:" + times + ", time:" + (end - start)); 67 | } 68 | } 69 | 70 | /** 71 | * 重新生成对象 72 | */ 73 | @Test 74 | public void test1() { 75 | // cglib 76 | Copier cglibCopier = Copiers.createCglib(Teacher.class, TeacherEntity.class); 77 | Stopwatch cglibWatch = Stopwatch.createStarted(); 78 | for (Integer times : timesList) { 79 | long start = cglibWatch.elapsed(TimeUnit.MILLISECONDS); 80 | for (int i = 0; i < times; i++) { 81 | cglibCopier.copy(source); 82 | } 83 | long end = cglibWatch.elapsed(TimeUnit.MILLISECONDS); 84 | System.out.println("copier-1: cglib, " + "times:" + times + ", time:" + (end - start)); 85 | } 86 | 87 | // orika 88 | Copier orikaCopier = 89 | Copiers.createOrika(Teacher.class, TeacherEntity.class) 90 | .skip("sub") 91 | .field("name", "username") 92 | .register(); 93 | Stopwatch mapperWatch = Stopwatch.createStarted(); 94 | for (Integer times : timesList) { 95 | long start = mapperWatch.elapsed(TimeUnit.MILLISECONDS); 96 | for (int i = 0; i < times; i++) { 97 | orikaCopier.copy(source); 98 | } 99 | long end = mapperWatch.elapsed(TimeUnit.MILLISECONDS); 100 | System.out.println("copier-1: orika, " + "times:" + times + ", time:" + (end - start)); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/coveralls/MapCopiersTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.coveralls; 2 | 3 | import static com.google.common.collect.Sets.newHashSet; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.concurrent.CopyOnWriteArraySet; 15 | 16 | import org.junit.Test; 17 | 18 | import com.github.trang.copiers.MapCopiers; 19 | import com.github.trang.copiers.test.bean.Teacher; 20 | import com.github.trang.copiers.test.util.MockUtils; 21 | import com.google.common.collect.Lists; 22 | 23 | /** 24 | * MapCopiersTest 25 | *

26 | * Write the code. Change the world. 27 | * 28 | * @author trang 29 | * @date 2018/6/21 30 | */ 31 | public class MapCopiersTest { 32 | 33 | private Teacher teacher = MockUtils.newTeacher(); 34 | 35 | @Test 36 | public void beanToMap() { 37 | Map map = MapCopiers.createBeanToMap().copy(teacher); 38 | System.out.println(map); 39 | 40 | Map map2 = new HashMap<>(); 41 | map2.put("value", "value"); 42 | map2.put("name", "name"); 43 | MapCopiers.createBeanToMap().copy(teacher, map2); 44 | System.out.println(map2); 45 | } 46 | 47 | @Test 48 | public void beanToMapList() { 49 | List teachers = Lists.newArrayList(teacher, teacher); 50 | List> list = MapCopiers.createBeanToMap().copyList(teachers); 51 | assertTrue(list instanceof ArrayList); 52 | assertEquals(2, list.size()); 53 | for (Map map : list) { 54 | System.out.println(map); 55 | } 56 | 57 | List> linkedList = MapCopiers.createBeanToMap().copyList(teachers, LinkedList::new); 58 | assertTrue(linkedList instanceof LinkedList); 59 | assertEquals(2, linkedList.size()); 60 | for (Map map : linkedList) { 61 | System.out.println(map); 62 | } 63 | } 64 | 65 | @Test 66 | public void beanToMapSet() { 67 | Set teachers = newHashSet(teacher, teacher); 68 | Set> hashSet = MapCopiers.createBeanToMap().copySet(teachers); 69 | assertTrue(hashSet instanceof HashSet); 70 | assertEquals(1, hashSet.size()); 71 | for (Map map : hashSet) { 72 | System.out.println(map); 73 | } 74 | 75 | Set> cowSet = MapCopiers.createBeanToMap().copySet(teachers, CopyOnWriteArraySet::new); 76 | assertTrue(cowSet instanceof CopyOnWriteArraySet); 77 | assertEquals(1, cowSet.size()); 78 | for (Map map : cowSet) { 79 | System.out.println(map); 80 | } 81 | } 82 | 83 | @Test 84 | public void beanToMapArray() { 85 | List teachers = Lists.newArrayList(teacher, teacher); 86 | List> list = MapCopiers.createBeanToMap().copyList(teachers); 87 | assertTrue(list instanceof ArrayList); 88 | assertEquals(2, list.size()); 89 | for (Map map : list) { 90 | System.out.println(map); 91 | } 92 | 93 | List> linkedList = MapCopiers.createBeanToMap().copyList(teachers, LinkedList::new); 94 | assertTrue(linkedList instanceof LinkedList); 95 | assertEquals(2, linkedList.size()); 96 | for (Map map : linkedList) { 97 | System.out.println(map); 98 | } 99 | } 100 | 101 | @Test 102 | public void mapToBean() { 103 | Map map = MapCopiers.createBeanToMap().copy(teacher); 104 | map.remove("handsome"); 105 | System.out.println(map); 106 | 107 | Teacher teacher = MapCopiers.createMapToBean(Teacher.class).copy(map); 108 | System.out.println(teacher); 109 | 110 | Teacher u2 = new Teacher(); 111 | u2.setName("name"); 112 | u2.setHandsome(true); 113 | MapCopiers.createMapToBean(Teacher.class).copy(map, u2); 114 | System.out.println(u2); 115 | 116 | } 117 | 118 | @Test 119 | public void mapToBeans() { 120 | List teachers = Lists.newArrayList(teacher, teacher); 121 | System.out.println(teachers); 122 | List> map = MapCopiers.createBeanToMap().copyList(teachers); 123 | System.out.println(map); 124 | List list = MapCopiers.createMapToBean(Teacher.class).copyList(map); 125 | System.out.println(list); 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/orika/OrikaMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.orika; 2 | 3 | import com.github.trang.copiers.exception.CopierException; 4 | import com.github.trang.copiers.orika.converter.BooleanConverters; 5 | import com.github.trang.copiers.orika.converter.ListConverters; 6 | 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | import ma.glasnost.orika.MapperFactory; 10 | import ma.glasnost.orika.converter.ConverterFactory; 11 | import ma.glasnost.orika.impl.ConfigurableMapper; 12 | import ma.glasnost.orika.impl.DefaultMapperFactory; 13 | 14 | /** 15 | * 自定义 Orika 配置,参考自 #{@link ConfigurableMapper},没有直接继承 ConfigurableMapper 的原因是需要定制初始化过程 16 | * 17 | * @author trang 18 | */ 19 | @Getter 20 | public class OrikaMapper { 21 | 22 | private static final Boolean DEFAULT_USE_BUILTIN_BOOLEAN_CONVERTERS = Boolean.TRUE; 23 | private static final Boolean DEFAULT_USE_BUILTIN_LIST_CONVERTERS = Boolean.TRUE; 24 | private static final String DEFAULT_DELIMITER = ","; 25 | 26 | /** 是否启用内置的 Boolean Converters,默认开启 */ 27 | private Boolean useBuiltinListConverters; 28 | /** 是否启用内置的 List Converters,默认开启 */ 29 | private Boolean useBuiltinBooleanConverters; 30 | /** 启用 List Converters 时使用的分隔符,默认为 "," */ 31 | private String delimiter; 32 | /** Orika MapperFactory */ 33 | private DefaultMapperFactory factory; 34 | /** OrikaMapper 初始化标识,用 volatile 来保证多线程环境下的可见性 */ 35 | private volatile boolean initialized; 36 | 37 | public OrikaMapper() { 38 | this(DEFAULT_USE_BUILTIN_BOOLEAN_CONVERTERS, DEFAULT_USE_BUILTIN_LIST_CONVERTERS, DEFAULT_DELIMITER); 39 | } 40 | 41 | public OrikaMapper(Boolean useBuiltinBooleanConverters, Boolean useBuiltinListConverters, String delimiter) { 42 | this.useBuiltinBooleanConverters = useBuiltinBooleanConverters; 43 | this.useBuiltinListConverters = useBuiltinListConverters; 44 | this.delimiter = delimiter; 45 | init(); 46 | } 47 | 48 | private synchronized void init() { 49 | try { 50 | if (!initialized) { 51 | initialized = true; 52 | DefaultMapperFactory.Builder factoryBuilder = new DefaultMapperFactory.Builder(); 53 | configureFactoryBuilder(factoryBuilder); 54 | factory = factoryBuilder.build(); 55 | configure(factory); 56 | configureConverterFactory(factory.getConverterFactory()); 57 | } 58 | } catch (Exception e) { 59 | initialized = false; 60 | throw new CopierException("create orika mapper-factory failed.", e); 61 | } 62 | } 63 | 64 | protected void configureFactoryBuilder(DefaultMapperFactory.Builder factoryBuilder) { 65 | factoryBuilder.mapNulls(false); 66 | } 67 | 68 | protected void configure(MapperFactory factory) {} 69 | 70 | protected void configureConverterFactory(ConverterFactory converterFactory) { 71 | if (useBuiltinBooleanConverters) { 72 | converterFactory.registerConverter(new BooleanConverters.BooleanToByteConverter()); 73 | converterFactory.registerConverter(new BooleanConverters.BooleanToShortConverter()); 74 | converterFactory.registerConverter(new BooleanConverters.BooleanToIntegerConverter()); 75 | converterFactory.registerConverter(new BooleanConverters.BooleanToLongConverter()); 76 | converterFactory.registerConverter(new BooleanConverters.BooleanToDoubleConverter()); 77 | converterFactory.registerConverter(new BooleanConverters.BooleanToFloatConverter()); 78 | converterFactory.registerConverter(new BooleanConverters.BooleanToBigDecimalConverter()); 79 | } 80 | if (useBuiltinListConverters && delimiter != null) { 81 | converterFactory.registerConverter(new ListConverters.ByteListToStringConverter(delimiter)); 82 | converterFactory.registerConverter(new ListConverters.ShortListToStringConverter(delimiter)); 83 | converterFactory.registerConverter(new ListConverters.IntegerListToStringConverter(delimiter)); 84 | converterFactory.registerConverter(new ListConverters.LongListToStringConverter(delimiter)); 85 | converterFactory.registerConverter(new ListConverters.DoubleListToStringConverter(delimiter)); 86 | converterFactory.registerConverter(new ListConverters.FloatListToStringConverter(delimiter)); 87 | converterFactory.registerConverter(new ListConverters.BigDecimalListToStringConverter(delimiter)); 88 | } 89 | } 90 | 91 | public static OrikaMapper.Builder builder() { 92 | return new OrikaMapper.Builder(); 93 | } 94 | 95 | @ToString 96 | public static class Builder { 97 | 98 | private Boolean useBuiltinBooleanConverters; 99 | private Boolean useBuiltinListConverters; 100 | private String delimiter; 101 | 102 | public Builder() { 103 | this.useBuiltinBooleanConverters = DEFAULT_USE_BUILTIN_BOOLEAN_CONVERTERS; 104 | this.useBuiltinListConverters = DEFAULT_USE_BUILTIN_LIST_CONVERTERS; 105 | this.delimiter = DEFAULT_DELIMITER; 106 | } 107 | 108 | public OrikaMapper.Builder useBuiltinBooleanConverters(Boolean useBuiltinBooleanConverters) { 109 | this.useBuiltinBooleanConverters = useBuiltinBooleanConverters; 110 | return this; 111 | } 112 | 113 | public OrikaMapper.Builder useBuiltinListConverters(Boolean useBuiltinListConverters) { 114 | this.useBuiltinListConverters = useBuiltinListConverters; 115 | return this; 116 | } 117 | 118 | public OrikaMapper.Builder delimiter(String delimiter) { 119 | this.delimiter = delimiter; 120 | return this; 121 | } 122 | 123 | public OrikaMapper build() { 124 | return new OrikaMapper(useBuiltinBooleanConverters, useBuiltinListConverters, delimiter); 125 | } 126 | 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/coveralls/OrikaCopierTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.coveralls; 2 | 3 | import static com.github.trang.copiers.test.util.MockUtils.newTeacher; 4 | import static com.github.trang.copiers.test.util.MockUtils.newTeacherEntity; 5 | import static com.google.common.collect.Lists.newArrayList; 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | import org.junit.Ignore; 9 | import org.junit.Test; 10 | 11 | import com.github.trang.copiers.Copiers; 12 | import com.github.trang.copiers.base.Copier; 13 | import com.github.trang.copiers.orika.OrikaCopier; 14 | import com.github.trang.copiers.test.bean.SimpleSource; 15 | import com.github.trang.copiers.test.bean.SimpleTarget; 16 | import com.github.trang.copiers.test.bean.Student; 17 | import com.github.trang.copiers.test.bean.StudentEntity; 18 | import com.github.trang.copiers.test.bean.Teacher; 19 | import com.github.trang.copiers.test.bean.TeacherEntity; 20 | 21 | import ma.glasnost.orika.CustomMapper; 22 | import ma.glasnost.orika.MappingContext; 23 | 24 | /** 25 | * OrikaCopierTest 26 | *

27 | * Write the code. Change the world. 28 | * 29 | * @author trang 30 | * @date 2018/6/21 31 | */ 32 | @SuppressWarnings("Duplicates") 33 | public class OrikaCopierTest { 34 | 35 | private static Copier copier = Copiers.create(Teacher.class, TeacherEntity.class); 36 | private static Copier reverseCopier = Copiers.create(TeacherEntity.class, Teacher.class); 37 | 38 | // orika 支持级联拷贝,当 pojo 中有级联对象时需要提前注册映射关系,否则会抛出异常 39 | private static Copier useless1 = Copiers.create(Student.class, StudentEntity.class); 40 | private static Copier useless2 = Copiers.create(StudentEntity.class, Student.class); 41 | 42 | @Test 43 | public void copyTest() { 44 | Teacher teacher = newTeacher(); 45 | TeacherEntity target = copier.copy(teacher); 46 | // 名称不同,忽略转换 47 | assertThat(target.getUsername()).isNull(); 48 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 49 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 50 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 51 | // 类型不同,由默认转换器转换 52 | assertThat(target.getWeight()).isEqualTo(teacher.getWeight().longValue()); 53 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 54 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 55 | // 名称不同,忽略转换 56 | assertThat(target.getLover().getUsername()).isNull(); 57 | // 相同类型的级联不需要提前注册映射关系 58 | assertThat(target.getLover().getAge()).isEqualTo(teacher.getLover().getAge()); 59 | // 支持级联拷贝,可以转换 60 | assertThat(target.getStudents().toString()).isEqualTo(teacher.getStudents().toString()); 61 | } 62 | 63 | @Test 64 | public void reverseCopyTest() { 65 | TeacherEntity teacher = newTeacherEntity(); 66 | Teacher target = reverseCopier.copy(teacher); 67 | // 名称不同 68 | assertThat(target.getName()).isNull(); 69 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 70 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 71 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 72 | // 类型不同 73 | assertThat(target.getWeight()).isEqualTo(teacher.getWeight().floatValue()); 74 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 75 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 76 | // 类型不同 77 | assertThat(target.getLover().getName()).isNull(); 78 | assertThat(target.getLover().getAge()).isEqualTo(teacher.getLover().getAge()); 79 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 80 | assertThat(target.getStudents().toString()).isEqualTo(teacher.getStudents().toString()); 81 | } 82 | 83 | @Test 84 | @Ignore 85 | public void orika() { 86 | Teacher teacher = newTeacher(); 87 | OrikaCopier copier = Copiers.createOrika(Teacher.class, TeacherEntity.class) 88 | .skip("age", "sex") 89 | .field("name", "username") 90 | .field("wife.sex", "wife.sex") 91 | .customize(new CustomMapper() { 92 | @Override 93 | public void mapAtoB(Teacher source, TeacherEntity target, MappingContext context) { 94 | target.setUsername("prefix:" + source.getName()); 95 | } 96 | }) 97 | .register(); 98 | 99 | TeacherEntity target = copier.copy(teacher); 100 | 101 | System.out.println(teacher); 102 | System.out.println(target); 103 | 104 | // age 属性跳过拷贝 105 | assertThat(target.getAge()).isNull(); 106 | // sex 属性跳过拷贝 107 | assertThat(target.getSex()).isNull(); 108 | // weight 属性在 teacher 对象中没有设置值 109 | assertThat(target.getWeight()).isNull(); 110 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 111 | assertThat(target.getUsername()).isEqualTo("prefix:" + teacher.getName()); 112 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 113 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 114 | } 115 | 116 | @Test 117 | public void list2String() { 118 | Copier copier = Copiers.createOrika(SimpleSource.class, SimpleTarget.class) 119 | .field("statusList", "statuses") 120 | .field("typeList", "types") 121 | .skip("id") 122 | // 自定义 constructor() 需在 skip() 之后 123 | .constructor("time") 124 | .register(); 125 | Copier copier2 = Copiers.createOrika(SimpleSource.class, SimpleTarget.class) 126 | .field("statusList", "statuses") 127 | .field("typeList", "types") 128 | .skip("id") 129 | // 自定义 constructor() 需在 skip() 之后 130 | .constructor("time") 131 | .register(); 132 | SimpleSource source = new SimpleSource(1, System.currentTimeMillis()); 133 | // source.setStatusList(newArrayList(1,2,3)); 134 | source.setTypeList(newArrayList(1.1,2.22,3.333)); 135 | SimpleTarget target = copier.copy(source); 136 | System.out.println(target); 137 | } 138 | 139 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copiers 2 | 3 | [![Build Status](https://img.shields.io/travis/drtrang/Copiers/master.svg?style=flat-square)](https://www.travis-ci.org/drtrang/Copiers) 4 | [![Coverage Status](https://img.shields.io/coveralls/drtrang/Copiers/master.svg?style=flat-square)](https://coveralls.io/github/drtrang/Copiers?branch=master) 5 | [![Maven Central](https://img.shields.io/maven-central/v/com.github.drtrang/copiers.svg?style=flat-square)](https://maven-badges.herokuapp.com/maven-central/com.github.drtrang/copiers) 6 | [![GitHub Release](https://img.shields.io/github/release/drtrang/Copiers.svg?style=flat-square)](https://github.com/drtrang/Copiers/releases) 7 | [![License](http://img.shields.io/badge/license-apache%202-blue.svg?style=flat-square)](https://github.com/drtrang/Copiers/blob/master/LICENSE) 8 | 9 | Copiers 是一个优雅的 Bean 拷贝工具,可通过友好的 Fluent API 帮助用户完成拷贝对象的操作。 10 | 11 | ## 依赖 12 | ```xml 13 | 14 | 15 | com.github.drtrang 16 | copiers 17 | 2.5.4 18 | 19 | 20 | 21 | 22 | com.github.drtrang 23 | copiers 24 | 1.4.2 25 | 26 | ``` 27 | 28 | ## 底层实现 29 | Copiers 目前有两种实现:`Cglib` & `Orika`,用户可以通过工厂方法来切换底层的拷贝方式。 30 | 31 | ```java 32 | // orika 33 | Copiers.create(Class sourceClass, Class targetClass) 34 | Copiers.createOrika(Class sourceClass, Class targetClass) 35 | // cglib 36 | Copiers.createCglib(Class sourceClass, Class targetClass) 37 | Copiers.createCglib(Class sourceClass, Class targetClass, Converter converter) 38 | ``` 39 | 40 | ### Cglib 41 | Cglib 中的 BeanCopier 是目前性能最好的拷贝方式,基于 ASM 字节码增强技术,千万次拷贝仅需毫秒即可完成,但高性能带来的显著缺点是功能单一、拓展性差,BeanCopier 仅支持源对象到目标对象的**完全拷贝**,不支持自定义映射,Convert 拓展也只能对拷贝的 value 做处理,很多情况下不满足实际的业务需求。 42 | 43 | > **注意:** 44 | > 1. BeanCopier 只拷贝名称和类型都相同的属性 45 | > 2. 当目标类的 setter 方法少于 getter 方法时,会导致创建 BeanCopier 失败 46 | > 3. 一旦使用 Converter,BeanCopier 将完全使用 Converter 中定义的规则去拷贝,所以在 `convert()` 方法中要考虑到所有的属性,否则会抛出 `ClassCastException` 47 | 48 | ### Orika 49 | [Orika](https://github.com/orika-mapper/orika) 基于 Javassist 字节码技术,千万次拷贝在 **5s** 左右。性能虽不如 Cglib,但 Orika 的优点在于灵活性、扩展性强,详细介绍可以查看 Orika 的 Github:https://github.com/orika-mapper/orika,另外强烈推荐这篇使用教程:http://www.baeldung.com/orika-mapping 50 | 51 | > **注意:** 52 | > 1. 拷贝结果为浅拷贝 53 | > 2. 支持级联拷贝,但是需要提前注册好级联对象之间的映射关系,且可以使用 `parent()` 方法来指定父类 54 | > 3. 支持源对象中的集合类型直接拷贝到目标对象的集合 55 | > 4. 不同类型有默认的 Converter 做转换 56 | 57 | ## 使用方式 58 | 通过工厂方法建立 sourceClass 与 targetClass 之间的关系后,调用 `copy()` 方法即可完成 Bean 拷贝,调用 `copyList()` 方法即可完成 List 拷贝,简洁高效。 59 | 60 | ### Cglib 61 | ```java 62 | // 建立 User.class 与 UserEntity.class 之间的映射关系 63 | Copier copier = Copiers.createCglib(User.class, UserEntity.class); 64 | 65 | // 拷贝对象,创建新对象 66 | User user = User.of("trang", 25); 67 | UserEntity entity = copier.copy(user); 68 | 69 | // 拷贝对象,传入已有对象,完全拷贝 70 | User user = User.of("trang", null); 71 | UserEntity entity = UserEntity.of("meng", 24); 72 | copier.copy(user, entity); 73 | 74 | // 拷贝 List,创建新 List 75 | User trang = User.of("trang", 25); 76 | User meng = User.of("meng", 24); 77 | List family = ImmutableList.of(trang, meng); 78 | List entries = copier.copyList(family); 79 | ``` 80 | 81 | ### Orika 82 | ```java 83 | // 建立 User.class 与 UserEntity.class 之间的映射关系 84 | Copier copier = Copiers.create(User.class, UserEntity.class); 85 | 86 | // 拷贝对象,创建新对象 87 | User user = User.of("trang", 25); 88 | UserEntity entity = copier.copy(user); 89 | 90 | // 拷贝对象,传入已有对象,不会拷贝值为 null 的属性(可以配置) 91 | User user = User.of("trang", null); 92 | UserEntity entity = UserEntity.of("meng", 24); 93 | copier.copy(user, entity); 94 | 95 | // 拷贝 List,创建新 List 96 | User trang = User.of("trang", 25); 97 | User meng = User.of("meng", 24); 98 | List family = ImmutableList.of(trang, meng); 99 | List entries = copier.copyList(family); 100 | ``` 101 | 102 | ## Orika 进阶 103 | Orika 支持强大的自定义关系映射,并且使用缓存技术,一次注册后续直接使用。 104 | 105 | ```java 106 | // 跳过拷贝的属性,支持配置多个 107 | // Orika 默认使用全参构造,这时 skip() 不生效,需要使用不包含 skip 属性的构造方法, 108 | // 所以 Copiers 将默认值改为了无参构造,用户也可以在调用 skip() 后使用 constructor() 方法自己指定 109 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 110 | .skip("age", "sex") 111 | .register(); 112 | 113 | // 将源对象的 `name` 属性映射到目标对象的 `username` 属性 114 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 115 | .field("name", "username") 116 | .register(); 117 | 118 | // 开启拷贝 null 值,默认 Orika 不会将源对象中值为 null 的属性拷贝到目标对象中,如有需要可以手动开启 119 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 120 | .nulls() 121 | .register(); 122 | 123 | // 全局自定义映射关系,若和其它方法结合使用则在最后执行 124 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 125 | .customize(new CustomMapper() { 126 | @Override 127 | public void mapAtoB(User source, UserEntity target, MappingContext context) { 128 | target.setUsername("prefix:" + source.getName()); 129 | } 130 | }) 131 | .register(); 132 | ``` 133 | 134 | 当然,以上映射关系可以任意搭配使用,同样只需一次注册。 135 | 136 | ```java 137 | // 创建 copier 138 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 139 | // 跳过拷贝 140 | .skip("age", "sex") 141 | // 自定义属性映射 142 | .field("name", "username") 143 | // 全局自定义映射关系 144 | .customize(new CustomMapper() { 145 | @Override 146 | public void mapAtoB(User source, UserEntity target, MappingContext context) { 147 | target.setUsername("prefix:" + source.getName()); 148 | } 149 | }) 150 | .register(); 151 | ``` 152 | 153 | 如果你在使用 Java8 那就更好了,利用 Copiers 可以更容易的完成拷贝操作。 154 | 155 | ```java 156 | // 创建 copier 157 | Copier copier = Copiers.createOrika(User.class, UserEntity.class) 158 | .field("name", "username") 159 | .register(); 160 | // 使用 Stream 拷贝 List 161 | sourceList.stream().map(copier::copy).collect(toList()); //copier.copyList(sourceList); 162 | // 使用并行 Stream 拷贝 List 163 | sourceList.parallelStream().map(copier::copy).collect(toList()); 164 | // 使用 Optional 拷贝 List 165 | Optional.of(name) 166 | .map(service::selectByName) 167 | .map(copier::copyList) 168 | .orElse(emptyList()); 169 | ``` 170 | 171 | ## Change Log 172 | [Release Notes](https://github.com/drtrang/Copiers/releases) 173 | 174 | ## TODO 175 | 任何意见和建议可以提 [ISSUE](https://github.com/drtrang/Copiers/issues),我会酌情加到 [TODO List](https://github.com/drtrang/Copiers/blob/master/TODO.md),一般情况一周内迭代完毕。 176 | 177 | ## About Me 178 | QQ:349096849
179 | Email:donghao.l@hotmail.com
180 | Blog:[Trang's Blog](http://blog.trang.space) 181 | -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/orika/OrikaCopier.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.orika; 2 | 3 | import static com.github.trang.copiers.util.Preconditions.checkNotNull; 4 | 5 | import com.github.trang.copiers.AbstractCopier; 6 | import com.github.trang.copiers.base.Copier; 7 | import com.github.trang.copiers.exception.CopierException; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | import ma.glasnost.orika.BoundMapperFacade; 11 | import ma.glasnost.orika.Mapper; 12 | import ma.glasnost.orika.metadata.ClassMapBuilder; 13 | 14 | /** 15 | * 基于 Orika 的 #{@link Copier} 实现 16 | * 17 | * @author trang 18 | */ 19 | @Slf4j(topic = "copiers") 20 | public class OrikaCopier extends AbstractCopier, F, T> { 21 | 22 | /** 23 | * 创建默认的 OrikaCopier 24 | * 25 | * @param sourceClass 源类型 26 | * @param targetClass 目标类型 27 | */ 28 | private OrikaCopier(Class sourceClass, Class targetClass) { 29 | super(sourceClass, targetClass, 30 | OrikaMapperFactory.getMapperFactory().getMapperFacade(sourceClass, targetClass)); 31 | } 32 | 33 | @Override 34 | public T copy(F source) { 35 | checkNotNull(source, "source bean cannot be null!"); 36 | try { 37 | return copier.map(source); 38 | } catch (Exception e) { 39 | throw new CopierException("create object fail, class: " + targetClass.getName(), e); 40 | } 41 | } 42 | 43 | @Override 44 | public void copy(F source, T target) { 45 | checkNotNull(source, "source bean cannot be null!"); 46 | checkNotNull(target, "target bean cannot be null!"); 47 | try { 48 | copier.map(source, target); 49 | } catch (Exception e) { 50 | throw new CopierException("create object fail, class: " + targetClass.getName(), e); 51 | } 52 | } 53 | 54 | public static class Builder { 55 | 56 | /** 57 | * 自定义 Copier 58 | */ 59 | private ClassMapBuilder builder; 60 | 61 | public Builder(Class sourceClass, Class targetClass) { 62 | this.builder = OrikaMapperFactory.getMapperFactory().classMap(sourceClass, targetClass); 63 | } 64 | 65 | /** 66 | * 自定义属性映射 67 | * 68 | * @param sourceField 源对象属性名称 69 | * @param targetField 目标对象属性名称 70 | * @return this 71 | */ 72 | public OrikaCopier.Builder field(String sourceField, String targetField) { 73 | builder.field(sourceField, targetField); 74 | return this; 75 | } 76 | 77 | /** 78 | * 自定义属性映射 79 | * 80 | * @param sourceField 源对象属性名称 81 | * @param targetField 目标对象属性名称 82 | * @param converterId 自定义转换规则 83 | * @return this 84 | */ 85 | public OrikaCopier.Builder field(String sourceField, String targetField, String converterId) { 86 | builder.fieldMap(sourceField, targetField) 87 | .converter(converterId) 88 | .add(); 89 | return this; 90 | } 91 | 92 | /** 93 | * 自定义属性映射 94 | * 95 | * @param sourceField 源对象属性名称 96 | * @param targetField 目标对象属性名称 97 | * @param sourceType 源对象属性类型 98 | * @param targetType 目标对象属性类型 99 | * @return this 100 | */ 101 | public OrikaCopier.Builder field(String sourceField, String targetField, Class sourceType, Class targetType) { 102 | builder.fieldMap(sourceField, targetField) 103 | .aElementType(sourceType) 104 | .bElementType(targetType) 105 | .add(); 106 | return this; 107 | } 108 | 109 | /** 110 | * 自定义属性映射 111 | * 112 | * @param sourceField 源对象属性名称 113 | * @param targetField 目标对象属性名称 114 | * @param sourceType 源对象属性类型 115 | * @param targetType 目标对象属性类型 116 | * @param converterId 自定义转换规则 117 | * @return this 118 | */ 119 | public OrikaCopier.Builder field(String sourceField, String targetField, Class sourceType, Class targetType, String converterId) { 120 | builder.fieldMap(sourceField, targetField) 121 | .aElementType(sourceType) 122 | .bElementType(targetType) 123 | .converter(converterId) 124 | .add(); 125 | return this; 126 | } 127 | 128 | /** 129 | * 是否拷贝值为 null 的属性 130 | * 131 | * @return this 132 | */ 133 | public OrikaCopier.Builder nulls() { 134 | builder.mapNulls(true); 135 | return this; 136 | } 137 | 138 | /** 139 | * 排除属性 140 | * 141 | * @param fields 要排除的属性名称 142 | * @return this 143 | */ 144 | public OrikaCopier.Builder skip(String... fields) { 145 | if (fields != null && fields.length != 0) { 146 | for (String field : fields) { 147 | builder.exclude(field); 148 | } 149 | // Orika 默认使用全参构造,这时 skip() 不生效,需要使用不包含 skip 属性的构造方法, 150 | // 所以 Copiers 将默认值改为了无参构造,用户也可以在调用 skip() 后使用 constructor() 方法自己指定 151 | // https://github.com/orika-mapper/orika/issues/135 152 | builder.constructorB(); 153 | } 154 | return this; 155 | } 156 | 157 | /** 158 | * 自定义映射规则 159 | * 160 | * @param customizedMapper 自定义映射规则 161 | * @return this 162 | */ 163 | public Builder customize(Mapper customizedMapper) { 164 | builder.customize(customizedMapper); 165 | return this; 166 | } 167 | 168 | /** 169 | * 自定义构造器 170 | * 171 | * @param args 构造参数 172 | * @return this 173 | */ 174 | public Builder constructor(String... args) { 175 | builder.constructorB(args); 176 | return this; 177 | } 178 | 179 | /** 180 | * 自定义构造器 181 | * 182 | * @param parentSourceClass 源对象父类类型 183 | * @param parentTargetClass 目标对象父类类型 184 | * @return this 185 | */ 186 | public OrikaCopier.Builder parent(Class parentSourceClass, Class parentTargetClass) { 187 | builder.use(parentSourceClass, parentTargetClass); 188 | return this; 189 | } 190 | 191 | /** 192 | * 构建执行拷贝的 Copier 193 | * 194 | * @return copier 195 | */ 196 | public OrikaCopier register() { 197 | builder.byDefault().register(); 198 | Class sourceClass = builder.getAType().getRawType(); 199 | Class targetClass = builder.getBType().getRawType(); 200 | return new OrikaCopier<>(sourceClass, targetClass); 201 | } 202 | 203 | } 204 | 205 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trang/copiers/orika/converter/ListConverters.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.orika.converter; 2 | 3 | import static java.util.stream.Collectors.joining; 4 | import static java.util.stream.Collectors.toList; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.RoundingMode; 8 | import java.text.NumberFormat; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.function.Function; 13 | 14 | import lombok.RequiredArgsConstructor; 15 | import ma.glasnost.orika.MappingContext; 16 | import ma.glasnost.orika.converter.BidirectionalConverter; 17 | import ma.glasnost.orika.metadata.Type; 18 | 19 | /** 20 | * List 转换器,用于 List 与常用类型之间的互相转换,默认会注册到 Orika 21 | * 22 | * @author trang 23 | */ 24 | public class ListConverters { 25 | 26 | @RequiredArgsConstructor 27 | public static class ByteListToStringConverter extends BidirectionalConverter, String> { 28 | 29 | private final String delimiter; 30 | 31 | @Override 32 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 33 | return convertList2String(source, delimiter, Object::toString); 34 | } 35 | 36 | @Override 37 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 38 | return convertString2List(source, delimiter, Byte::parseByte); 39 | } 40 | } 41 | 42 | @RequiredArgsConstructor 43 | public static class ShortListToStringConverter extends BidirectionalConverter, String> { 44 | 45 | private final String delimiter; 46 | 47 | @Override 48 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 49 | return convertList2String(source, delimiter, Object::toString); 50 | } 51 | 52 | @Override 53 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 54 | return convertString2List(source, delimiter, Short::parseShort); 55 | } 56 | } 57 | 58 | @RequiredArgsConstructor 59 | public static class IntegerListToStringConverter extends BidirectionalConverter, String> { 60 | 61 | private final String delimiter; 62 | 63 | @Override 64 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 65 | return convertList2String(source, delimiter, Object::toString); 66 | } 67 | 68 | @Override 69 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 70 | return convertString2List(source, delimiter, Integer::parseInt); 71 | } 72 | } 73 | 74 | @RequiredArgsConstructor 75 | public static class LongListToStringConverter extends BidirectionalConverter, String> { 76 | 77 | private final String delimiter; 78 | 79 | @Override 80 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 81 | return convertList2String(source, delimiter, Object::toString); 82 | } 83 | 84 | @Override 85 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 86 | return convertString2List(source, delimiter, Long::parseLong); 87 | } 88 | 89 | } 90 | 91 | @RequiredArgsConstructor 92 | public static class DoubleListToStringConverter extends BidirectionalConverter, String> { 93 | 94 | private final String delimiter; 95 | private NumberFormat formatter; 96 | 97 | public DoubleListToStringConverter(String delimiter, Integer scale, RoundingMode mode) { 98 | this.delimiter = delimiter; 99 | if (scale != null && scale >= 0) { 100 | this.formatter = createNumberFormatter(scale, mode); 101 | } 102 | } 103 | 104 | @Override 105 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 106 | return convertList2String(source, delimiter, e -> formatter != null ? formatter.format(e) : e.toString()); 107 | } 108 | 109 | @Override 110 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 111 | return convertString2List(source, delimiter, Double::parseDouble); 112 | } 113 | 114 | } 115 | 116 | @RequiredArgsConstructor 117 | public static class FloatListToStringConverter extends BidirectionalConverter, String> { 118 | 119 | private final String delimiter; 120 | private NumberFormat formatter; 121 | 122 | public FloatListToStringConverter(String delimiter, Integer scale, RoundingMode mode) { 123 | this.delimiter = delimiter; 124 | if (scale != null) { 125 | this.formatter = createNumberFormatter(scale, mode); 126 | } 127 | } 128 | 129 | @Override 130 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 131 | return convertList2String(source, delimiter, e -> formatter != null ? formatter.format(e) : e.toString()); 132 | } 133 | 134 | @Override 135 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 136 | return convertString2List(source, delimiter, Float::parseFloat); 137 | } 138 | 139 | } 140 | 141 | @RequiredArgsConstructor 142 | public static class BigDecimalListToStringConverter extends BidirectionalConverter, String> { 143 | 144 | private final String delimiter; 145 | 146 | @Override 147 | public String convertTo(List source, Type destinationType, MappingContext mappingContext) { 148 | return convertList2String(source, delimiter, BigDecimal::toPlainString); 149 | } 150 | 151 | @Override 152 | public List convertFrom(String source, Type> destinationType, MappingContext mappingContext) { 153 | return convertString2List(source, delimiter, BigDecimal::new); 154 | } 155 | 156 | } 157 | 158 | private static NumberFormat createNumberFormatter(Integer scale, RoundingMode mode) { 159 | NumberFormat format = NumberFormat.getInstance(); 160 | format.setMinimumFractionDigits(scale); 161 | format.setMaximumFractionDigits(scale); 162 | format.setRoundingMode(mode); 163 | return format; 164 | } 165 | 166 | private static String convertList2String(List source, String delimiter, Function transformer) { 167 | return source.stream() 168 | .filter(Objects::nonNull) 169 | .map(transformer) 170 | .collect(joining(delimiter)); 171 | } 172 | 173 | private static List convertString2List(String source, String delimiter, Function transformer) { 174 | return Arrays.stream(source.split(delimiter)) 175 | .filter(s -> s != null && !s.isEmpty()) 176 | .map(transformer) 177 | .collect(toList()); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /src/test/java/com/github/trang/copiers/test/coveralls/CglibCopierTest.java: -------------------------------------------------------------------------------- 1 | package com.github.trang.copiers.test.coveralls; 2 | 3 | import static com.github.trang.copiers.test.util.MockUtils.newTeacher; 4 | import static com.github.trang.copiers.test.util.MockUtils.newTeacherEntity; 5 | import static com.google.common.collect.Lists.newArrayList; 6 | import static com.google.common.collect.Sets.newHashSet; 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | import java.util.ArrayList; 10 | import java.util.LinkedHashSet; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.concurrent.ConcurrentSkipListSet; 15 | import java.util.concurrent.CopyOnWriteArrayList; 16 | import java.util.concurrent.CopyOnWriteArraySet; 17 | 18 | import org.junit.Test; 19 | 20 | import com.github.trang.copiers.Copiers; 21 | import com.github.trang.copiers.base.Copier; 22 | import com.github.trang.copiers.exception.CopierException; 23 | import com.github.trang.copiers.test.bean.Teacher; 24 | import com.github.trang.copiers.test.bean.TeacherEntity; 25 | import com.github.trang.copiers.test.plugin.TestConverter; 26 | 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | /** 30 | * CglibCopierTest 31 | *

32 | * Write the code. Change the world. 33 | * 34 | * @author trang 35 | * @date 2018/6/21 36 | */ 37 | @Slf4j 38 | @SuppressWarnings("Duplicates") 39 | public class CglibCopierTest { 40 | 41 | private static Copier copier = Copiers.createCglib(Teacher.class, TeacherEntity.class); 42 | private static Copier converterCopier = Copiers.createCglib(Teacher.class, TeacherEntity.class, new TestConverter()); 43 | private static Copier reverseCopier = Copiers.createCglib(TeacherEntity.class, Teacher.class); 44 | 45 | /** 46 | * cglib 仅支持相同名称、相同类型的拷贝,不支持自定义 47 | */ 48 | @Test 49 | public void copyTest() { 50 | // 名称不同,忽略转换 51 | Teacher teacher = newTeacher(); 52 | TeacherEntity target = copier.copy(teacher); 53 | // 名称不同 54 | assertThat(target.getUsername()).isNull(); 55 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 56 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 57 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 58 | // 类型不同 59 | assertThat(target.getWeight()).isNull(); 60 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 61 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 62 | // 类型不同 63 | assertThat(target.getLover()).isNull(); 64 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 65 | assertThat(target.getStudents()).isEqualTo(teacher.getStudents()); 66 | } 67 | 68 | @Test 69 | public void reverseCopyTest() { 70 | TeacherEntity teacher = newTeacherEntity(); 71 | Teacher target = reverseCopier.copy(teacher); 72 | // 名称不同 73 | assertThat(target.getName()).isNull(); 74 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 75 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 76 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 77 | // 类型不同 78 | assertThat(target.getWeight()).isNull(); 79 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 80 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 81 | // 类型不同 82 | assertThat(target.getLover()).isNull(); 83 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 84 | assertThat(target.getStudents()).isEqualTo(teacher.getStudents()); 85 | } 86 | 87 | @Test(expected = CopierException.class) 88 | public void converterCopyTest() { 89 | Teacher teacher = newTeacher(); 90 | // converter 要考虑所有的场景,这里 lover 字段会抛出转换异常 91 | converterCopier.copy(teacher); 92 | } 93 | 94 | @Test 95 | public void copyArrayTest() { 96 | Teacher teacher = newTeacher(); 97 | Teacher[] teachers = {teacher, teacher}; 98 | TeacherEntity[] targets = copier.copyArray(teachers, TeacherEntity[]::new); 99 | for (TeacherEntity target : targets) { 100 | // 名称不同 101 | assertThat(target.getUsername()).isNull(); 102 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 103 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 104 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 105 | // 类型不同 106 | assertThat(target.getWeight()).isNull(); 107 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 108 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 109 | // 类型不同 110 | assertThat(target.getLover()).isNull(); 111 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 112 | assertThat(target.getStudents()).isEqualTo(teacher.getStudents()); 113 | } 114 | // 简单测试一下 115 | copier.copyArray(teachers); 116 | } 117 | 118 | @Test 119 | public void copyListTest() { 120 | Teacher teacher = newTeacher(); 121 | ArrayList teachers = newArrayList(teacher, teacher); 122 | List targets = copier.copyList(teachers); 123 | for (TeacherEntity target : targets) { 124 | // 名称不同 125 | assertThat(target.getUsername()).isNull(); 126 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 127 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 128 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 129 | // 类型不同 130 | assertThat(target.getWeight()).isNull(); 131 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 132 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 133 | // 类型不同 134 | assertThat(target.getLover()).isNull(); 135 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 136 | assertThat(target.getStudents()).isEqualTo(teacher.getStudents()); 137 | } 138 | // 简单测试一下 139 | copier.copyList(teachers, LinkedList::new); 140 | copier.copyList(teachers, CopyOnWriteArrayList::new); 141 | } 142 | 143 | @Test 144 | public void copySetTest() { 145 | Teacher teacher = newTeacher(); 146 | Set teachers = newHashSet(teacher, teacher); 147 | Set targets = copier.copySet(teachers); 148 | for (TeacherEntity target : targets) { 149 | // 名称不同 150 | assertThat(target.getUsername()).isNull(); 151 | assertThat(target.getAge()).isEqualTo(teacher.getAge()); 152 | assertThat(target.getSex()).isEqualTo(teacher.getSex()); 153 | assertThat(target.getHeight()).isEqualTo(teacher.getHeight()); 154 | // 类型不同 155 | assertThat(target.getWeight()).isNull(); 156 | assertThat(target.getHandsome()).isEqualTo(teacher.getHandsome()); 157 | assertThat(target.getHouse()).isEqualTo(teacher.getHouse()); 158 | // 类型不同 159 | assertThat(target.getLover()).isNull(); 160 | // 这里解释一下为什么相等,因为两个字段名称相同且类型都为 list 161 | assertThat(target.getStudents()).isEqualTo(teacher.getStudents()); 162 | } 163 | // 简单测试一下 164 | copier.copySet(teachers, LinkedHashSet::new); 165 | copier.copySet(teachers, CopyOnWriteArraySet::new); 166 | copier.copySet(teachers, ConcurrentSkipListSet::new); 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------