XToolBean 使用教程

源码-github

前言

已存在的值拷贝工具中,mapstruct流行很实用流行很广 ,但是源码非常不好改,无法扩展,只能等待更新,因此在它所有拥有的功能基础上,实现大部分功能的同时扩展思路和常用方法,更加快捷

XToolBean是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,XToolBean将生成此接口相关Bean的Class文件。
与动态映射框架如Spring的beanUtil或是json序列化生成对象相比,具有更快的速度,以下优点:

  1. 通过使用普通方法调用(settter/getter)而不是反射来快速执行
  2. 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
  3. 如果有如下问题,编译时会抛出异常
    1. 类字段(包括父类)必须实现Getter/Setter方法(结合lombok @Data即可)
    2. 类深度拷贝必须实现序列化(Serializable)
    3. 异常处理:generated-sources 会生成java文件,由此查看编译失败的原因
      在这里插入图片描述

相较于mapstruct优点:

1 只有一个依赖
2 只有一个工具 一行代码
3 有timezone时区
4 json字符串和对象映射
5 代码功能容易扩展
6 …其它的功能详见下文

缺点:不能关联第三个类的拷贝,不能通过子字段对象的子字段赋值,还有一些不常用的功能未开发
性能比较(预编译相同,性能类似于mapstruct)

工具10次1000次1W次10W次
mapstruct2ms5ms7ms31ms
hutools的BeanUtil.copyProperties61ms809ms6802ms
hutools的BeanUtil.copyToList()125082ms
spring的BeanUtils)5ms42ms235ms1934ms
apache的BeanUtils39ms209ms1602ms16174ms

1. 设置

1.1 Maven

对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:

<!--xtool-bean依赖 高性能对象映射-->
            <dependency>
                <groupId>io.github.myswordsky</groupId>
                <artifactId>xtool-bean</artifactId>
                <version>0.0.1</version>
            </dependency>

Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用

		<dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>1.18.12</version>
        </dependency>

1.2 预编译自动映射器

除了基础包装类型转换,还可以实现以下转换

类型(包含包装类型)boolean/BooleanbyteshortintlongfloatdoubleStringDateBigDecimal对象
boolean/Boolean√ 0/1√ false/true
byte/Byte
short/Short
int/Integer√ true/false√ 时间戳秒
long/Long√ 时间戳毫秒
float/Float
double/Double
String√ 需要开启jsonMapping
Date√ 时间戳秒√ 时间戳毫秒√ 默认yyyy-MM-dd HH:mm:ss
BigDecimal
对象√ 需要开启jsonMapping

2. 定义一个预编译映射器

2.1 基本映射 @XToolMapping字段注解

要创建映射器,只需使用所需的映射方法定义一个Java接口,并用注释对其进行org.mapstruct.Mapper注释:
该@Mapper注释将使得MapStruct代码生成器创建的执行PersonMapper 过程中生成时的界面。

在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。

  2. 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

如果不指定@XToolMapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。
忽略字段加@XToolMapping#ignore() = true

@Data
public class Person implements Serializable{
    String describe;
    private String id;
    private String name;
    private int age;
    private BigDecimal source;
    private double height;
    private Date createTime;
}

@Data
public class PersonDTO implements Serializable{
    String describe;
    private Long id;
    private String personName;
    private String age;
    private String source;
    private String height;
    private String createTime;
}

@XToolBean()//必须注解
public interface PersonMapper {

	@XToolMapping(target = "personName", source = "name") //默认相互映射, 如果不存在则不映射
	@XToolMapping(target = "id", ignore = true) // 忽略id,不进行映射
    void copy(StringGen first, StringGenDTO second);
    
    //不需要任何@XToolMapping更改属性,可直接使用默认配置转化
    //  void copy(StringGen first, StringGenDTO second);
}

生成的实现类:

public class PersonAndPersonDTO
    implements BaseMapping<Person,PersonDTO> {
  public Person toEntity(PersonDTO source) {
    	if (source == null) {
            return null;
        }
        Person target = new Person();
       	target.setName(source.getPersonName());
       	target.setDescribe(source.getDescribe());
       	
       	if (!Objects.equals(source.getAge(), null)) {
       	    target.setAge(Integer.parseInt(source.getAge()));
       	}
       	if (!Objects.equals(source.getHeight(), null)) {
       	    target.setHeight(Double.parseDouble(source.getHeight()));
       	}
        return target;
  }
  public PersonDTO toDto(Person source) {
    	if (source == null) {
            return null;
        }
        PersonDTO target = new PersonDTO();
       	target.setDescribe(source.getDescribe());
       	if (Objects.equals(source.getSource(), null)) {
       	    target.setSource(null);
       	} else {
       	    target.setSource(String.valueOf(source.getSource()));
       	}
       	target.setAge(String.valueOf(source.getAge()));
       
       	target.setHeight(String.valueOf(source.getHeight()));
        
        return target;
  }
  public Person thisEntity(Person source) {
    	if (source == null) {
            return null;
        }
        Person target = new Person();
       	if (Objects.equals(source.getCreateTime(), null)) {
       	    target.setCreateTime(null);
       	} else {
       	    target.setCreateTime(new Date(source.getCreateTime().getTime()));
       	}
       	target.setDescribe(source.getDescribe());
       	if (Objects.equals(source.getSource(), null)) {
       	    target.setSource(null);
       	} else {
       	    target.setSource(BigDecimal.valueOf(source.getSource().doubleValue()));
       	}
       	target.setAge(source.getAge());
       	target.setHeight(source.getHeight());
        return target;
  }
  public PersonDTO thisDto(PersonDTO source) {
    	if (source == null) {
            return null;
        }
        PersonDTO target = new PersonDTO();
       	target.setPersonName(source.getPersonName());
       	target.setDescribe(source.getDescribe());
       	target.setSource(source.getSource());
       	target.setAge(source.getAge());
       	target.setHeight(source.getHeight());
        return target;
  }
}

测试:

@Test
public void test(){
     Person person = new Person();
     person.setDescribe("测试");
     person.setAge(18);
     person.setName("张三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = BeanMapping.copy(person, PersonDTO.class);
     System.out.println(dto);
    // PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5)
     Person entity = BeanMapping.copy(dto, Person.class);
     System.out.println(entity);
    // Person(describe=测试, id=null, name=null, age=18, source=100, height=170.5)
}

2.2 拷贝指向 index()

@XToolMapping(target = "personName", source = "name", index = MappingIndexEnums.First)//不加参数默认是相互映射
void copy(PersonDTO first, Person second)
MappingIndexEnums.First ~=   PersonDTO copy(Person second)
MappingIndexEnums.Second ~=  Person copy(PersonDTO first)
默认Default  即双向映射类似于mapstruct的映射和@InheritInverseConfiguration结合的双向映射

MappingIndexEnums.First指向第一个参数,则target对应的目标是PersonDTO,相当于 PersonDTO copy(Person second)
同理MappingIndexEnums.Second对应的target对应的目标是Person

2.3 指定值(targetValue()直接忽略来源值)、默认值(defaultValue():来源值为空才处理)(自定义java代码、使用表达式)

target() 必须添加,source()可以不添加,则直接使用defaultValue,defaultValue()实际是动态的java代码
设置目标字段默认值(支持表达式) 字段不同类型需要指定index(如果同类型可以无需指定)

设置目标字段默认值(支持表达式) 字段不同类型需要指定index(如果同类型可以无需指定)
例:defaultValue = "1"
支持{@link FieldType}的基础类型和包装类型
其它类型(或新对象) 需要XToolMapping.index()配置
1 数字:defaultValue = "25"
2 字符串:defaultValue = "\"25\""
3 使用拷贝来源变量值(固定为source.getXX)
     defaultValue = "source.getName().toString"
4 对象需要携带全名(可以结合@XToolBean#imports()来导入新的类,这样就不用加全名了):
defaultValue = "new java.util.Date()"
defaultValue = "new java.math.BigDecimal(1.23)"
defaultValue = "new java.util.ArrayList<io.github.xtools.bean.entity.config.ClassInfo>(){{
            add(new io.github.xtools.bean.entity.config.ClassInfo());
        }};"
defaultValue = "new new io.github.xtools.bean.entity.config.User(\"name\", 25)"
5 调用接口方法
	...
	@XToolMapping(target = "describe", defaultValue = "\"默认值\"")//字符串类型需要加\"
	@XToolMapping(target = "age", defaultValue = "25")
	//复制非基础类型需要完整包名(或者提前导入类 @see 下文XToolBean的imports())
	//这里必须要指定MappingIndexEnums.First,因为PersonDTO和Person的createTime是不同类型
	@XToolMapping(target = "createTime", defaultValue = "null", index = MappingIndexEnums.First)
	@XToolMapping(target = "createTime", defaultValue = "new java.util.Date()", index = MappingIndexEnums.Second)
	void copy(PersonDTO first, Person second);

生成的impl:这里功能不全

...
if (person.getDescribe() != null) {
    personDTO.setDescribe(person.getDescribe());
 } else {
     personDTO.setDescribe("默认值");
 }
 ...

测试:

@Test
public void test(){
     Person person = new Person();
     //person.setDescribe("测试");
     person.setAge(18);
     person.setName("张三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = BeanMapping.copy(person, PersonDTO.class);

     System.out.println(dto);
	// PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5, createTime=null)

 }

2.4 日期处理 dateFormat() 和 timeZone()

如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。
mapstruct是没有timeZone时区的,这里

....
//如果不加dateFormat ,则默认映射数据类型为yyyy-MM-dd HH:mm:ss 
@XToolMapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
void conver2(Person person, PersonDTO dto);
@XToolMapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd", timeZone = "GMT+8")
void conver2(Person person, PersonDTO dto);

impl:

try {
	if (person.getCreateTime() != null) {
    	personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
	}
} catch (ParseException var4) {
    throw new RuntimeException(var4);
}

try {
	if (person.getCreateTime() != null) {
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd")
		sf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
    	personDTO.setCreateTime(sf.parse(person.getCreateTime()));
	}
} catch (ParseException var4) {
    throw new RuntimeException(var4);
}

2.5 组合映射 当前版本暂不支持该功能

2.6 嵌套映射 暂不支持

2.7 字符串格式化 numberFormat()

目前支持格式:Double->String Float->String BigDecimal->String

如果带注释的方法从数字映射到字符串,则使用DecimalFormat将格式字符串作为可处理的格式。反之亦然。对于所有其他元素类型,将被忽略。
从基本2.1 基本映射可以看出,number类型与字符串直接的转换是通过valueOf(),如果字符串格式不正确会抛出java.lang.NumberFormatException异常,例如:Integer.valueOf(“10.2”)

使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE异常

// mapper
....
	@XToolMapping(target = "age",source = "age", numberFormat = "#0.00")
	void conver2(Person person, PersonDTO dto);
...
// imppl
personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));

2.8 逆映射 自带 (实体到DTO以及从DTO到实体)

2.9.1 忽略映射 igonre()

@XToolMapping#igonre() = true 不对目标类的target指定字段进行复制

	@XToolMapping(target = "age", ignore = true)
	void conver(Person person, PersonDTO dto);

2.9.2 不同类型是否自动映射 copyType() 和@MappingCopyType

映射详细参考1.2表格
字段级 @XToolMapping#copyType()
方法级:@MappingCopyType

	@XToolMapping(target = "age", source = "age", copyType= false)//该方法字段类型如果不对则不复制
	void conver(Person person, PersonDTO dto);
	@MappingCopyType(false)//该方法字段类型如果不对则不复制
	void conver(Person person, PersonDTO dto);

2.9.3 json转对象或对象转json(需要fastjson的支持) jsonMapping()

Person class{
	String nameJson;
}
PersonDTO class{
	User  nameJson;
}
    @XToolMapping(source = "nameJson", target = "nameJson", jsonMapping = true)
	void conver(Person person, PersonDTO dto);

2.9.4 字段方法执行器 resultType() 和resultTypeIsInner()

字段方法执行器 (使用此功能defaultValue失效、且需要配合resultTypeIsInner使用) 必须指定index First or Second且类型一致
user方法(目标字段名) 执行映射时, 可以将返回值转换为 User 类型。

		//source不为空则为默认字段 找不到则不处理
        @XToolMapping(source = "name", target = "name", index = MappingIndexEnums.First, resultType = Name.class)
        void copy(Source source, Target target);
    
//public外部类  非public内部类需要加上resultTypeIsInner = false
package entity;
public class Name {
	//User:返回字段类型  user:返回字段名称(String:来源字段类型 user:随意变量值(取决于source))
    public User user(String user) {  
        String[] parts = user.split(",");
        return new User(parts[0], parts[1]);
    }
    public String name(String name) {
        return "new User(parts[0], parts[1])";
    }
}
package entity;
@AllArgsConstructor
@Data
public class User{
	String s1;
	String s2;
}

impl

	Source class{
		String user;
	}
	Target class{
		User user;
	}
target.setUser((entity.User)ClassUtils.executeMethod("entity.Name", targetFieldName, source.getUser()));

3.@XToolBean类注解

3.1 isSpring()是否是Spring类

会在生成类上加Component 注解

@XToolBean(isSpring = true)
public class DateFormtUtil {
    void copy(Source source, Target target);
}
可以结合Spring List注入进行统一管理

3.2 使用 copyType()

同2.92 但此注解作用在类上 copyType =false则该类所有方法不执行类型转换

@XToolBean(copyType = false)
public class DateFormtUtil {
    void copy(Person source, PersonDTO target);
}

3.3 生成类额外导入的类 imports()

使用 生成类额外导入的类(添加import),可结合defaultValue或targetValue执行
import entity.User;
public class PersonAndPersonDTO implements BaseMapping<Person,PersonDTO> {

@XToolBean(imports= {User.class})
public class DateFormtUtil {
	@XToolMapping(target = "user", defaultValue = "new User()", index = MappingIndexEnums.First)
    void copy(Person source, PersonDTO target);
}

3.3 生成类的继承 extendsClazz()

import entity.User;
public class PersonAndPersonDTO extend User implements BaseMapping<Person,PersonDTO> {

@XToolBean(extendsClazz= User.class)
public class DateFormtUtil {
    void copy(Source source, Target target);
}
可以统一管理

3.4 克隆模式 mappingControl() 和方法级@MappingDeepClone

@XToolBean#mappingControl 将作用于该类下所有的方法
仅作用于某个方法 详见{@link MappingDeepClone}
DeepClone 深度克隆(需要实现Serializable接口)
默认:基础类型(还有String)不影响,对象类型浅克隆

@XToolBean(mappingControl = MappingDeepClone.class)
public class DateFormtUtil {
    void copy(Source source, Target target);
    @MappingDeepClone
    void copy2(Source source, Target target);
}

4 Guava自动映射器(不建议使用)

1 运行时动态拷贝(完全和预编译独立), 需要开启是否使用Guava,因为使用代码直接加载,一个显著的缺点是:每个第一次拷贝的耗时500毫秒左右,因此可使用线程池在项目启动时异步加载

@Component
public class StartRunning implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        String init = BeanMapping.initAuto(true);
        //如果运行时速度很慢,可异步加载
        List<Consumer<?>> list = new ArrayList<Consumer<?>>() {{
            add((e) -> BeanMapping.copy(new Person(), PersonDTO.class));
            //...
        }};
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
        list.forEach(e -> executorService.execute(() -> e.accept(null)));
    }
}

源码-github
优化Spring.BeanUtil文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值