文章
问答
冒泡
浅用MapStruct

MapStruct简介

MapStruct 其实就是一个对象映射框架,作用与orika、Spring BeanUtils、Apache BeanUtils等工具作用相同,它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。生成的映射代码使用简单的方法调用,因此速度快、类型安全且易于理解。

官方网址:https://mapstruct.org/

简单使用

下面我将基于maven项目来做一个简单的使用演示。

pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.justin</groupId>
    <artifactId>mapstruct-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>mapstruct-demo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
        <lombok.version>1.18.26</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <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>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

model类

package com.justin.mapstruct.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class Car {
    private String name;
    private Integer seatNumber;
    private CarTypeEnum type;

    public enum CarTypeEnum {
        TYPE_1, TYPE_2;
    }
}
package com.justin.mapstruct.model;

import com.justin.mapstruct.model.Car.CarTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class CarDTO {
    private String name;
    private Integer seatNumber;
    private CarTypeEnum type;
}

mapper类

package com.justin.mapstruct.mapper;

import com.justin.mapstruct.model.Car;
import com.justin.mapstruct.model.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

    @Mapping(source = "type", target = "type", defaultExpression = "java(com.justin.mapstruct.model.Car.CarTypeEnum.TYPE_2)")
    @Mapping(target = "seatNumber", ignore = true)
    CarDTO carToCarDTO(Car car);

    List<CarDTO> carToCarDTO(List<Car> cars);

    /**
     * add custom methods to mappers
     */
    default List<CarDTO> carToCarDTOWithCustomMethod(Set<Car> cars) {
        if (cars == null) {
            return null;
        }

        List<CarDTO> list = new ArrayList<>();
        for (Car car : cars) {
            list.add(carToCarDTO(car));
        }

        return list;
    }

    CarDTO mapToCarDTO(Map<String, String> params);
}

测试类

package com.justin.mapstruct;

import com.justin.mapstruct.mapper.CarMapper;
import com.justin.mapstruct.model.Car;
import com.justin.mapstruct.model.Car.CarTypeEnum;
import com.justin.mapstruct.model.CarDTO;
import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class AppTest {

    @Test
    public void carToCarDTO() {
        Car car = Car.builder().name("123").seatNumber(2).type(CarTypeEnum.TYPE_1).build();

        CarDTO carDTO = CarMapper.INSTANCE.carToCarDTO(car);

        Assert.assertEquals(car.getName(), carDTO.getName());
        Assert.assertNull(carDTO.getSeatNumber());
        Assert.assertEquals(CarTypeEnum.TYPE_1, carDTO.getType());
    }

    @Test
    public void carListToCarDTOList() {
        List<Car> cars = new ArrayList<>();
        cars.add(Car.builder().name("1").seatNumber(1).type(CarTypeEnum.TYPE_1).build());
        cars.add(Car.builder().name("2").seatNumber(2).type(CarTypeEnum.TYPE_2).build());

        List<CarDTO> carDTOS = CarMapper.INSTANCE.carToCarDTO(cars);

        Assert.assertNotNull(carDTOS);
        Assert.assertEquals(cars.size(), carDTOS.size());
    }

    @Test
    public void carToCarDTOWithCustomMethod() {
        Set<Car> cars = new HashSet<>();
        cars.add(Car.builder().name("1").seatNumber(1).type(CarTypeEnum.TYPE_1).build());
        cars.add(Car.builder().name("2").seatNumber(2).type(CarTypeEnum.TYPE_2).build());

        List<CarDTO> carDTOS = CarMapper.INSTANCE.carToCarDTOWithCustomMethod(cars);

        Assert.assertNotNull(carDTOS);
        Assert.assertEquals(cars.size(), carDTOS.size());
    }

    @Test
    public void mapToCarDTO() {
        Map<String, String> params = new HashMap<>();
        params.put("name", "1");
        params.put("seatNumber", "2");
        params.put("type", CarTypeEnum.TYPE_1.name());

        CarDTO carDTO = CarMapper.INSTANCE.mapToCarDTO(params);

        Assert.assertEquals("1", carDTO.getName());
        Assert.assertEquals(Optional.of(2).get(), carDTO.getSeatNumber());
        Assert.assertEquals(CarTypeEnum.TYPE_1, carDTO.getType());
    }
}

所有测试类都可运行通过,这里我验证了几个常用的场景,基本可以满足日常使用了。如果是在spring环境中的话,可以通过配置将对应的mapper类自动注入到spring容器中来使用,还是比较方便的。

另外相对于orika比较来说,虽说增加了一些代码配置,但是orika好几年不更新了,最近在jdk11上使用也遇到一些问题,mapstruct可以作为orika的一个替换方案吧。

下面我们可以看一看mapstruct具体为我们生成的实现类代码(CarMapperImpl):

package com.justin.mapstruct.mapper;

import com.justin.mapstruct.model.Car;
import com.justin.mapstruct.model.CarDTO;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-05-13T14:51:23+0800",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 11.0.2 (Oracle Corporation)"
)
public class CarMapperImpl implements CarMapper {

    @Override
    public CarDTO carToCarDTO(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDTO.CarDTOBuilder carDTO = CarDTO.builder();

        if ( car.getType() != null ) {
            carDTO.type( car.getType() );
        }
        else {
            carDTO.type( com.justin.mapstruct.model.Car.CarTypeEnum.TYPE_2 );
        }
        carDTO.name( car.getName() );

        return carDTO.build();
    }

    @Override
    public List<CarDTO> carToCarDTO(List<Car> cars) {
        if ( cars == null ) {
            return null;
        }

        List<CarDTO> list = new ArrayList<CarDTO>( cars.size() );
        for ( Car car : cars ) {
            list.add( carToCarDTO( car ) );
        }

        return list;
    }

    @Override
    public CarDTO mapToCarDTO(Map<String, String> params) {
        if ( params == null ) {
            return null;
        }

        CarDTO.CarDTOBuilder carDTO = CarDTO.builder();

        if ( params.containsKey( "name" ) ) {
            carDTO.name( params.get( "name" ) );
        }
        if ( params.containsKey( "seatNumber" ) ) {
            carDTO.seatNumber( Integer.parseInt( params.get( "seatNumber" ) ) );
        }
        if ( params.containsKey( "type" ) ) {
            carDTO.type( Enum.valueOf( Car.CarTypeEnum.class, params.get( "type" ) ) );
        }

        return carDTO.build();
    }
}

其实与我们一般自己的实现差不多。

IDEA开发环境插件集成

File=>Settings=>Plugins,安装插件:【MapStruct Support】

可能会遇到的问题

问题一:java: No property named "type" exists in source parameter(s). Type "Car" has no properties.

这个是我同时使用了lombok和mapstruct出现的问题,其实也是lombok与mapstruct使用顺序的问题,因为这两个都会在编译期生成代码,mapstruct是基于getter/setter方法,而getter/setter方法是由lombok生成的代码,因此mapstruct需要在lombok生成完代码之后才能正常工作,那么在maven中就需要先配置lombok依赖,然后再配置mapstruct依赖,或者是在maven-compiler-plugin中使用annotationProcessorPaths,确保注解处理器的使用顺序。

方式一(通过jar包引入的顺序来确定注解处理器的使用顺序):

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.justin</groupId>
    <artifactId>mapstruct-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>mapstruct-demo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
        <lombok.version>1.18.26</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

方式二(也是官方使用的方式,这样就没有必要刻意需要去保证依赖的顺序了):

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.justin</groupId>
    <artifactId>mapstruct-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>mapstruct-demo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
        <lombok.version>1.18.26</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <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>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

问题二:生成的mapper实现类中,javax.annotation.Generated报错(类找不到)

jdk11及其以上移除了该包,引入对应jar即可:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
java
mapstruct

关于作者

justin
123456
获得点赞
文章被阅读