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