Skip to content

Latest commit

 

History

History
399 lines (287 loc) · 13.1 KB

readme.md

File metadata and controls

399 lines (287 loc) · 13.1 KB

1. 简介

lamia 是一个高性能的对象转换框架, 使用简单的表达式就能帮助你在编译期生成对应的转换代码

1.1 优势

  • 使用简单,没有繁琐的配置和接口定义,开箱即用
  • 高性能的对象转换,转换性能和手写get/set一致
  • 完善的兼容性,支持jdk8-jdk21等常用JDK版本以及可以和lombokAnnotationProcessor框架同时使用
  • 在Idea插件的加持下,支持增量编译、可视化转换等

2. 快速使用

1.引入maven坐标

引入的包分为编译期运行期的包,编译期的包仅仅在编译时生成对应的转换代码

<dependencies>
  <dependency>
    <groupId>io.github.cao2068959</groupId>
    <artifactId>lamia-compile</artifactId>
    <version>2.2.2</version>
    <scope>compile</scope>
  </dependency>

  <dependency>
    <groupId>io.github.cao2068959</groupId>
    <artifactId>lamia-runtime</artifactId>
    <version>2.2.2</version>
  </dependency>
</dependencies>

如果你的项目中指定了编译器的annotationProcessorPaths, 需要把lamia-compile 加入其中,如下所示

<build>
        <pluginManagement>
            <plugins>
                ...
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                        <annotationProcessorPaths>
                            ...你其他 AnnotationProcessor 的包,比如lombok
                            <path>
                              <groupId>io.github.cao2068959</groupId>
                              <artifactId>lamia-compile</artifactId>
                              <version>${lamia.version}</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

2.写一个转换方法并标注上注解@@LamiaMapping

3.在转换方法里面调用生成赋值语句的静态方法

T varName = (强转成你要返回的类型 比如 T)Lamia.mapping(转换的参数);

如果你不喜欢使用强转(可能某些代码规范检查的时候会提示,不安全的类型),这边也提供了其他的方式,如下所示:

T varName = Lamia.builder().mapping(转换的参数).build(T.class)

对应的最简demo如下所示

public class Test {

  public static void main(String[] args) {
    User user = User.builder().old(1).name("testLamia").address("test").build();
    UserVO userVO = test(user);
    System.out.println(userVO);
  } 
    
  @LamiaMapping
  public static UserVO test(User user){
    System.out.println("-----> start");
    UserVO userVO = (UserVO) Lamia.mapping(user);

    System.out.println("-----> builder type");
    return  Lamia.builder().mapping(user).build(UserVO.class);
  }
}

@Data
@Builder
public class User {
  String name;
  Integer old;
  String address;
}

@Data
public class UserVO {
  String name;
  Integer old;
  String address;
}

对应编译之后的代码如下:

a

这里需要注意的是:

  • 生成的实体必须要有对应的构造方法/setter方法, 如果某个字段在 构造方法/setter方法里都不存在,那么这个字段将不会被设置进去
    • 如果有多个构造方法,那么将会选择和被转换对象中最匹配的
  • 被转换对象中需要有对应的getter方法

以上就是lamia 最简单的用法,也是最常用的用法,你可以像使用 beanUtils.copy 一样来快速在业务中转换两个对象,同时又享受到极致的性能

3. 详细配置

在介绍详细配置的时候,会展示一些表达式最终生成的语句来让大家更加理解对应配置的作用,在文中截图展示最终生成语句的工具是lamia Idea插件 欢迎大家去idea插件商店下载体验

3.1 注解 @LamiaMapping

  • 对于标注了注解@LamiaMapping的方法, 那么在编译期中将会去扫描这个方法中的每一行代码, 直到扫描到语句 Lamia转换语句, 然后根据Lamia转换语句中配置的参数来生成对应的转换代码, 然后使用生成的代码替换掉语句 Lamia转换语句
  • 一个方法里面可以同时存在多个 Lamia.convert 语句

注意: 这里生成对应的转换代码后,不是整个方法去替换的,而是仅仅只是替换Lamia转换语句 语句

3.2 转换函数

lamia中提供了两种最基础的转换函数来进行对象的转换,分别是

  • mapping
  • setField

3.2.1 mapping 函数

作用: 将mapping函数中的入参的对象映射到目标对象上面,这里是根据字段的名称去映射的,如果名称相同,但是类型不同 最终编译的时候会报类型不匹配异常,可以使用函数ignoreField来忽略掉对应的字段,具体可以看 ignoreField 相关章节

mapping函数使用的是可变参数,可以同时放入多个参数,多个参数如果可以映射同一个字段 那么放在后面的参数优先级更高

    @LamiaMapping
    public static UserVO test2(User user) {
        NameWrapper nameWrapper = new NameWrapper("testName");
        UserVO userVO = (UserVO) Lamia.mapping(user, nameWrapper);
        return userVO;
    }

    @Data
    @AllArgsConstructor
    public class NameWrapper {
      String name;
    }
    
    // UserVo 和 User 对象结构一致
    @Data
    public class UserVO {
      String name;
      Integer old;
      String address;
    }

上述代码中,NameWrapperUser 对象中都同时拥有name 字段,但是 nameWrapper 放在了后面,优先级更高,所以最终生成代码为:

img.png

3.2.1 setField 函数

作用: setField函数将会根据入参中变量的名称来直接映射到目标对象中,类似直接写 set 函数,如:

    @LamiaMapping
    public void  test4() {
        User user = new User();
        // 等同于 直接 UserWrapper.setUser(user)
        UserWrapper result = (UserWrapper)Lamia.setField(user);
    }

    @Data
    public class UserWrapper {
      User user;
    }

上述代码生成的代码如下: img_3.png

注意: 这里是直接使用入参的变量名称和结果对象中的字段做映射的,所以setField 中的入参只能放入已经定义好的变量 不能接收一个表达式

  • 其实如果有一些额外参数更建议用户手动去set,而不是使用 setField 方法
  • 你问我这个功能的意义? 我可以不用但是不能没有!!!

3.3 设置目标对象

这里目标对象代表的是最终需要生成的对象,lamia提供了三种方式来指定最终生成的对象是什么,分别是

  1. 强转指定类型
  2. T.class 的方式指定
  3. 直接设置目标对象
  • 前两种方式仅仅只是指定了类型,所以lamia将会根据指定类型来生成对应的new语句
  • 后两种方式只会在builder 模式下提供

3.3.1 强转指定类型

具体用法如下:

User user = ...;
// 使用强转的方式来指定 最终生成的对象的类型
UserVO userVO = (UserVO) Lamia.mapping(user);
// builder 模式下也可以使用强转来指定类型
userVO = (UserVO)Lamia.builder().mapping(user).build();

使用强转的优势在于可以可以去指定泛型,如:

Map<String, Object> map = (Map<String, Object>) Lamia.mapping(user);

3.3.2 T.class 的方式指定

builder 模式下,可以去指定最终要生成的类型,如:

UserVO userVO = Lamia.builder().mapping(user).build(UserVO.class);

3.3.3 设置目标对象

如果你已经有一个半成品的对象了,不希望lamia给你重新new 一个,那么可以使用此种方式

同样需要在 builder 模式下

User user = ...;
UserVO result = ...;
// 会直接用result来进行转换,不需要接收新对象了
Lamia.builder().mapping(user).build(result);

3.4 builder 模式

除了提供最基础的转换之外,还提供了 builder模式来丰富配置

在builder模式之下可以 混合使用setField / mapping 函数如:

        User user = new User();
        String name = "new_name";
        Integer old = 100;
        // 表达式混合转换
        UserVO userVO = Lamia.builder().mapping(user)
        .setField(name).setField(old)
        .build(UserVO.class);

上述表达式中,手动使用setField 来覆盖了字段name/old, 只有address 字段使用了user中的字段进行了映射,所以最终生成的代码如下

img_4.png

3.5 Rule

需要开启builder 模式 之后使用

除了正常的转换,lamia还内置了一些额外的规则,这些内置的小规则可能在某些场景之下能大大帮助你提高效率

rule 配置的规则如下所示:

// 开启builder模式
Lamia.builder()
    // 开启一个规则, 这里面的规则只对 入参 bo1 生效
    .rule({使用内置规则(可选)}).{设置规则方法(可选)}.mapping(bo1)
    // 开启一个新的规则, 这里面的规则只对 入参 bo2 生效
    .rule({使用内置规则(可选)}).{设置规则方法(可选)}.mapping(bo2)
    // 设置最终要生成的类型
    .build(VO.class);

从上可以看出,有两个地方可以去设置规则,分别是

  • rule() 方法的入参可以设置规则,这一类规则一般都是内置好的固定规则,比如在转换的时候忽略所有null值,会对所有字段生效
  • 在rule和mapping 中间的方法可以设置一些定义好的规则,这类规则可以去设置一些指定参数,可能会有不同的效果

接下来会介绍一下当前已经内置的规则,更多的规则会持续添加,也欢迎各位大神一起构建

3.5.1 转换的时候忽略null值

使用内置规则RuleType.IGNORE_NULL 可以来帮助你生成

        UserVO userVO = Lamia.builder().rule(RuleType.IGNORE_NULL)
                .mapping(user)
                .build(UserVO.class);

生成的代码如下: img_5.png

3.5.2 转换的时候忽略指定字段

上面提到过,lamia是完全根据字段的名称进行映射的,所以当两个类字段名称一致,但是类型不一致的时候会导致编译报错 这时候就需要去忽略掉这些类型不匹配的字段

  • 为什么不自动判断类型然后忽略?
    • 因为可能用户并不知道为什么这个字段转换不生效,类型不对可能他要到运行的时候才能感受到某个字段不存在了,太多的隐藏规则可能反而是一种负担,因为你不知道他什么时候 可能会不生效

如:类型不匹配可能会如下:

// 两个 Family 相互转换
FamilyVO result = (FamilyVO) Lamia.mapping(family);

// 下面是 两个 Family 类的定义,Family中持有的 User对象不同
public class Family {
  String name;
  User user;
}

public class FamilyVO {
    String name;
    UserVO user;
}

img_6.png

如果在idea中配合lamia-plugin 使用可以在编码期就提示你类型不匹配,并且可以自动生成对应的对策语句,具体可以参考 lamia-plugin

可以看到生成的代码中,两个user 字段因类型不对报错了,所以需要去定义忽略掉user字段

使用rule().ignoreField(你的转换类::对应字段的getter方法) 可以忽略对应的字段,如下所示

FamilyVO familyVO = Lamia.builder().rule().ignoreField(FamilyVO::getUser)
                .mapping(family)
                .build(FamilyVO.class);

img_7.png

3.6 对象和map的相互转换

也提供了能力,可以让对象和map,相互转换

对于 对象 --> map,需要注意现在只支持泛型<String, Object> 以及 <String, String>, 其他的泛型会报错

如: img_8.png

img_9.png

注意: 在转成 <String, String> 的时候,因为自动使用了toString 为了防止出现npe 建议配合 规则规则RuleType.IGNORE_NULL 使用

默认情况之下,转成map, 都是使用 new HashMap() , 如果想设置成其他的map请自行创建,下面例子将展示怎么转换成ConcurrentHashMap:

 @LamiaMapping
    public void test8() {
        User user = ...;
        Map<String, String> result = new ConcurrentHashMap<>();
        Lamia.builder().mapping(user).build(result);
    }

以及可以反过来,map --> obj ,反向转换的时候,仅仅支持<String, Object>, 如下: img_10.png

4. 其他

  • 配合 lamia-plugin 可以获得更完整的体验
  • 欢迎各路大佬来帮忙完善整个框架
  • QQ交流群: 179545939