Springboot使用Mapstruct拷贝对象,集成swagger2

    技术2024-06-17  80

    实际需求

    通过feign获取第三方接口,将结果映射成dto,不过dto里面的对象的属性接收的值命名可能不规范(全是大写等,不是驼峰命令等方式),所以才会用vo来接收dto的值。

    如果只是对象copy,可以使用BeanUtils.copyProperties进行对象之间的属性赋值(浅拷贝)

    但是如果对象里面还有对象和集合之类的,这样就copy失败了,这里就可以采用Mapstruct工具类进行深拷贝。

    Mapstruct实现步骤

    1.引入相关依赖(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.引入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.新增mapper接口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.调用接口方式,传入源数据,生成目标数据

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

    集成mapstruct可能遇到的坑

    1)查看资料如果引入swagger,在引入mapstruct,需要在swagger依赖下添加<exclusions>如下文所示</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)mapstruct和lombok同时引入,可能会出现生成不了get、set方法(compile生成接口的实现类),在plugin里面添加<annotationProcessorPaths>如下文所示</annotationProcessorPaths>

    注意lombok的版本不要太低,我这里是:

    <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)最后一点我被坑惨了。

    注意DTO和VO里面的属性名称一定要一致,返回的对象可以不一样,但是属性名一定要一致,图中的list里面的对象的属性名也要保持一致,不然compile不会生成对应的get、set这样拷贝的对象就不正确,没有拷贝完所有属性,这个地方我被坑惨了(实际项目中DTO对象嵌套的层数太多了,没有逐一去查看属性是否一致。)

    4)如果对象不复杂,可以通过下面的方式进行mapping一一映射

    /** * 这个方法就是用于实现对象属性复制的方法 * * @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);

    目录结构:

    application.properties文件暂未配置内容

    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); } }

    编译项目之后,会自动生成mapper的实现类。

    http://localhost:8080/swagger-ui.html   这里可以查看对应接口的请求参数、返回对象等信息。

    项目地址

    Processed: 0.014, SQL: 9