Skip to content

装配操作

huangchengxing edited this page Feb 15, 2023 · 1 revision

概述

crane4j 中, “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这句话被视为一次装配操作,一次装配操作只会基于一个 key 字段触发,一个对象完成一次填充可能需要经过多次装配操作。

一、声明

一个 Class 对应的操作配置都通过 BeanOperationParser 解析,由于目前解析器只有 AnnotationAwareBeanOperationParser 一种,因此只能基于注解完成装配操作的声明。

关于属性映射部分可以参考字段映射一节。

1、在属性上声明

一般情况下,可以使用 @Assemble 在某个 key 字段上声明一次装配操作:

public class Student {
    @Assemble(namespace = "student", props = @Mapping(src = "studentName", ref = "name"))
    private Integer id;
    private String name;
}

上述配置表示这样一个操作:

  1. 使用 id 字段的值;
  2. namespacestudent 的数据源容器中获取数据源对象;
  3. 获得与 id 字段值对应的数据后,将数据源对象中的 studentName 字段值,映射到 name 字段上;

在父类中声明的操作,子类也会一并继承。

2、在类上声明

有时候为了保持代码的整洁,或者不方便直接修改父类,我们也可以不直接在属性上添加注解,而是在类上添加注解:

@Operations(
    assembles = @Assemble(key = "id", namespace = "student", props = @Mapping(src = "studentName", ref = "name"))
)
public class Student {
    private Integer id;
    private String name;
}

与直接在属性上声明不同,需要通过 key 属性绑定对应的 key 字段。

在父类上声明的配置同样可以被子类继承。

3、多重装配

有时候,我们会需要在一个 key 上声明多次装配操作,这也是允许的:

public class UserVO {
    @Assemble(namespace = "user", props = @Mapping(src = "role", ref = "role"), groups = "admin")
    @Assemble(namespace = "user", props = @Mapping(src = "name", ref = "name"), groups = {"base", "admin"})
    private Integer id;
    private String name;
    private String role;
}

当使用默认的执行器时,不必担心多次查库,默认的执行器 DisorderedBeanOperationExecutor 会将容器相同的操作汇总在一起再执行,保证尽可能少的减少数据源容器的调用次数。

二、组合注解

出于代码的整洁,有时候如果要映射的字段特别多,或者相同的注解配置需要反复使用,推荐使用组合注解:

// 将目标注解作为元注解
@Assemble(key = "id", namespace = "student", props = @Mapping(src = "studentName", ref = "name"))
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AssembleStudent { }

然后直接使用合成注解 @AssembleStudent 代替原本的一大坨:

public class Student {
    // 直接使用合成注解
    @AssembleStudent
    private Integer id;
    private String name;
}

该功能基于 SpringMergeAnnotation 实现,遵循 Spring 组合注解的语义。

三、排序

默认情况下,统一类中的操作的排序为:

  • 在属性上直接使用 @Assemble 声明的操作,按属性顺序排序;
  • 通过 @Operations 声明的操作,按它们声明的顺序排序;
  • 在同一类中,属性上声明的操作优先于类上声明的操作;
  • 在不同中,子类中声明的操作优先于父类中声明的操作;

除此之外,也可以通过 sort 属性可以指定不同操作之间的顺序:

public class Student {
    @Assemble(namespace = "id", sort = 0, props = @Mapping(ref = "id3"))
    private Integer id1;
    @Assemble(namespace = "id", sort = 1, props = @Mapping(ref = "id4"))
    private Integer id2;
    @Assemble(namespace = "id", sort = 1, props = @Mapping(ref = "id2"))
    private Integer id3;
    private Integer id4;
}

上述示例中, 三个操作的执行顺序分别为 id1 -> id3 -> id2sort 的值越小,则排序时越靠前,这个顺序将会优先于默认的排序规则。

需要注意的是,排序值不一定代表最终的执行顺序,只代表遍历 BeanOperations 中的装配操作时的顺序,最终的执行顺序需要通过 BeanOperationExecutor 来保证。

默认支持按顺序执行的执行器为 OrderedBeanOperationExecutor,不过相较其他执行器性能会差一些,用户可以根据自己的需求选择。

四、装配操作处理器

所有的装配操作中的读跟写最终都通过装配操作处理器 AssembleOperationHandler 完成处理,用户同样也可以在注解的 handler 属性中选择处理器:

public class Student {
    @Assemble(
        key = "id", namespace = "student", 
        props = @Mapping(src = "studentName", ref = "name"),
        handler = ReflectAssembleOperationHandler.class // 指定操作处理器
    )
    private Integer id;
    private String name;
}

在配置解析过程中,会从 Spring 上下文根据指定的类型获取对应的操作处理器。

目前 AssembleOperationHandler 只提供了基于反射的单 key 值处理器 ReflectAssembleOperationHandler 和多 key 值处理器 MultiKeyAssembleOperationHandler两种实现。

用户可以自定义操作处理器,以便更好的支持其他类型的数据,或者提高处理器的执行效率。