一、前言
在设计 REST API 时,我们总是需要将内部实体转换为更通用的外部 DTO,并将其作为响应数据返回给客户端。有多种方法可以将 Spring Boot应用程序的内部实体转换为外部 DTO。在本篇文章中,我们将介绍不同的转换。
下面是一些常用的转换库:
- Dozer
该项目目前不活跃,并且很可能在未来被弃用。 - ModelMapper
一个智能对象映射库,可自动将对象相互映射。它采用基于约定的方法,同时提供简单、重构安全的应用程序接口(API)来处理特定用例。 - MapStruct
它是一个代码生成器,它基于约定优于配置的方法,极大地简化了 Java Bean 类型之间的映射实现。生成的映射代码使用简单的方法调用,因此执行速度快、类型安全且易于理解。 - Orika
是一个 Java Bean 映射框架,它(除其他功能外)可以递归地将数据从一个对象复制到另一个对象。它在开发多层应用程序时非常有用。 - Selma
它一方面是一个注解处理器,能够在编译时生成处理字段到字段映射的 Java 代码;另一方面,它是一个运行时库,用于实例化和调用生成的映射器。
本篇文章,我们将介绍ModelMapper的使用。
二、项目实践
1.引入依赖
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.1</version>
</dependency>
在Spring环境下,建议配置如下的Bean以方便我们进行转换。
package com.example.springbootdemo.config;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
2.准备实体类和DTO类
package com.example.springbootdemo.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class MyOrder {
private String orderNumber;
private double orderAmount;
private double tax;
private Customer customer;
private Address shippingAddress;
}
package com.example.springbootdemo.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private String userId;
private String firstName;
private String lastName;
private String email;
}
package com.example.springbootdemo.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String addressLine1;
private String street;
private String city;
private String postalCode;
}
下面是我们希望进行转换的DTO对象。
package com.example.springbootdemo.dto;
import com.example.springbootdemo.domain.Address;
import com.example.springbootdemo.domain.Customer;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OrderDto {
public String orderNumber;
private double orderAmount;
private double tax;
private Customer customer;
private Address shippingAddress;
}
接下来,我们将围绕上面定义的类进行讲解。
再写个静态的Service
package com.example.springbootdemo.service;
import com.example.springbootdemo.domain.Address;
import com.example.springbootdemo.domain.Customer;
import com.example.springbootdemo.domain.MyOrder;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public MyOrder getOrder() {
MyOrder order = new MyOrder();
order.setOrderAmount(266.6D);
order.setTax(1.5D);
order.setOrderNumber("PACK-00001");
Customer customer = new Customer("U00001", "Pack", "AKF", "66666@qq.com");
order.setCustomer(customer);
Address address = new Address("XJ0001", "HTYJ", "WLMQ", "830000");
order.setShippingAddress(address);
return order;
}
}
3.定义门面Facade
在本示例中,我将使用 Facade 层来简化服务层,如下定义:
package com.example.springbootdemo.service;
import com.example.springbootdemo.domain.MyOrder;
import com.example.springbootdemo.domain.Order;
import com.example.springbootdemo.dto.OrderDto;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Component;
@Component
public class OrderFacade {
private final ModelMapper modelMapper;
public OrderFacade(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public OrderDto convert(MyOrder order) {
return convertToOrderDto(order);
}
private OrderDto convertToOrderDto(MyOrder order) {
return this.modelMapper.map(order, OrderDto.class);
}
}
4.定义Controller
package com.example.springbootdemo.controller;
import com.example.springbootdemo.domain.MyOrder;
import com.example.springbootdemo.dto.OrderDto;
import com.example.springbootdemo.service.OrderFacade;
import com.example.springbootdemo.service.OrderService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderFacade orderFacade;
private final OrderService orderService;
public OrderController(OrderFacade orderFacade, OrderService orderService) {
this.orderFacade = orderFacade;
this.orderService = orderService;
}
@GetMapping("/test")
public ResponseEntity<OrderDto> getOrder() {
MyOrder order = this.orderService.getOrder();
OrderDto orderDto = this.orderFacade.convert(order);
return ResponseEntity.status(HttpStatus.OK.value()).body(orderDto);
}
}
在上面的接口中,我们模拟了一个REST API的完整处理过程。在该接口中调用Facade将Order实体转换为对应的DTO响应给客户端。访问接口:
正确的映射对应的属性。