文章目录
一.前言
作为一名有梦想的CRUD工程师,你是否也为Entity与Dto的转换问题而感到头痛?烦人的get/set让我们本该优雅的代码变得冗长且臃肿,拖慢我们本该高速的开发效率,简直不能忍有没有?
经过一段时间的折磨之后我决定问问"度娘",结果看到了一些解决方案,但是这些工具大多都治标不治本,有的只能帮我们转换那些属性名相同的属性,那些不同名的属性还得自己get/set来操作,好不容易找到可以转换属性名不同的工具,但又发现这些工具需要使用大量的注解去标注Entity类和Dto类,还是不够优雅!!而且这类工具大多也无法实现聚合对象的转换。
痛定思痛,最终决定找资料自写一个转换工具,于是就有了这篇博客!(工具中的转化模式并非原创,但是基于模式的实现实为本人原创,不喜还望勿喷!!)
二.下载地址
包含源码和jar包,由于在项目中使用了Lambda表达式,使用时请务必使用JDK8及以上版本
下载地址:网盘地址
提取码:l4s9
三.工具的使用说明
3.1创建Entity类
1.Type类
@Data
public class Type {
//类型ID
private String id;
//类型名
private String name;
}
1.Goods类
@Data
public class Goods {
//商品ID
private String id;
//商品名称
private String name;
//商品单价
private Double price;
//商品所属类型
private Type type;
}
1.Oeder类
@Data
public class Order {
//订单ID
private String id;
//创建时间
private Date createTime;
//购买人姓名
private String buyerName;
//订单总价
private Double totalPrice;
//订单中的商品
private Goods goods;
}
3.2创建Dto类
1.OederDto类
@Data
public class OrderDto {
//订单ID
private String id;
//创建时间
private Date createTime;
//购买人姓名
private String buyerName;
//订单总价
private Double totalPrice;
//商品名称
private String goodsName;
//商品单价
private Double price;
//商品类型
private String typeName;
}
3.3 创建负责转换Order类与OrderDto类的接口
3.3.1 接口编写规范:
注:该接口必须继承LuckyConversion<TEntity,TDto>接口,设置泛型,继承(不用实现)toDto和toEntity方法,在方法上使用@Mappings注解和@Mapping注解设置映射规则!
3.3.2 属性映射规范:
1.Entity与Dto同名的一级属性自动映射,无需配置.
2.Entity与Dto不同名的一级属性映射规则: @Mapping(source=“待转换的属性名”,target=“目标属性的属性名”)
3.Entity与Dto不同名的非一级属性映射规则:@Mapping(source=“属性名.属性中的属性名”,target=“目标属性的属性名.属性中的属性名”)
4.如果Entity和Dto中均存在其他对应的Entity和Dto对象,可以在接口上使用@Conversion注解将处理之一对Entity和Dto的Conversion注册到该Conversion中来处理该ED
3.3.3 OrderConversion接口
public interface OrderConversion extends LuckyConversion<Order, OrderDto> {
//同名的一级属性不用额外配置,工具会自动转换
@Mappings({
//将Order中Goods对象的name属性映射给OrderDto的goodsName属性
@Mapping(source = "goods.name",target = "goodsName"),
//将Order中Goods对象的price属性映射给OrderDto的price属性
@Mapping(source = "goods.price",target = "price"),
//将Order中Goods对象中的Type对象的name属性映射给OrderDto的typeName属性
@Mapping(source = "goods.type.name",target = "typeName"),
})
@Override
OrderDto toDto(Order entity);
@Mappings({
@Mapping(source = "goodsName",target = "goods.name"),
@Mapping(source = "price",target = "goods.price"),
@Mapping(source = "typeName",target = "goods.type.name"),
})
@Override
Order toEntity(OrderDto dto);
}
3.4 使用EDC工具类获取转换接口的代理对象,并完成转换
//使用EDC工具类的getProxy方法获取转换接口的代理对象
public static void main(String[] args) {
//获取转换接口的代理对象
OrderConversion conversion=EDC.getProxy(OrderConversion.class);
Order order=getOrder();
//执行转换
OrderDto orderDto=conversion.toDto(order);
System.out.println(orderDto);
//OrderDto(id=order-oo1, createTime=Mon Apr 27 12:03:59 CST 2020, buyerName=Jack Fu, totalPrice=13699.0, goodsName=Mac Book Pro, price=13699.0, typeName=电脑)
}
public static Order getOrder(){
Order order=new Order();
Goods goods=new Goods();
Type type=new Type();
type.setId("type-001");
type.setName("电脑");
goods.setType(type);
goods.setId("goods-001");
goods.setName("Mac Book Pro");
goods.setPrice(13699.0);
order.setGoods(goods);
order.setId("order-oo1");
order.setBuyerName("Jack Fu");
order.setTotalPrice(13699.0);
order.setCreateTime(new Date());
}
四.工具的源码解析
4.1 注解
1.@Conversion(在Conversion接口上使用)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conversion {
String id() default "";
//在本Conversion中注入其他的Conversion
Class<? extends LuckyConversion>[] value() default LuckyConversion.class;
}
2@Mappings(在接口方法上使用)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mappings {
//用于使用@Mapping注解定义多个映射规则
Mapping[] value();
}
@Mapping(在接口方法上或者在@Mappings注解中使用)
/**
* 定义一个具体属性的映射规则
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mapping {
//源属性名
String source();
//目标属性名
String target();
}
4.2 LuckyConversion接口
/**
* Entity与Dto相互转化的泛型接口,主要作用是定义规范以及从中获取具体的Entity和Dto的类型
* @param <E> Entity
* @param <D> Dto
*/
public interface LuckyConversion<E,D> {
/**
* Dao转为Entity
* @param dto Dto对象
* @return Entity对象
*/
E toEntity(D dto);
/**
* Entity转为Dao
* @param entity Entity对象
* @return Dao对象
*/
D toDto(E entity);
}
4.3 EDC工具类
重点,前面的接口和注解只是为了定义规范,而EDC类的作用是生产出符合这些规范的代理对象,核心代码都在此处。请小伙伴们耐心的阅读。其中包含了Java反射技术、JDK的动态代理技术和注解!
/**
* 得到一个LuckyConversion接口的实现类JDK实现
* @author fk7075
*
*/
@SuppressWarnings("all")
public class EDC {
/*
一、Conversion接口代理对象的获取
1.传入一个LuckyConversion接口的子接口的Class
2.方法内部会获取到该接口的父接口LuckyConversion中的两个泛型
3.在执行toDto和toEntity方法时会分别做不同的代理
4.以toDto方法的执行过程为例
1.找出该接口上@Conversion中配置的LuckyConversion数组
2.根据这个数组得到一个EntityAndDto集合,EntityAndDto对象中封装的是LuckyConversion的代理对象、Dto泛型和Entity泛型
3.从传入的参数中获取待转换的Entity对象,并将这个对象转化为一个"全属性名"和属性值组成的Map<String,Object>
注:原对象中每个自定义类型都会特别生成一个“全类名”=“对象值”的K-V,每个泛型为自定义类型的集合也会特别生成一个“Collection<全类名>”=“集合值”的K-V
eg:
Map ==>{
name=Jack,(普通属性)
age=24,
type.name=TYPE-NAME,(嵌套在对象中的属性)
com.lucky.Type=com.lucky.Type@a09ee92,(自定义的类型)
Collection<com.lucky.Type>=[com.lucky.Type@a04eg12](泛型为自定义类型的集合)
}
4.检查方法上@Mapping注解或者@Mappings注解中配置的转换映射(source->target),并使用映射Value代替Map中的映射Key,以达到特殊映射的目的
5.通过反射创建一个空的Dto对象,遍历这个对象的所有属性,并使用属性的“全属性名”去Map中拿到该属性的值,
首先会检查这个类型的LuckyConversion是否已被配置在@Conversion注解中,如果存在,则调用该LuckyConversion对象执行对这个entity的转换,
否则创建一个空对象,继续遍历这个属性对象的所有属性
6.如果是泛型为定义类型集合,则去找对应的LuckyConversion,找不到则会抛出异常!
*/
/**
*JDK动态代理的模板代码
*1.实现一个自己的InvocationHandler重写invoke方法,当代理类的每个方法执行时都会会回调这个invoke方法
*2.使用Proxy类的newProxyInstance方法便可以得到代理对象,此方法的第一个参数为需要代理的接口的类加载器(ClassLoader),第二个参数为该接口和其所有父接口的Class组成的Class[],第三个参数是我们自定义的InvocationHandler子类的对象
* 得到一个LuckyConversion接口子接口的代理对象
* @param childInterfaceClass LuckyConversion子接口的Class
* @param <T>
* @return
*/
public static <T extends LuckyConversion> T getProxy(Class<T> childInterfaceClass){
//得到该class的所有泛型接口的Type
Type[] luckyConversionGenericClass=childInterfaceClass.getGenericInterfaces();
ParameterizedType interfaceType=(ParameterizedType) luckyConversionGenericClass[0];
//从父接口中(LuckuConversion)得到当前Entity的Class
Class<?> entityClass =(Class<?>) interfaceType.getActualTypeArguments()[0];
//从父接口中(LuckuConversion)得到当前Dto的Class
Class<?> dtoClass =(Class<?>) interfaceType.getActualTypeArguments()[1];
//检查该接口是否被@Conversion注解标注,如果存在@Conversion注解则拿出其中配置的所有LuckyConversion接口的子接口Class,放到一个Class[]中,后面需要用这个Class[]构建一个List<EntityAndDto>,其作用后文会详细说明。
final Class<? extends LuckyConversion>[] luckyConversionClasses=getLuckyConversionClasses(childInterfaceClass);
Class<?>[] interfaces={childInterfaceClass};
//使用Lambda表达式快速构建一个InvocationHandler接口的子类对象,当代理对象toDto和toEntity方法被调用时都会调用change方法,请先提前记住这个方法,这个方法就是负责Entity与Dto转换的方法!
InvocationHandler myInvocationHandler=(proxy,method,params)->{
String methodName=method.getName();
if("toEntity".equals(methodName)){
return change(method,params[0],luckyConversionClasses,false,entityClass);
}else if("toDto".equals(methodName)){
return change(method,params[0],luckyConversionClasses,true,dtoClass);
}else {
return method.invoke(proxy,params);
}
};
//使用Proxy的newProxyInstance生产出一个接口的代理对象,并返回给getProxy方法方法的调用者
return (T) Proxy.newProxyInstance(childInterfaceClass.getClassLoader(), interfaces, myInvocationHandler);
}
/**
* 检查该接口是否被@Conversion注解标注,如果存在@Conversion注解则拿出其中配置的所有LuckyConversion接口的子接口Class,放到一个Class[]中,后面需要用这个Class[]构建一个
*/
private static Class<? extends LuckyConversion>[] getLuckyConversionClasses(Class<?> childInterfaceClass){
return childInterfaceClass.isAnnotationPresent(Conversion.class)?childInterfaceClass.getAnnotation(Conversion.class).value():null;
}
/*
该方法主要是将@Conversion注解中配置Class[]转化为EntityAndDto集合,EntityAndDao类中封装了一个具体的Conversion,以及该转换器要处理的Entity和Dto的类型(Class)
*/
public static List<EntityAndDto> getEntityAndDtoByConversion(Class<? extends LuckyConversion>[] conversionClasses){
List<EntityAndDto> eds=new ArrayList<>();
if(conversionClasses==null)
return eds;
LuckyConversion luckyConversion;
Type[] conversionGenericTypes;
ParameterizedType interfaceType;
Class<?> dtoClass;
Class<?> entityClass;
for(Class<? extends LuckyConversion> conversionClass:conversionClasses){
//过滤掉@Conversion注解的默认配置LuckyConversion接口的Class
if(conversionClass==LuckyConversion.class)
continue;
//为每个转换器生成代理对象
luckyConversion=EDC.getProxy(conversionClass);
conversionGenericTypes=conversionClass.getGenericInterfaces();
//通过父接口泛型得到Entity和Dto的Class
interfaceType=(ParameterizedType) conversionGenericTypes[0];
entityClass=(Class<?>) interfaceType.getActualTypeArguments()[0];
dtoClass=(Class<?>) interfaceType.getActualTypeArguments()[1];
//构建EntityAndDto对象并放入集合
eds.add(new EntityAndDto(luckyConversion,entityClass,dtoClass));
}
return eds;
}
/**
* 原对象转目标对象
* @param method 调用的方法
* @param sourceObj 原对象
* @param luckyConversionClasses @Conversion注解中的LuckyConversion的Class[]
* @param toDto 当前执行的方法是否为toDto
* @param targetClass 目标对象的CLass
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
* @throws NoSuchMethodException
*/
private static Object change(Method method, Object sourceObj, Class<? extends LuckyConversion>[] luckyConversionClasses,boolean toDto,Class<?> targetClass) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
List<EntityAndDto> eds=getEntityAndDtoByConversion(luckyConversionClasses);
Constructor<?> constructor = targetClass.getConstructor();
constructor.setAccessible(true);
//使用反射技术生成一个空的转化目标类对象
Object targetObj=constructor.newInstance();
//得到源对象中由所有一级属性以及非一级属性的的”全属性名“与”属性值“组成的Map
Map<String,Object> sourceFieldNameValueMap =getSourceNameValueMap(sourceObj,"");
Map<String,String> changeMap=new HashMap<>();
//遍历@Mappings中的所有@Mapping注解,取出所有的映射规则,这个规则的具体体现也是一个Map<source,target>
if(method.isAnnotationPresent(Mapping.class)){
Mapping mapping=method.getAnnotation(Mapping.class);
changeMap.put(mapping.source(),mapping.target());
}else if(method.isAnnotationPresent(Mappings.class)){
Mapping[] mappings=method.getAnnotation(Mappings.class).value();
for(Mapping mapping:mappings)
changeMap.put(mapping.source(),mapping.target());
}
//依照映射规则修改之前生成的”全属性名“和”属性值“组成的Map,如果Map中存在与soucre相同的key,则用该source对应的target替换掉Map中的key
Set<String> keySet=changeMap.keySet();
for(String key:keySet){
if(sourceFieldNameValueMap.containsKey(key)){
Object changeValue=sourceFieldNameValueMap.get(key);
sourceFieldNameValueMap.remove(key);
sourceFieldNameValueMap.put(changeMap.get(key),changeValue);
}
}
//为这个空的转化目标类对象的所有属性赋值
return setTargetObject(targetObj,sourceFieldNameValueMap,eds,toDto,"");
}
/**
* 源对象生成”全属性名“与”属性值“所组成的Map,这里会为所有自定义类型的属性、和泛型为自定义类型的集合特别生成一个”全类名“=”对象值“、”Collection<全类名>“=”集合对象“的特殊Key-Value,这些特殊的K的Value的处理对去使用@Conversion注解中的Conversion。注意:这个方法是一个迭代方法!
* @param sourceObject 原对象
* @param initialName 初始属性名,用于递归时获取带层级的属性名
* @return
* @throws IllegalAccessException
*/
public static Map<String,Object> getSourceNameValueMap(Object sourceObject, String initialName) throws IllegalAccessException {
Map<String,Object> sourceNameValueMap=new HashMap<>();
Class<?> sourceClass=sourceObject.getClass();
Field[] fields=sourceClass.getDeclaredFields();
Object fieldValue;
String fieldName;
for(Field field:fields){
field.setAccessible(true);
fieldValue=field.get(sourceObject);
fieldName="".equals(initialName)?field.getName():initialName+"."+field.getName();
if(FieldUtils.isBasicSimpleType(field)){
sourceNameValueMap.put(fieldName,fieldValue);
}else if(field.getType().getClassLoader()!=null){
sourceNameValueMap.put(field.getType().getName(),fieldValue);
Map<String, Object> fieldNameValueMap = getSourceNameValueMap(fieldValue, fieldName);
for(String key:fieldNameValueMap.keySet()){
sourceNameValueMap.put(key,fieldNameValueMap.get(key));
}
}else if(Collection.class.isAssignableFrom(field.getType())){
Class<?> genericClass=FieldUtils.getGenericType(field)[0];
sourceNameValueMap.put("Collection<"+genericClass.getName()+">",fieldValue);
}
}
return sourceNameValueMap;
}
/**
* 为目标对象设置属性
* @param targetObject 目标对象
* @param sourceMap 原对象的属性名与属性值所组成的Map
* @param eds EntityAndDto对象集合
* @param toDto 当前执行的方法是否为toDto
* @param initialName 初始属性名,用于递归时获取带层级的属性名
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Object setTargetObject(Object targetObject, Map<String, Object> sourceMap,List<EntityAndDto> eds,boolean toDto, String initialName) throws IllegalAccessException, InstantiationException {
Class<?> targetClass = targetObject.getClass();
Field[] targetFields=targetClass.getDeclaredFields();
Class<?> fieldClass;
String fieldName;
for(Field field:targetFields){
fieldClass=field.getType();
field.setAccessible(true);
fieldName="".equals(initialName)?field.getName():initialName+"."+field.getName();
if(FieldUtils.isBasicSimpleType(field)){
if(sourceMap.containsKey(fieldName)){
field.set(targetObject,sourceMap.get(fieldName));
}
}else if(fieldClass.getClassLoader()!=null){
Object fieldValue=field.getType().newInstance();
EntityAndDto ed=toDto? EntityAndDto.getEntityAndDtoByDaoClass(eds,field.getType()):EntityAndDto.getEntityAndDtoByEntityClass(eds,field.getType());
if(ed!=null){
String classKey;
LuckyConversion conversion=ed.getConversion();
if(toDto){//Entity转Dto
classKey=ed.getEntityClass().getName();
if(sourceMap.containsKey(classKey)){
field.set(targetObject,conversion.toDto(sourceMap.get(classKey)));
}else {
field.set(targetObject,setTargetObject(fieldValue,sourceMap,eds,true,fieldName));
}
}else{//Dto转Entity
classKey=ed.getDtoClass().getName();
if(sourceMap.containsKey(classKey)){
field.set(targetObject,conversion.toEntity(sourceMap.get(classKey)));
}else {
field.set(targetObject,setTargetObject(fieldValue,sourceMap,eds,false,fieldName));
}
}
}else{
field.set(targetObject,setTargetObject(fieldValue,sourceMap,eds,toDto,fieldName));
}
}if(Collection.class.isAssignableFrom(field.getType())){
Class<?> genericClass=FieldUtils.getGenericType(field)[0];
EntityAndDto ed;
String classKey;
LuckyConversion conversion;
if(toDto){
ed=EntityAndDto.getEntityAndDtoByDaoClass(eds,genericClass);
if(ed==null)
throw new RuntimeException("在@Conversion注解中找不到"+genericClass+"类相对应的LuckyConversion,无法转换属性("+field.getType()+")"+field.getName());
conversion=ed.getConversion();
classKey="Collection<"+ed.getEntityClass().getName()+">";
if(sourceMap.containsKey(classKey)){
Collection coll=(Collection) sourceMap.get(classKey);
Object collect = coll.stream().map(a -> conversion.toDto(a)).collect(Collectors.toList());
if(List.class.isAssignableFrom(field.getType()))
field.set(targetObject,collect);
else if(Set.class.isAssignableFrom(field.getType()))
field.set(targetObject,new HashSet((List)collect));
}
}else{
ed=EntityAndDto.getEntityAndDtoByEntityClass(eds,genericClass);
conversion=ed.getConversion();
classKey="Collection<"+ed.getDtoClass().getName()+">";
if(sourceMap.containsKey(classKey)){
Collection coll=(Collection) sourceMap.get(classKey);
Object collect = coll.stream().map(a -> conversion.toEntity(a)).collect(Collectors.toList());
if(List.class.isAssignableFrom(field.getType()))
field.set(targetObject,collect);
else if(Set.class.isAssignableFrom(field.getType()))
field.set(targetObject,new HashSet((List)collect));
}
}
}
}
return targetObject;
}
}
4.4 项目中的工具类
EntityAndDto类
public class EntityAndDto {
private LuckyConversion conversion;
private Class<?> entityClass;
private Class<?> dtoClass;
public EntityAndDto(LuckyConversion conversion, Class<?> entityClass, Class<?> dtoClass) {
this.conversion = conversion;
this.entityClass = entityClass;
this.dtoClass = dtoClass;
}
public LuckyConversion getConversion() {
return conversion;
}
public void setConversion(LuckyConversion conversion) {
this.conversion = conversion;
}
public Class<?> getEntityClass() {
return entityClass;
}
public void setEntityClass(Class<?> entityClass) {
this.entityClass = entityClass;
}
public Class<?> getDtoClass() {
return dtoClass;
}
public void setDtoClass(Class<?> dtoClass) {
this.dtoClass = dtoClass;
}
public static EntityAndDto getEntityAndDtoByDaoClass(List<EntityAndDto> eds, Class<?> dtoClass){
for(EntityAndDto ed:eds){
if(dtoClass==ed.getDtoClass())
return ed;
}
return null;
}
public static EntityAndDto getEntityAndDtoByEntityClass(List<EntityAndDto> eds,Class<?> entityClass){
for(EntityAndDto ed:eds){
if(ed.getEntityClass()==entityClass)
return ed;
}
return null;
}
}
FieldUtils类
public class FieldUtils {
public static boolean isArray(Field field){
return field.getType().isArray();
}
public static boolean isCollection(Field field){
return Collection.class.isAssignableFrom(field.getType());
}
public static boolean isMap(Field field){
return Map.class.isAssignableFrom(field.getType());
}
public static Class<?>[] getGenericType(Field field){
Type type = field.getGenericType();
if(type!=null && type instanceof ParameterizedType){
ParameterizedType pt=(ParameterizedType) type;
Type[] types=pt.getActualTypeArguments();
Class<?>[] genericType=new Class<?>[types.length];
for(int i=0;i<types.length;i++)
genericType[i]=(Class<?>)types[i];
return genericType;
}else{
return null;
}
}
public static boolean isBasicCollection(Field field){
Class<?>[] genericClasses=getGenericType(field);
if(genericClasses==null||genericClasses.length!=1)
return false;
Class<?> generic=genericClasses[0];
return generic.getClassLoader()==null;
}
public static boolean isBasicSimpleType(Field field){
Class<?> fieldClass=field.getType();
if(fieldClass.getClassLoader()!=null)
return false;
Class<?>[] genericTypes = getGenericType(field);
if(genericTypes==null)
return true;
for (Class<?> clzz:genericTypes){
if(clzz.getClassLoader()!=null)
return false;
}
return true;
}
}
五.总结
虽然不提倡重复造轮子,但我们一定要有造轮子的能力,这个小工具只是大船上的一个小小零件,想了解这艘大船的小伙伴请移步 github-lucky 这个是本人基于对现今流行框架的理解下自己手写的一个小型的全栈型框架,框架名字叫Lucky!欢迎各位小伙伴们以及各位大佬们的意见与建议,希望可以和大家一起学习一起进步!!!!