Springboot uses Mapstruct to copy objects and integrate swagger2

Actual demand

Obtain the third-party interface through feign, and map the result to dto, but the naming of the value received by the properties of the object in dto may be irregular (all uppercase, etc., not camel-case commands, etc.), so vo is used to receive the value of dto .

If it's just an object copy, you can use BeanUtils.copyProperties to assign properties between objects (shallow copy)

But if there are objects and collections in the object, and the copy fails, you can use the Mapstruct tool class for deep copy.

Mapstruct implementation steps

1. Introduce related dependencies (pom.xml)

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

2. Introduce plugin (pom.xml)

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!--<annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-jdk8</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>-->
                </configuration>
            </plugin>

3. New mapper interface SourceTargetMapper

package com.frank.hello.mapper;

import com.frank.hello.dto.DataResponse;
import com.frank.hello.dto.Teacher;
import com.frank.hello.vo.DataResponseVO;
import com.frank.hello.vo.TeacherVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:39
 * @Description: ${todo}
 */
@Mapper(componentModel = "spring")
public interface SourceTargetMapper {

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

    TeacherVO toTarget(Teacher source);
    // 如果需要转多个,则再定义一个方法,将源数据和目标数据修改即可
    // TargetData toTargetData(SourceData sourceData);

}

4. Call the interface method, pass in the source data, and generate the target data

SoucrceData data = SourceTargetMapper.MAPPER.toTarget(targetData);

Potential pitfalls in integrating mapstruct

1) View the information. If swagger is introduced, and mapstruct is introduced, you need to add <exclusions> under the swagger dependency as shown below</exclusions>

<!--  引入swagger包 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.2.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>mapstruct</artifactId>
                    <groupId>org.mapstruct</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.2.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>mapstruct</artifactId>
                    <groupId>org.mapstruct</groupId>
                </exclusion>
            </exclusions>
        </dependency>

2) When mapstruct and lombok are introduced at the same time, get and set methods (implementation classes of compile generation interface) may not be generated. Add <annotationProcessorPaths> to the plugin as shown below</annotationProcessorPaths>

Note that the version of lombok should not be too low, here I am:

<properties>
    <java.version>1.8</java.version>
    <m2e.apt.activation>jdt_apt</m2e.apt.activation>
    <org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
    <lombok.version>1.18.12</lombok.version>
</properties>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!--<annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-jdk8</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>-->
                </configuration>
            </plugin>

3) The last point I was miserable.

Note that the attribute names in DTO and VO must be the same, the returned objects can be different, but the attribute names must be the same, and the attribute names of the objects in the list in the figure must also be consistent, otherwise compile will not generate the corresponding get, The object copied by set is incorrect. I didn't copy all the attributes. I was miserable at this place. (In the actual project, there are too many levels of nesting of DTO objects. I didn't check whether the attributes are consistent.

4) If the object is not complicated, you can perform mapping one-to-one mapping in the following way

/**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param user 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);

Directory Structure:

The application.properties file has not yet configured content

Swagger2
package com.frank.hello.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author 小石潭记
 * @date 2020/7/2 22:11
 * @Description: ${todo}
 */
@Configuration
@EnableSwagger2
// http://localhost:8080/swagger-ui.html
public class Swagger2 {
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.frank.hello"))//扫描接口的包
                .build();
    }

    public ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("spring boot利用swagger构建api文档")
                .description("简单优雅的rest风格")
                .termsOfServiceUrl("http://localhost:8080")//文档遵循的开发协议的展现网址
                .version("1.0")//版本
                .build();
    }
}
Animal
package com.frank.hello.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:11
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Animal {

    @JsonProperty("NAME")
    private String name;

    @JsonProperty("AGE")
    private int age;

}
DataResponse
package com.frank.hello.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:15
 * @Description: ${todo}
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
public class DataResponse {

    @JsonProperty("CODE")
    private int code;

    @JsonProperty("MESSAGE")
    private String message;

    @JsonProperty("DATA")
    private Teacher data;

}
Student
package com.frank.hello.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:10
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    @JsonProperty("ID")
    private int id;

    @JsonProperty("NAME")
    private String name;

    @JsonProperty("ADDRESS")
    private String address;

    @JsonProperty("ANIMALS")
    private List<Animal> animals;

}
Teacher
package com.frank.hello.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:10
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {

    @JsonProperty("ID")
    private int id;

    @JsonProperty("NAME")
    private String name;

    @JsonProperty("ADDRESS")
    private String address;

    @JsonProperty("STUDENTS")
    private List<Student> students;

}
SourceTargetMapper(核心配置,compile项目之后,会生成该接口的实现类)
package com.frank.hello.mapper;

import com.frank.hello.dto.DataResponse;
import com.frank.hello.dto.Teacher;
import com.frank.hello.vo.DataResponseVO;
import com.frank.hello.vo.TeacherVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:39
 * @Description: ${todo}
 */
@Mapper(componentModel = "spring")
public interface SourceTargetMapper {

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

    TeacherVO toTarget(Teacher dataResponse);
    // 如果需要转多个,则再定义一个方法,将源数据和目标数据修改即可
    // TargetData toTargetData(SourceData sourceData);

}
AnimalVO
package com.frank.hello.vo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:11
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "AnimalVO", description = "动物类")
public class AnimalVO {

    @ApiModelProperty(value = "名字",
                        name = "name",
                        example = "小花")
    private String name;

    @ApiModelProperty(value = "年级",
            name = "age",
            example = "1")
    private int age;
}
DataResponseVO
package com.frank.hello.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:15
 * @Description: ${todo}
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Accessors(chain = true) //链式风格,在调用set方法时,返回这个类的实例对象
@ApiModel(value = "DataResponseVO", description = "返回的结果类")
public class DataResponseVO {

    @ApiModelProperty(value = "返回的code码",
            name = "code",
            example = "200")
    private int code;

    @ApiModelProperty(value = "返回的信息",
            name = "message",
            example = "success")
    private String message;

    @ApiModelProperty(value = "返回的数据",
            name = "data",
            example = "返回的数据")
    private TeacherVO data;
}
StudentVO
package com.frank.hello.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:10
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "StudentVO", description = "学生类")
public class StudentVO {

    @ApiModelProperty(value = "学号",
            name = "id",
            example = "1001")
    private int id;

    @ApiModelProperty(value = "学生的姓名",
            name = "name",
            example = "张三")
    private String name;

    @ApiModelProperty(value = "学生地址",
            name = "address",
            example = "成都")
    private String address;

    @ApiModelProperty(value = "学生所拥有的动物",
            name = "animals",
            example = "学生所拥有的动物")
    private List<AnimalVO> animals;
}
TeacherVO
package com.frank.hello.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:10
 * @Description: ${todo}
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "TeacherVO", description = "老师类")
public class TeacherVO {

    @ApiModelProperty(value = "老师的编号",
            name = "id",
            example = "1001")
    private int id;

    @ApiModelProperty(value = "老师的姓名",
            name = "name",
            example = "张老师")
    private String name;

    @ApiModelProperty(value = "地址",
            name = "address",
            example = "成都")
    private String address;

    @ApiModelProperty(value = "老师所教的学生",
            name = "students",
            example = "老师所教的学生")
    private List<StudentVO> students;
}
HelloController
package com.frank.hello.web;

import com.frank.hello.dto.Animal;
import com.frank.hello.dto.DataResponse;
import com.frank.hello.dto.Student;
import com.frank.hello.dto.Teacher;
import com.frank.hello.mapper.SourceTargetMapper;
import com.frank.hello.vo.DataResponseVO;
import com.frank.hello.vo.TeacherVO;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 小石潭记
 * @date 2020/7/2 21:12
 * @Description: ${todo}
 */
@RestController
@Api(value = "HelloController", description = "主页")
public class HelloController {

    @GetMapping("/index")
    @ApiOperation(value = "获取接口信息",notes = "获取接口信息",tags = "DataResponseVO",httpMethod = "GET")
    @ApiResponses({//方法返回值的swagger注释
            @ApiResponse(code = 200,message = "成功",response = DataResponseVO.class),
            @ApiResponse(code = 400,message = "用户输入错误",response = DataResponseVO.class),
            @ApiResponse(code = 500,message = "系统内部错误",response = DataResponseVO.class)
    })
    public DataResponseVO index(){
        List<Animal> animals = new ArrayList<>();
        animals.add(new Animal("小一", 1));
        animals.add(new Animal("小二", 2));
        animals.add(new Animal("小三", 3));
        List<Student> students = new ArrayList<>();
        students.add(new Student(1, "小明", "成都", animals));
        Teacher teacher = new Teacher(1, "小梅沙", "四川", students);
        // 这里模拟从第三方接口获取的数据
        DataResponse dataResponse = new DataResponse(200, "成功", teacher);
        // 这里直接将上面的数据拷贝变成DataResponseVO,这里注意一下先compile一下整个项目
        TeacherVO responseVo = SourceTargetMapper.MAPPER.toTarget(dataResponse.getData());
        DataResponseVO dataResponseVO = new DataResponseVO();
        dataResponseVO.setCode(dataResponse.getCode()).setMessage(dataResponse.getMessage()).setData(responseVo);
        return dataResponseVO;
    }
}
HelloApplication
package com.frank.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }

}

After compiling the project, the mapper implementation class will be automatically generated.

http://localhost:8080/swagger-ui.html    Here you can view the request parameters of the corresponding interface, return objects and other information.

project address

Guess you like

Origin blog.csdn.net/qq_33371766/article/details/107114031