Internship record (2) Java common tool library

1. Lombok

1. Background overview

        Lombok is a very efficient automatic build plug-in library dedicated to Java, which simplifies the writing of JavaBean, avoids the appearance of redundant and boilerplate code, makes the written class more concise and clear, and can help you save a lot of repetitive and inefficient code writing. For example, repetitive Setters, Getters, ToStrings, constructors, logs, etc., can be automatically completed for us with only one line of annotation.

2. Principle analysis

        Lombok uses compile-time enhancements. At present, there are two kinds of common compile-time enhancement technologies for the Java language: 1. Use a special compiler, such as AspectJ's compiler AJC; 2. Use the relevant interfaces provided by JSR 269, such as the implementation of Lombok. Aspectj's AJC will be discussed below, so here we first discuss Lombok's implementation of JSR 269.

        The specific content of JSR 269 is Pluggable Annotation Processing API, which translates to plug-in annotation processing application program interface. This is an extension point provided by Java during compilation. Users can edit the content of classes at the stage of code compilation into bytecode Adjustment, the overall workflow is shown in the figure below:

         The figure above shows a general Javac compilation process. The Java file first constructs an AST (Abstract Syntax Tree Abstract Syntax Tree) through source code parsing, then executes JSR 269 plug-in extensions for annotation processing, and finally analyzes and optimizes the final Java file Generate binary .class files.

        Lombok takes advantage of the capabilities provided by JSR 269 and completes the repetitive work of getters, setters, etc. that we criticize very much during the stage of code compilation. However, methods such as getters and setters of classes are only generated during the stage of code compilation. , so when we use IDE tools, if there is no special function support (no Lomobk plug-in installed), we cannot use the code prompt function of the IDE, and the code will report red.

3. Installation configuration

(1) Introduce maven dependency

<dependencies>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.28</version>
		<scope>provided</scope>
	</dependency>
</dependencies>

(2) Install the IDEA plugin

  • Go to File > Settings > Plugins

  • Click on Browse repositories...

  • Search for Lombok Plugin

  • Click on Install plugin

  • Restart IntelliJ IDEA

        Note: The introduction of lombok will contain get and set methods in the .class file after the .java file is compiled, but the source code cannot find the definition of the method. IDEA will think this is an error, so a lombok plug-in needs to be installed

4. Commonly used annotations

(1) @Getter / @Setter annotation

        Annotate any field property or the entire class with @Getter and/or @Setter and let lombok automatically generate default getter/setter methods, which default to public. It is used as follows:

  • Before compiling:
@Getter
@Setter
public class LombokAnnotationTest {
    private Integer id;
    private String name;
}
  • After compilation:
public class LombokAnnotationTest {
    private Integer id;
    private String name;

    public LombokAnnotationTest() {
    }

    public Integer getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }
}

(2)@ToString/@EqualsAndHashCode

        These two annotations are also relatively easy to understand, that is, toString, equals and hashcode methods are generated, and the latter also generates a canEqual method to determine whether an object is an instance of the current class. By default, when generating a method, it will only act on non-static member variable fields in the class.

@ToString
@EqualsAndHashCode
public class Demo {

    private String name;
    private int age;
}

//@EqualsAndHashCode也有类似的下面的属性,
@ToString(
        includeFieldNames = true, //是否使用字段名
        exclude = {"name"}, //排除某些字段
        of = {"age"}, //只使用某些字段
        callSuper = true //是否让父类字段也参与 默认false
)
//@EqualsAndHashCode也有类似的下面的属性,
@ToString(
        includeFieldNames = true, //是否使用字段名
        exclude = {"name"}, //排除某些字段
        of = {"age"}, //只使用某些字段
        callSuper = true //是否让父类字段也参与 默认false
)

(3)@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor

        These 3 annotations are all used to generate a constructor that will accept 1 parameter for some field and simply assign this parameter to the field. in:

  • @NoArgsConstructor: used to generate a parameterless constructor

  • @RequiredArgsConstructor: Generates a special field constructor for all uninitialized final fields and fields marked @NonNull that are not initialized at declaration. For those fields marked with @NonNull, an explicit null check is also generated.

  • @AllArgsConstructor: A parameterized constructor used to generate all attribute fields, fields marked with @NonNull cause a null check for those parameters.

(4)@Data

        @Data is a composite annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor . @Data generates getters for all fields, setters for all non-final fields, and appropriate toString, equals, and hashCode implementations involving class fields, and constructors that initialize all final fields and all non-final fields have no initializers marked with @NonNull , to ensure that the field is never empty.

        Note: @RequiredArgsConstructor annotation will generate a parameterless constructor when there are no final and @NonNull annotated member variables in the class (because there are no parameters that meet the requirements), and many people think that @Data will generate a parameterless constructor. .

(5)@Slf4j

        The log class annotation is used on the class , which can save the step of generating the log object from the log factory, and log directly. The specific annotations vary according to different logging tools . Here, Slf4j is taken as an example. Log facade (Simple Logging Facade For Java), Slf4j is mainly to provide a standard and standardized API framework for Java log access. Its main significance is to provide interfaces. The specific implementation can be handed over to other log frameworks, such as log4j and logback, etc. .

  • Before compiling:

@Slf4j
public class LombokAnnotationTest {
    private Integer id;
    private String name;
}
  • After compilation:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LombokAnnotationTest {
    private static final Logger log = LoggerFactory.getLogger(LombokAnnotationTest.class);
    private Integer id;
    private String name;

    public LombokAnnotationTest() {
    }
}

Two. MapStruct

1. Overview of MapStruct

(1) Hierarchical background of project architecture

        In the common project architecture development process, software projects are designed hierarchically. The hierarchical design form and architecture design diagram are roughly as follows:

level

module name

method name

abbreviation

presentation layer

web

controller

VO (view object): View object, used for the presentation layer, its function is to encapsulate all the data of a specified page (or component).

domain layer

service

service

DTO (Data Transfer Object): Data transfer object, the data transfer object between the presentation layer and the service layer.

application layer

biz

domain/application

DO (Domain Object) : domain object is a tangible or intangible business entity abstracted from the real world.

persistence layer

dal

dao

PO (Persistent Object) : Persistent object, which forms a one-to-one mapping relationship with the data structure of the persistence layer (usually a relational database). If the persistence layer is a relational database, then each field in the data table ( or several) corresponds to one (or several) attributes of PO.

 The request flow is as follows:

  1. The user makes a request (maybe filling out a form), and the data in the form is matched as VO at the presentation layer.

  2. The display layer converts the VO into the DTO required by the corresponding method of the service layer and sends it to the service layer.

  3. The service layer first constructs (or rebuilds) a DO based on the data of the DTO, and calls the business method of the DO to complete the specific business.

  4. The service layer converts the DO into the PO corresponding to the persistence layer, calls the persistence method of the persistence layer, passes the PO to it, and completes the persistence operation.

  5. For a reverse operation, such as reading data, it is converted and passed in a similar way.

(2) Encoding problem

        The layered process brings a coding problem: between applications, there is a separate application subdivision module, and DO generally does not allow external dependencies. At this time, it is necessary to put DTO in the module that provides external interfaces for object transmission. , that is, the DO object is internal, and the DTO object is external. DTO can be changed according to business needs, and it is not necessary to map all attributes of DO. This kind of object-to-object conversion requires a tool specially designed to solve the conversion problem. After all, it will be troublesome to get/set every field. Therefore, the emergence of the object conversion framework solves the above coding pain points.

        Before the appearance of object conversion tools, we usually used get/set to perform mapping operations between object fields one by one, which was very complicated and hard-coded; later, many open source object conversion toolkits appeared, such as the common BeanUtils.copyProperties, But there are many restrictions and not flexible enough, so there is MapStruct.

        MapStruct is a Java annotation-based mapping processor for generating type-safe bean-mapped classes. You simply define a mapper interface that declares any required mapping methods. During compilation, MapStruct will automatically generate an implementation of this interface . This implementation uses ordinary Java method calls to map between the source object and the target object. During use, you only need to run mvn compile after the configuration is complete, and you will find that a mapper interface implementation class is generated in the target folder.

2. MapStruct User Guide

2.1 Configuration introduction

...
<!-- 版本定义 -->
<properties>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
...
<!-- mapstruct核心依赖 -->
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<!-- mapstruct代码构建和生成插件 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.2 Mapper definition

        To use mapstruct, you must first create a mapping converter, and mark the core annotation @Mapper on the interface or abstract class to define a mapper as an access entry. When we build/compile the application, the MapStruct annotation processor plug-in will identify the corresponding interface and generate an implementation class for it, and the specific conversion/mapping method will be declared in the interface.

Annotation Description Optional Elements
@Mapper Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via MapStruct.
  • injectionStrategy: The generation injection strategy of the conversion field, including constructor injection and field injection, the default is InjectionStrategy.FIELD
  • componentModel: Generate the component model of the mapper (defining the usage method), including default (getMapper method), spring (spring injection method), etc., the default is default

(1) Factory singleton injection

//接口定义
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    //convert method
    //...
}
//单例使用
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

(2) Spring dependency injection

//接口定义
@Mapper(componentModel = "spring")
public interface CarMapper {
    //convert method
    //...
}
//注入
@Autowired
private CarMapper carMapper;
//使用
CarDto carDto = carMapper.carToCarDto(car);

2.3 Mapping

2.3.1 Basic mapping

        The easiest way to use it is to directly declare the conversion method in the mapper (without configuring any annotations). The conversion method requires the source object as a parameter and the target object as a return value. The method name of the method can be freely selected, and the mapper only needs to pay attention to input The parameter type and return type can be determined to generate conversion code, public readable properties will be copied to the corresponding properties in the target (including properties declared on superclasses), and all fields with the same name and type will be copied implicitly. Basic mappings include the following cases and are illustrated in code samples:

  • Consistent mapping: The attribute field name, field type, and field quantity between conversions are completely consistent and correspond one-to-one. At this time, the conversion will be completely equivalent
  • Field inconsistency: The attribute field names are not completely consistent between conversions, inconsistent attribute names will not be automatically mapped, and the default value or null will be used
  • Inconsistent quantity: The number of attribute fields is not completely consistent between conversions, including more to less, less to more, missing or redundant attributes use default values ​​or null
  • Type inconsistency: The types of attribute fields between conversions are not completely consistent, and some fields with the same name will be converted implicitly, including basic types and packaging types, basic types and String, String and enumeration types, etc.

        After the project is built and compiled, the corresponding implementation class will be generated, and the relevant files are located in the target/generated-sources/annotations/...

@Mapper
public interface ConvertMapper{
    ConvertMapper INSTANCE = Mappers.getMapper(ConvertMapper.class);

    /**
     * 单对象转换: Source to Target
     */
    public Target singleConvert(Source source);

    /**
     * 集合List转换:List<Source> to List<Target>
     */
    public List<Target> listConvert(List<Source> sourceList);

    /**
     * 集合Set转换:Set<Integer> to Set<String>
     */
    public Set<String> setConvert(Set<Integer>  integerSet);
}

        Among them, the mapping of the collection type ( List, Setetc.) maps the element type in the same way as the object mapping, and the collection mapping is completed by calling the corresponding single object conversion method defined in the loop . If the corresponding single object mapping method is not declared in the interface in advance, it will be implicit Generate code. Note that declaring a map method with an iterable source and a non-iterable target, or vice versa, is not allowed. The source code of the generated implementation method is as follows:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-06-14T14:05:44+0800",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 11.0.19 (Oracle Corporation)"
)
public class ConvertMapperImpl implements ConvertMapper {

    /**
     * 单对象转换: Source to Target
     */
    @Override
    public Target singleConvert(Source source){
        if ( source == null ) {
            return null;
        }

        Target target = new Target();

        target.setId( source.getId() );
        target.setName( source.getName() );
        target.setXXX...

        return target;
    }


    /**
     * 集合List转换:List<Source> to List<Target>
     */
    @Override
    public List<Target> listConvert(List<Source> sourceList){
        if ( sourceList == null ) {
            return null;
        }
        //- 若元素映射方法事先声明:mapstruct则会自动查找对应的源和目标方法,并进行隐式调用
        //- 若元素映射方法未声明:mapstruct则自动会隐式生成singleConvert转换方法
        List<Target> list = new ArrayList<Target>( sourceList.size() );
        for ( Source source : sourceList ) {
            list.add( singleConvert( source ) );
        }

        return list;
    }

    /**
     * 集合Set转换:Set<Integer> to Set<String>
     */
    @Override
    public Set<String> integerSetToStringSet(Set<Integer> integerSet) {
        if ( integerSet == null ) {
            return null;
        }

        Set<String> set = new LinkedHashSet<String>();
        //自动调用元素类型的隐式转换
        for ( Integer integer : integerSet ) {
            set.add( String.valueOf( integer ) );
        }

        return set;
    }

}

2.3.2 Inconsistent mapping

        In actual development, usually the field names will not be exactly the same between different models, and the names may have slight changes. For inconsistent mapping, we mainly consider the case of name inconsistency and multi-source mapping. We will discuss data inconsistency in a separate section. Inconsistent mapping is mainly converted through @Mapping and @Mappings  annotations:

Annotation Description
@Mapping  Configures the mapping of one bean attribute or enum constant.
  • target: The field name of the target object to be mapped, the same target property cannot be mapped multiple times.

  • source: The field name of the data source object. Note that this attribute cannot be used with constant() or expression().

  • expression: The calculation expression must be given in the form of a Java expression, the format is as follows: java( <expression> ). Any types referenced in expressions must be given by their fully qualified names. Alternatively, types can be imported via Mapper.imports(). Note that this property cannot be used with source() , defaultValue() , defaultExpression() , qualifiedBy() , qualifiedByName() or constant() .

  • ignore: If set to true, the specified field will not be converted, the default is false.

  • dateFormat: A format string that can be handled by SimpleDateFormat if the property maps from String to Date or vice versa. Ignored for all other property types and when mapping enum constants. example:dateFormat = "yyyy-MM-dd HH:mm:ss"

  • numberFormat: A format string that can be handled by DecimalFormat if the annotated method maps from Number to String, and vice versa. Ignored for all other element types. example:numberFormat = "$#.00"

  • defaultValue:Defaults. If  source the property is null, the default value here will be used. If the target field is not of String type, it will try to find a matching conversion method, otherwise an error will be reported. Note that this attribute cannot be used with constant(), expression() or defaultExpression().

  • constant: Regardless of the original attribute value, directly set the target attribute to the specified constant. If the target field is not of String type, it will try to find a matching conversion method, otherwise an error will be reported. Note that this attribute cannot be used with source(), defaultValue(), defaultExpression(), or expression().

  • qualifiedBy: Select the mapper to assign a value to the target field. This is useful in cases where multiple mapping methods (handwritten or generated) qualify and thus result in an "Ambiguous mapping method found" error.

Annotation Description Optional Elements
@Mappings 可以包装配置一组多个 @Mapping 转换,当使用 Java 8 或更高版本时,可以省略 @Mappings 包装器注释并直接在一个方法上指定多个 @Mapping 注释。
  • Mapping [ ]:配置 @Mapping 集合

(1)字段名称不一致

        此处字段名称不一致主要指的是类型一致、但字段名称不一致的情况,类型不一致我们放到数据转换章节分析。

// We need map Source.sourceName to Target.targetName
@Mapper
public interface ConvertMapper{
    ConvertMapper INSTANCE = Mappers.getMapper(ConvertMapper.class);

    @Mapping(source="sourceName", target="targetName")
    @Mapping(source="sourceId", target="targetId")
    public Target singleConvert(Source source);
}

// generates:
@Override
public Target singleConvert(Source source) {
    target.setTargetName( source.getSourceName() );
    target.setTargetId( source.getSourceId() );
    // ...
}

(2)多源映射

        多源映射是指映射方法具有多个源参数,将多个源参数实体组合成一个返回目标对象。因为有时单个类不足以构建目标,我们可能希望将多个类中的值聚合为一个返回目标。多源映射与单参数映射方法一样,公共属性按名称对应自动映射。但如果多个源对象定义具有相同名称的公共属性,则必须使用@Mapping注解指定从中检索属性的源参数是哪个。

@Mapper
public interface ConvertMapper{
    ConvertMapper INSTANCE = Mappers.getMapper(ConvertMapper.class);

    @Mapping(source="source_a.Id", target="Id")
    @Mapping(source="source_b.sourceName", target="Name")
    public Target singleConvert(SourceA source_a,SourceB source_b);
}

2.3.3 更新现有实例

        在某些情况下,我们并不需要映射一个新的对象出来(以上方式的实现都是new object and return),而是需要对已有对象实例的某些映射属性值进行更新。可以通过为目标对象参数添加 @MappingTarget 注解来实现:

Annotation Description Optional Elements
@MappingTarget Declares a parameter of a mapping method to be the target of the mapping.

最多只能将一个方法参数声明为 MappingTarget。注意:作为映射目标传递的参数不能为空(null)。

//1. Update exist bean without return value - 无返回值
@Mapper
public interface ConvertMapper {
    void updateHuman(HumanDto humanDto, @MappingTarget Human human);
}
// generates
@Override
public void updateHuman(HumanDto humanDto, Human human) {
    human.setName( humanDto.getName() );
    // ...
}
 
//2. Update exist bean and return it - 更新并返回
@Mapper
public interface ConvertMapper {
    Human updateHuman(HumanDto humanDto, @MappingTarget Human human);
}
// generates:
@Override
public Human updateHuman(HumanDto humanDto, Human human) {
    // ...
    human.setName( humanDto.getName() );
    return human;
}

注意:更新现有实例也可以同步添加@Mapping注解来映射不一致字段,二者并不冲突 ,非映射项则不会更新。

2.3.4 自定义映射

(1)使用表达式

        有时我们的目标属性并不只是进行简单的映射,MapStruct允许在@Mapping注解中定义Java表达式来进行简单的逻辑映射。该注解参数包括 defaultExpression source 取值为 null时生效),或者 expression(固定执行,不能与 source 、defaultExpression 一起使用),整个源对象都可以在表达式中使用,但表达式中引用的任何类型都必须通过其完全限定名称给出, 或者通过 Mapper.imports() 导入类型。

//1. 全限定类名引用
@Mapper
public interface SourceTargetMapper {

    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(target="id", source="sourceId", defaultExpression = "java( java.util.UUID.randomUUID().toString() )")
    @Mapping(target = "timeAndFormat",expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
    @Mapping(target = "targetTime", expression = "java(date2Long(s.getSourceTime()))")
    Target sourceToTarget(Source s);

    default Long date2Long(Date date) {
        return date != null ? date.getTime() : null;
    }
}

//2. imports 导入引用
imports org.sample.TimeAndFormat;
imports java.util.UUID;

@Mapper( imports = {TimeAndFormat.class, UUID.class})
public interface SourceTargetMapper {

    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
    @Mapping(target = "timeAndFormat",
         expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
    Target sourceToTarget(Source s);

    //...
}

注意:MapStruct在任何方法中数据类型不匹配需要转换时(比如date2Long),会根据入参和返回值类型,自动匹配定义的方法(即使不指明),即隐式调用。但是如果你的Mapper接口比较复杂了,里面定义了出参和返回值相同的两个方法,则必须使用@Mapping指定使用哪个方法(或者使用@Named标记方法防止隐式调用),否则在编译时MapStruct会因为不知道用哪个方法而报错。当然你可以不用想这么多,先编译再说,如果报错了再去处理即可,这也是MapStruct的一个好处:在编译期就可以发现对象转换的错误,而不是到运行时。

(2)自定义方法

        大部分情况下我们通过mapstruct 注解自动生成的映射代码就可以进行各种属性转换,但有时我们也需要实现一些复杂、自定义的映射逻辑,这种情况下mapstruct允许我们在映射器中添加自定义的映射方法,并且像其他自动映射方法一样访问:

  • 接口 interface:通过接口方法的默认实现 default
  • 抽象类 abstract:通过类方法的实现,可以在类中声明其他字段
//1. 接口方式
@Mapper
public interface ConvertMapper {

    @Mapping(...)
    ...
    CarDto carToCarDto(Car car);

    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

//2. 抽象类方式
@Mapper
public abstract class CarMapper {

    @Mapping(...)
    ...
    public abstract CarDto carToCarDto(Car car);

    public PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

注意:如果参数和返回类型匹配,mapstruct自动生成的代码将会在嵌套映射或集合映射需要时调用自定义方法实现做元素转换。 

2.4 数据转换

2.4.1 隐式类型转换

        对于映射数据类型不一致的情况,MapStruct支持sourcetarget属性之间的大部分常见数据类型的自动转换,或称为隐式转换。自动类型转换适用于:

  • 基本类型及其对应的包装类之间。比如, intIntegerfloatFloatlongLongbooleanBoolean 等。
  • 任意基本类型与任意包装类之间。如 intlongbyteInteger 等。
  • 所有基本数据类型及包装类与String之间。如 booleanStringIntegerStringfloatString 等。
  • 枚举类和String之间。
  • Java大数类型(java.math.BigIntegerjava.math.BigDecimal) 和Java基本类型(包括其包装类)与String之间。
  • Java日期类型 DateTimeLocalDateTimeLocalDateLocalTime和 String 之间等,可以通过SimpleDateFormat的dateFormat选项指定格式字符串。
  • Java String 和 StringBuilder、UUID String 之间等。
  • 其它情况详见MapStruct官方文档

        因此,在生成映射器代码的过程中,如果源字段和目标字段之间属于上述任何一种情况,则MapStrcut会自行处理类型转换即隐式转换。

@Mapper
public interface CarMapper {
    //int to String
    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);
    //Date to String
    @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
    CarDto carToCarDto(Car car);
}

2.4.2 嵌套对象转换

(1)引用映射与隐式调用

        通常情况下,对象的属性并不止包括基本数据类型,还有对其它对象的引用或更深的嵌套。比如 Car 类可以包含一个名为 driver 的 Person 对象(代表汽车驾驶员)的引用属性,该对象应映射到 CarDto 类引用的 PersonDto 对象。

@Mapper
public interface CarMapper {

    CarDto carToCarDto(Car car);

    PersonDto personToPersonDto(Person person);
}

        生成的carToCarDto()方法代码将自动调用personToPersonDto()用于映射driver属性的方法,而生成的personToPersonDto()实现用于执行person对象的映射。 理论上这样就可以映射任意深度对象图,这种自动映射也被称为隐式调用,常出现在以下场景:

  • 如果source和target属性具有相同的类型,则该值将简单地从源复制到目标(浅拷贝)。如果属性是集合(例如 List),则集合的副本将被设置到目标属性中。

  • 如果源属性和目标属性类型不同,则会自动检查是否存在一种已经声明的映射方法(该方法将源属性的类型作为参数类型,并将目标属性的类型作为返回类型),如果存在这样的方法,则会在生成的映射实现中自动调用它来转换。

  • 如果不存在此类方法,MapStruct将查判断否存在属性的源类型和目标类型的内置转换(隐式类型转换)。如果是这种情况,生成的映射代码将应用此转换。

  • 如果不存在这样的方法,MapStruct 将尝试应用复杂的转换

    • target = method1( method2( source ) )

    • target = method( conversion( source ) )

    • target = conversion( method( source ) )

  • 如果没有找到这样的方法,MapStruct将尝试生成一个自动子映射方法,该方法将执行源和目标属性之间的简单映射。

  • 如果MapStruct无法创建基于名称的映射方法,则会在构建编译时引发错误,指示不可映射的属性及其路径。

        注意:可以使用 @Mapper( disableSubMappingMethodsGeneration = true ) 阻止MapStruct 生成自动子映射方法,也可以通过元注释完全控制映射比如 @DeepClone

(2)嵌套映射

        在一些复杂情况下,嵌套对象属性的引用可能包含多个层级并且很多情况下名称也存在不匹配的差异,这时就需要人为的在自动映射的基础上进行映射控制。现有source和target如下:

//1. spurce FishTank
public class Fish {

    private String type;
}

public class Interior {

    private String designer;
    private Ornament ornament;
}

public class FishTank {

    private Fish fish;
    private String name;
    private MaterialType material;
    private Interior interior;
    private WaterQuality quality;
}

//2. target FishTankDto 
public class FishDto {

    private String kind;

    // make sure that mapping on name does not happen based on name mapping
    private String name;
}

public class MaterialDto {

    private String manufacturer;
    private MaterialTypeDto materialType;// same to MaterialType
}

public class FishTankDto {

    private FishDto fish;
    private String name;
    private MaterialDto material;
    private OrnamentDto ornament; //same to Interior.ornament
    private WaterQualityDto quality;
}

        In simple scenarios, we only need corrections for properties on the nesting level (such as fish); the same construct can be used to ignore certain properties at the nesting level (such as ignore); when the source and target do not share the same When nesting levels (attributes of the same depth), hierarchical selection (such as material) can be performed; when the mapping first shares a common mapping base, mapping completion (such as quality and report) can be performed on the base, and the mappers are as follows:

@Mapper
public interface FishTankMapper {

    @Mapping(target = "fish.kind", source = "fish.type")
    @Mapping(target = "fish.name", ignore = true)
    @Mapping(target = "ornament", source = "interior.ornament")
    @Mapping(target = "material.materialType", source = "material")
    @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
    FishTankDto map( FishTank source );
}

        It even includes two ways of writing another situation:

//第一种写法:逐一指定各个资源的映射关系
//优点:方便精细控制需要转换的字段
@Mapping(source = "userInfo.idCard", target = "idCard")
@Mapping(source = "userInfo.avatar", target = "avatar")
UserVO convert(UserDO person);

//第二种写法:利用隐式转换对所有同名字段做转换
//优点:书写简单
@Mapping(source = "userInfo", target = ".")
UserVO convert(UserDO person);

2.5 Advanced Mapping

2.5.1 Custom facets

        For further control and customization, MapStruct also provides two custom aspect annotations   to implement method enhancement and unified logic control before and after the corresponding type mapping method . Annotated methods must have a corresponding implementation body, which is implemented by default methods in the interface interface and non-abstract methods in the abstract class abstract .@BeforeMapping, @AfterMapping

If the @BeforeMapping / @AfterMapping method has parameters, the method invocation is only generated if the return type of the method (if non-void) is assignable to the return type of the mapping method and all parameters can be assigned by the source or target parameters of the mapping method

  • The two annotations do not have any parameters
  • Two annotations can mark multiple methods as aspects, and the calling order of the same type follows the definition order
@Mapper
public abstract class HumanConvertor {
    //前置通知类型1:无参类型
    @BeforeMapping
    public void calledWithoutArgsBefore() {
         // ...
    }
    //前置通知类型2:参数含有对应Source类型
    @BeforeMapping
    protected void calledWithHuman(Human human) {
        // ...
    }
    //前置通知类型3:参数含有对应Source类型和对应Target类型@MappingTarget(性别判断需求)
    @BeforeMapping
    protected void calledWithSourceAndTargetBefore(Human human, @MappingTarget HumanDto humanDto) {
        if (human instanceof Man) {
            humanDto.setGenderType(GenderType.MAN);
        } else if (human instanceof Woman) {
            humanDto.setGenderType(GenderType.WOMAN);
        }
    }


    //后置通知类型1:无参类型
    @AfterMapping
    public void calledWithoutArgsAfter() {
         // ...
    }
    //后置通知类型2:参数含有对应Target类型(@MappingTarget)
    @AfterMapping
    protected void calledWithDto(@MappingTarget HumanDto humanDto) {
        humanDto.setName(String.format("【%s】", humanDto.getName()));
    }
    //后置通知类型3:参数含有对应Source类型和对应Target类型@MappingTarget
    @AfterMapping
    public void calledWithSourceAndTargetAfter(Human human, @MappingTarget HumanDto humanDto) {
         // ...
    }

    public abstract HumanDto toHumanDto(Human human);
}

        The generated code calls in the following order:

 // generates:
public class HumanConvertorImpl extends HumanConvertor {

    @Override
    public HumanDto toHumanDto(Human human) {
        //前置通知1
        calledWithoutArgsBefore();
        //前置通知2
        calledWithHuman(human);

        if (human == null) {
            return null;
        }

        HumanDto humanDto = new HumanDto();

        //前置通知3
        calledWithSourceAndTargetBefore( human, humanDto );

        ...

        //后置通知1
        calledWithoutArgsAfter();
        //后置通知2
        calledWithDto( humanDto );
        //后置通知3
        calledWithSourceAndTargetAfter( human, humanDto );

        return humanDto;
    }
}

Guess you like

Origin blog.csdn.net/qq_40772692/article/details/131179152