跪拜 Guibai
← Back to the summary

MapStruct Plus Eliminates the Mapper Interface, Leaving Only an Annotation

Follow my public account: [编程朝花夕拾] to get first-release content.

01 Introduction

In daily development, we often encounter the problem of object conversion. Especially after Alibaba proposed the concepts of DTO, BO, VO, and PO, the scenarios for conversion have increased even more.

There are quite a few conversion tools available, such as BeanUtils, MapStruct, Orika, and others, each with different choices made by individuals or teams.

02 Three Major Schools

The matter of "object-to-object" conversion in the Java ecosystem roughly falls into three schools.

2.1 The Manual School

new an object, then assign values one by one using setter. This is the simplest and most common method. However, if there are many fields, it feels very tedious.

UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setAge(user.getAge());
dto.setEmail(user.getEmail());
dto.setPhone(user.getPhone());
dto.setCreateTime(user.getCreateTime());
// ... 30 lines of setters
return dto;

But in the AI era, AI suggestion tools help us directly associate relationships, saving a lot of effort, but the amount of code remains unchanged.

Pros and Cons:

2.2 The Reflection School

Representative reflection utility classes: Spring BeanUtils, Apache Commons BeanUtils, Hutool BeanUtil.

BeanUtils.copyProperties(source, target);

Using utility classes is the most effortless, but different tools have slightly different parameters and final structures. If field types are inconsistent, conversion failures may occur. Most critically, performance is the worst.

Pros and Cons:

2.3 The Compile-Time Generation School

Representatives: MapStruct, MapStruct Plus.

This means during your javac compilation, APT (Annotation Processing Tool) automatically generates conversion code, with no reflection at runtime. The runtime effect is the same as the Setter method.

This is also the key tool we are introducing today.

03 MapStruct

3.1 Introduction

MapStruct is the de facto standard for Bean mapping in the Java world, under the Apache-2.0 license, with 7.7k stars on GitHub, maintained by an entire organization. The latest stable version is 1.6.3.

Official Website: https://mapstruct.org

GitHub Address: https://github.com/mapstruct/mapstruct

3.2 Usage

Usage is also very simple.

Maven Dependency

...
<properties>
    <org.mapstruct.version>1.6.3</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

Define Conversion Interface

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapper {

    UserVO userDtoToVO(UserDTO userDto);
}

Here, using the Spring instance mode, you can customize the mapping of different fields on the defined methods via @Mapping(target = "a", source = "b").

Configure the annotation processor in Maven, and during compilation, it generates a UserMapperImpl.java for you, filled entirely with proper target.setXxx(source.getXxx()).

Invocation

3.3 Disadvantages

The biggest problem with this thing: annoying.

For every pair of type conversions, you have to write a @Mapper interface. If your project has 50 DTOs, that's 50 Converter classes. A direct class explosion!

04 MapStruct Plus

A domestic "plug-in" has arrived, which, as the name suggests, is an enhanced version of MapStruct.

4.1 Introduction

Mapstruct Plus is an enhancement tool for Mapstruct. Based on Mapstruct, it implements the function of automatically generating Mapper interfaces and strengthens some features, making Java type conversion more convenient and elegant.

It might be the simplest and most powerful Java Bean conversion tool.

The pain point solved by Mapstruct Plus is precisely the elimination of defining conversion interfaces, replaced by annotations.

Official Website: https://www.mapstruct.plus/

GitHub Address: https://github.com/linpeilie/mapstruct-plus

4.2 Usage

Maven Dependency

<properties>
    <mapstruct-plus.version>latest version</mapstruct-plus.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.github.linpeilie</groupId>
                        <artifactId>mapstruct-plus-processor</artifactId>
                        <version>${mapstruct-plus.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Define Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = UserVO.class)
public class UserDTO {
    /**  User ID */
    private Integer id;
    /**  Username */
    private String name;
    /**  Date of Birth */
    private Date birthday;
    /**  Phone Number */
    private String phone;
}

The most critical annotation here is @AutoMapper(target = UserVO.class)

Invocation

It's that simple, a single annotation achieves mutual conversion. Let's see why: it turns out that during compilation, implementation classes for mutual conversion are generated.

4.3 Advantages

4.4 Disadvantages

Since it is an enhancement tool for another, it inevitably depends on MapStruct.

05 Bean Conversion Tool Collection

Let's organize the commonly used conversion tools:

5.1 Compile-Time School

5.2 Reflection School

5.3 Bytecode School

06 Summary

Choosing a tool is not about choosing the strongest, but the most suitable. Every tool has its necessary use cases, and complex business logic can never be separated from handwritten code as a fallback.