# EasyCopy2.0 **Repository Path**: xizil/easy-copy2.0 ## Basic Information - **Project Name**: EasyCopy2.0 - **Description**: 类似框架MapStruct 属性拷贝,属性映射,对象拷贝,对象复制 运行原理: 编译时期,生成字节码. 性能优势: 放弃1.0中动态代理和反射,全部都是原生Java代码.运行时期没有任何性能损失 优点: 1.功能齐全 2.并发安全 3.低耦合设计,易维护. - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: https://gitee.com/li_ziyun/easy-copy2.0 - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2022-07-07 - **Last Updated**: 2022-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyCopy2.0 ## 为什么使用EasyCopy2.0 ``` 项目中存在大量代码: 两个对象上的属性,大量相互复制 stu1.setName( stu2.getName() ); stu1.setAge( stu2.getAge() ); .... 存在问题: 这些代码跟业务没有太大关系,却经常重复写这些不太重要的代码. 我们应该尝试这部分代码抽离出业务中! ``` ## 学习本项目价值 ``` 底层工作原理与工作中常用的MapStruct相同,可以达到MapStruct一样效果. 1. 有助于研究JSR269插件化注解开发 2. 项目中拥有大量操作编译时期字节码的案例 3. 本项目对比MapStruct的控制能力更强,不会出现MapStruct封装过于严重.产生意想不到的底层错误. ``` # 测试案例 ``` https://gitee.com/li_ziyun/easy-copy2.0-test/ ``` #### 介绍 ``` 运行原理: 编译时期,生成字节码. 性能优势: 放弃1.0中动态代理和反射,全部都是原生Java代码.运行时期没有任何性能损失 优点: 1.功能齐全 2.并发安全 3.低耦合设计,易维护 ``` ### 强大功能 1. 这里是列表文本使用方式简单,编写接口即可。 2. 支持多级拷贝 例如: Student.name 映射 Teacher.School.name 3. 支持深克隆 例如:创建一个自定义类型转换器 4. 支持运算符操作 例如: stu.setAge( (tea.getAge()+3)*2 + '小豆' ) 5. 支持强制类型转换 例如: int a = (int)1.1; Person p = (Person)student; 6. 默认高精度运算 例如: a//b 相当于 a和b使用BigDecimal运算 7. 支持精度控制 例如: BigDecimal前提下,scale设置保留小数点位数,round舍入模式(默认四舍五入) 8. 支持if运算 例如: age >= 18 && age <= 30 ? 1 : 2 9. 支持第三方工具类(类型转换) 例如: cast="Integer.valueOf($)" cast=" DateUtil.parse($,'yyyy-MM-dd') " ### 使用案例 - 使用方式: 1. 在接口或类上,添加注解@EasyCopy 2.在方法上添加@ParamMaps(可以是任何方法,抽象方法,接口方法 等等) - 注意: 必须符合JavaBean规范,提供GetSet方法 ``` @EasyCopy public interface StudentCopy { @ParamMaps(value = { //scale含义: 保存0位小数 round含义: 并且向上取整 //注意: BigDecimal必须指定cast类型转换,因为运算结果是String字符串 //注意: 一个运算符表示普通运算,两个运算符表示BigDecimal. + 和 ++ //注意: 普通运算 和 BigDecimal运算,在同一个表达式中不能同时存在 @ParamMap(targetProperty = "stu1.height", resourceProperty = "(num//stu2.age)++22**stu2.age",cast = "Double.valueOf($)",scale = 0,round = BigDecimal.ROUND_HALF_UP), //深度拷贝School: 自定义类型转换器 @ParamMap(targetProperty = "stu1.school", resourceProperty = "stu2.school",TYPE_TRANSFORM = TypeTransformImpl.class), //属性拷贝:表达式支持形参列表,支持字符串,支持数字 @ParamMap(targetProperty = "stu1.school.name", resourceProperty = "proviceName+stu2.school.name+'嘿嘿嘿'"), //由于height是double类型,需要强制转化成int //注意: 支持参数列表上任意参数,参与运算 @ParamMap(targetProperty = "stu1.age", resourceProperty = "stu2.age*(stu1.age+stu2.height)",cast = "int") } ) public Student fun(Student stu1, Student stu2,double num,String proviceName) ; //注意事项: 方法的返回值,默认返回参数列表中的第一个 public static void main(String[] args) { //数据准备 School school = new School("襄阳三中", "襄阳市樊城区大庆路"); Student student = new Student("小豆",172.0, 21, 65, "襄阳市樊城区",school); //运算 StudentCopy studentCopy = Easy.bean(StudentCopy.class); Student stu = studentCopy.fun(new Student(),student,210,"湖北省"); //输出 System.out.println(stu); } } ``` - 创建自定义转换器: 实现深拷贝 ``` public class TypeTransformImpl implements TypeTransform { /** * 深度拷贝: School属性 * @param s * @return */ @Override public School transform(School s) { School school = new School(); school.setLocation( s.getLocation() ); school.setName( s.getName() ); return school; } } ``` - 反编译生成的字节码: 权限修饰符:public static(不是图片中default) ![权限修饰符:public static(不是图片中default)](https://images.gitee.com/uploads/images/2021/0409/233140_41074e44_5240108.png "屏幕截图.png") ## 使用IF判断 - 简单IF: ``` @ParamMaps(value = { @ParamMap(targetProperty = "stu1.age", resourceProperty = "'2222'.equals(stu1.name) || '211'.equals('333') ? stu2.age++stu2.age++1 : stu2.age*(stu2.age+stu2.age)+1 "), @ParamMap(targetProperty = "stu1.dsc", resourceProperty = " stu2.age > 18 && stu2.age <= 30 ? '青年' : '非青年' "), } ) public Student fun(Student stu1, Student stu2,double num,String proviceName,String dateFormat) ; ``` - 复杂IF > 注意: $是特殊符号,表示resourceProperty中的内容 ``` @EasyCopy() public interface StudentCopy { @ParamMaps(value = { //@ParamMap(targetProperty = "stu1.age", resourceProperty = "stu2.age",cast = "StudentCopy.doAge($,1,stu2.height)") } ) public Student fun(Student stu1, Student stu2,double num,String proviceName,String dateFormat) ; //注意事项: 方法的返回值,默认返回参数列表中的第一个 public static int doAge(int age,int a,double height){ if (age > 40 && height > 1.75){ return 3; }else if (age > 18 && height > 1.7 ){ return 2; }else { return 1; } } } ``` ## 兼容任意第三方工具类: HuTool - 依赖 ``` cn.hutool hutool-all 5.6.3 ``` - 使用: @EasyCopy中导入第三方的包 - 注意: $是特殊符号,表示resourceProperty中的内容 ``` @EasyCopy( packageNames={"cn.hutool.core.date.DateUtil"} ) public interface MyTest { @ParamMaps(value = { @ParamMap(targetProperty = "stu1.date", resourceProperty = "stu2.dateStr",cast = "DateUtil.parse($,'yyyy-MM-dd')"), @ParamMap(targetProperty = "stu1.date", resourceProperty = "stu2.dateStr",cast = "DateUtil.parse($,dateFormat)"), } ) public Student fun(Student stu1, Student stu2,double num,String proviceName,String dateFormat) ; } ``` #### 安装教程 1. 新建一个项目添加maven依赖 ``` work.liziyun EasyCopy_V2.0 3.9.1 maven2 https://repo.maven.apache.org/maven2 true true always fail ``` ### 错误使用方式 1. targetPropertyName = "stu1.age+stu2.age++stu1.age" > 错误原因: 普通运算 和 BigDecimal不能同时存在 2. targetPropertyName = "stu1.age>1?1:stu1.age>2?1:2" > 错误原因: 三元运算不支持嵌套.复杂的判断,请使用外部方法. 3. targetPropertyName = "!(stu1.age>0)?1:2" > 错误原因: 不支持!,支持&&和|| #### 软件架构 1. Scan扫描组件: 找到需要处理的类,保存到成员属性中 2. ParseMeta解析元数据组件: 将类中注解信息,解析到pojo中 3. express表达式组件,注解里面都是没有意义字符串。 ``` a.拆分出各种节点类型,字符类型,小数类型,整数类型,字段类型 b.中缀表达式转后缀表达式,这个跟计算机底层工作原理有关,是一个算法,为了更方便解析表达式 ``` 4. code生成字节码组件。 ``` 最高层次的父类,是最底层最通用的操作字节码方法。 下面的继承层次,每一个层次处理一部分功能,复用上面最高层次的操作字节码。 充分发挥继承的特点Is-A的关系,继承体系越下,功能越强大。 ``` 5. 总体设计: ``` Application: 上下文环境,管理所有组件 Process接口: 启动接口,组件启动必须执行的操作. 组件设计原则: 每个组件相互独立,耦合性低.可以独立运行. 组件启动后,将处理结果保存到成员属性容器中. 组件间调用: 每个组件都是单例 A组件调用B组件的功能: 不直接调用,必须通过Application上下文环境获取相应组件.再调用 A组件调用B组件的成员属性: 直接调用 为什么这么设计? A组件直接调用B组件,代码显得混乱.无法无法一眼分辨出,这是两个组件在相互调用的代码. 组件内部设计: 1.算法组件: 寻找抽象逻辑,面向抽象.提高代码复用率 2.功能复杂组件: 抽象类继承设计.最顶层的父类,封装最原始最通用的功能 子类不断扩展父类的功能,将复杂的逻辑拆分到不同的继承体系上. 采用模板方法的设计模式,拆分复杂的if流程.具体到子类中的方法上,更易维护. ```