Springboot使用Mapstruct拷貝對象,集成swagger2

項目需求

通過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   這裏可以查看對應接口的請求參數、返回對象等信息。

項目地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章