什么 XO, 喝酒吗? 哦, DO/DTO/BO/AO/VO 啊, 不知道, 我就知道 XO 能帮我们转换他们, 你说我醉了, 我没醉我还能喝

文章介绍了分层领域模型中的DO、DTO、BO、AO和VO等对象类型及其应用场景,强调了它们在职责分离、代码复用和解耦方面的重要性。在用户注册场景中展示了对象转换的过程。MapStruct作为编译时代码生成库,简化了对象之间的转换,提高了代码的简洁性和性能。
摘要由CSDN通过智能技术生成

原来平淡的生活,就是爱情最好的礼物

分层领域模型规约:

DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
BO(Business Object):业务对象。由 Service 层输出的封装业务逻辑的对象。
AO(Application Object):应用对象。在 Web 层与 Service 层之间抽象的复用对象模型,
极为贴近展示层,复用度不高。
VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止
使用 Map 类来传输。
现在你是一个高级java工程师, 请你帮我解答一下
在开发springboot web 应用的时候, 我们经常每层用一种 Object, 这样做是为什么, 有什么优点吗?

加这么多 VO AO BO… 做什么

在开发 Spring Boot Web 应用时,我们经常为每层使用一种 Object 类型(如 DO、DTO、BO、AO、VO 等),这种分层领域模型规约具有以下优点:

  1. 职责分离:将不同层次的逻辑和数据处理分离,使每个层次的职责更加明确。这有助于降低系统的复杂性,提高可维护性和可读性。

  2. 代码复用:通过将各层的逻辑和数据处理分离,可以实现不同层次间的代码复用,避免代码冗余和重复编写。

  3. 解耦:分层领域模型有助于实现各层之间的解耦。当某一层的实现需要修改时,只需关注当前层的代码,而不会影响到其他层次。这有助于提高代码的灵活性和可扩展性。

  4. 易于测试:分层领域模型有助于编写单元测试和集成测试,可以针对每一层进行独立测试,提高测试效率。

  5. 提高团队协作效率:当团队成员对各层的职责和规约有清晰的认识时,可以更好地分工合作,降低开发过程中的沟通成本,提高团队的协作效率。

总之,采用分层领域模型规约可以使项目结构更加清晰、易于维护和扩展,有利于提高代码质量和开发效率。

各自的应用场景

以下是一个简单的 Spring Boot Web 应用的例子,展示了分层领域模型中每一个 Object 的应用场景:

以一个用户管理系统为例,我们有一个 User 实体,涉及到的 Object 类型包括 DO、DTO、BO、AO 和 VO。在这个例子中,我们将处理用户注册和查询用户信息的功能。

  1. DO(Data Object)
// UserDO.java
public class UserDO {
    private Long id;
    private String username;
    private String password;
    private String email;
    // getter 和 setter 省略
}

UserDO 对应数据库中的用户表,表示存储在数据库中的用户数据。它通过 DAO 层(Data Access Object 层)与数据库进行交互。

  1. DTO(Data Transfer Object)
// UserDTO.java
public class UserDTO {
    private String username;
    private String password;
    private String email;
    // getter 和 setter 省略
}

以下是一个简单的 Spring Boot Web 应用的例子,展示了分层领域模型中每一个 Object 的应用场景:

以一个用户管理系统为例,我们有一个 User 实体,涉及到的 Object 类型包括 DO、DTO、BO、AO 和 VO。在这个例子中,我们将处理用户注册和查询用户信息的功能。

  1. DO(Data Object)

javaCopy code

// UserDO.java public class UserDO { private Long id; private String username; private String password; private String email; // getter 和 setter 省略 }

UserDO 对应数据库中的用户表,表示存储在数据库中的用户数据。它通过 DAO 层(Data Access Object 层)与数据库进行交互。

  1. DTO(Data Transfer Object)

javaCopy code

// UserDTO.java public class UserDTO { private String username; private String password; private String email; // getter 和 setter 省略 }

UserDTO 是 Service 层或 Manager 层向外传输的数据对象。它用于在 Service 层与外部(如 Controller 层)传递数据,通常不包含敏感信息(如密码哈希)。

  1. BO(Business Object)
// UserBO.java
public class UserBO {
    private String username;
    private String email;
    // getter 和 setter 省略
}

UserBO 是 Service 层输出的业务对象,包含业务逻辑处理后的数据。例如,在本例中,我们可能需要对用户的密码进行加密,而加密后的数据将保存在 UserBO 中。

  1. AO(Application Object)
// UserAO.java
public class UserAO {
    private String username;
    private String email;
    // getter 和 setter 省略
}

UserAO 是 Web 层与 Service 层之间的抽象复用对象模型。它很接近展示层,主要用于在 Controller 层与 Service 层之间传递数据。在本例中,UserAO 可能与 UserBO 类似。

  1. VO(View Object)
// UserVO.java
public class UserVO {
    private String username;
    private String email;
    // getter 和 setter 省略
}

UserVO 是显示层对象,通常由 Web 层向模板渲染引擎传递。它包含用于展示的数据,可以根据页面需求定制。例如,本例中 UserVO 可能仅包含用户名和电子邮件地址。

通过以上示例代码,可以看到分层领域模型中每一个 Object 的应用场景。每个 Object 类型在不同层之间起到数据传递和处理的作用,有助于实现代码的职责分离、复用和解耦。

使用了所有的 Object 类型(VO、AO、BO、DTO 和 DO)换一个场景: 用户注册

  1. 用户在前端页面填写注册信息(用户名、密码和邮箱),点击提交。

  2. 前端将数据封装成一个 JSON 对象,通过 AJAX 请求发送到后端接口。后端接口接收到请求后,首先将数据转换为 UserVO。

// UserVO.java
public class UserVO {
    private String username;
    private String password;
    private String email;
    // getter 和 setter 省略
}

  1. 在 Controller 层,将 UserVO 转换为 UserAO。
// UserController.java
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody UserVO userVO) {
    UserAO userAO = convertVOToAO(userVO);
    userService.register(userAO);
    return ResponseEntity.ok("注册成功");
}

private UserAO convertVOToAO(UserVO userVO) {
    UserAO userAO = new UserAO();
    userAO.setUsername(userVO.getUsername());
    userAO.setPassword(userVO.getPassword());
    userAO.setEmail(userVO.getEmail());
    return userAO;
}

  1. 在 Service 层,处理业务逻辑。将 UserAO 转换为 UserBO,对用户密码进行加密。
// UserService.java
public void register(UserAO userAO) {
    UserBO userBO = convertAOToBO(userAO);
    String encryptedPassword = passwordEncoder.encode(userBO.getPassword());
    userBO.setPassword(encryptedPassword);
    userDAO.insertUser(convertBOToDTO(userBO));
}

private UserBO convertAOToBO(UserAO userAO) {
    UserBO userBO = new UserBO();
    userBO.setUsername(userAO.getUsername());
    userBO.setPassword(userAO.getPassword());
    userBO.setEmail(userAO.getEmail());
    return userBO;
}

  1. 将 UserBO 转换为 UserDTO,准备传递给 DAO 层。
// UserService.java
private UserDTO convertBOToDTO(UserBO userBO) {
    UserDTO userDTO = new UserDTO();
    userDTO.setUsername(userBO.getUsername());
    userDTO.setPassword(userBO.getPassword());
    userDTO.setEmail(userBO.getEmail());
    return userDTO;
}

  1. 在 DAO 层,将 UserDTO 转换为 UserDO,然后将数据插入数据库。
// UserDAO.java
public void insertUser(UserDTO userDTO) {
    UserDO userDO = convertDTOToDO(userDTO);
    // 插入数据库操作
}

private UserDO convertDTOToDO(UserDTO userDTO) {
    UserDO userDO = new UserDO();
    userDO.setUsername(userDTO.getUsername());
    userDO.setPassword(userDTO.getPassword());
    userDO.setEmail(userDTO.getEmail());
    return userDO;
}

在这个用户注册的场景中,我们使用了所有的 Object 类型,通过对象的转换,将数据从前端页面传递到数据库操作。这种分层领域模型有助于实现代码的职责分离、复用和解耦。

转换类型

分层领域模型对象之间如何转换才更加简洁而不是使用new

为了简化分层领域模型对象之间的转换,可以使用一些对象映射库,如 ModelMapper、MapStruct 或 Dozer。这些库可以自动完成对象之间的属性映射,使代码更简洁。下面分别介绍 ModelMapper 和 MapStruct 的使用方法。

  1. ModelMapper

首先,添加 ModelMapper 的依赖:

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.4.5</version>
</dependency>

然后,创建一个 ModelMapper 的实例,并使用它进行对象转换:

// UserService.java
ModelMapper modelMapper = new ModelMapper();

public void register(UserAO userAO) {
    UserBO userBO = modelMapper.map(userAO, UserBO.class);
    // ...
}

  1. MapStruct

首先,添加 MapStruct 的依赖:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.2.Final</version>
</dependency>

然后,定义一个映射接口,并使用 @Mapper 注解:

// UserMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserBO userAOtoUserBO(UserAO userAO);
    UserDTO userBOtoUserDTO(UserBO userBO);
    UserDO userDTOtoUserDO(UserDTO userDTO);
}

最后,在需要进行对象转换的地方,使用定义好的映射接口:

// UserService.java
public void register(UserAO userAO) {
    UserBO userBO = UserMapper.INSTANCE.userAOtoUserBO(userAO);
    // ...
}

通过使用对象映射库,可以简化分层领域模型对象之间的转换,使代码更加简洁和易于维护。

总结

为了实现分层领域模型对象之间的简洁且高效的转换,可以使用 MapStruct。MapStruct 是一个高性能的 Java 代码生成库,可以自动生成对象之间属性映射的代码。与其他对象映射库(如 ModelMapper)相比,MapStruct 在运行时性能更高,因为它是基于编译时生成的代码,而不是运行时的反射。

MapStruct + java 泛型转换

以下是一个使用 MapStruct 和 Java 泛型实现的转换工具类示例。通过这个工具类,我们可以更方便地实现分层领域模型对象之间的转换。

首先,为了使用泛型,我们需要创建一个基础映射器接口 BaseMapper,然后在具体的映射器接口(如 UserMapper)中继承它。

  1. 创建 BaseMapper 接口
// BaseMapper.java
import org.mapstruct.MapperConfig;

@MapperConfig
public interface BaseMapper<S, T> {
    T map(S source);
}

BaseMapper 接口定义了一个泛型方法 map,用于将源对象 S 映射到目标对象 T。

  1. 创建 UserMapper 接口

接下来,创建 UserMapper 接口,并继承 BaseMapper 接口。将 UserMapper 接口中的方法替换为继承自 BaseMapper 的泛型方法。

// UserMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper(config = BaseMapper.class)
public interface UserMapper extends BaseMapper<UserAO, UserBO>,
                                      BaseMapper<UserBO, UserDTO>,
                                      BaseMapper<UserDTO, UserDO> {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
}

  1. 创建转换工具类

创建一个名为 ConvertUtils 的工具类,该类包含一个静态泛型方法 convert,用于将源对象转换为目标对象。

// ConvertUtils.java
public class ConvertUtils {
    public static <S, T> T convert(S source, BaseMapper<S, T> mapper) {
        return mapper.map(source);
    }
}

现在,我们可以使用 ConvertUtils 工具类方便地进行对象转换。

// UserService.java
public void register(UserAO userAO) {
    UserBO userBO = ConvertUtils.convert(userAO, UserMapper.INSTANCE);
    // ...
}

通过使用 MapStruct 结合 Java 泛型,我们创建了一个简洁且高效的转换工具类。这个工具类可以方便地实现分层领域模型对象之间的转换,同时保持了 MapStruct 的编译时代码生成优势,避免了运行时反射的性能开销。

编译期间帮我们生成对应的转换类

MapStruct 是一个编译时代码生成库,它会根据你定义的映射器接口(如 UserMapper)生成实现类,这个实现类包含了映射方法的具体实现。生成的映射代码将源对象的属性值复制到目标对象的相应属性中。生成的代码具有很高的性能,因为它不依赖于运行时的反射机制。

以我们之前定义的 UserMapper 为例,MapStruct 会生成一个名为 UserMapperImpl 的实现类,该类包含了 UserMapper 接口定义的方法的实现。这个实现类大致长这样:


// UserMapperImpl.java (由 MapStruct 自动生成)
public class UserMapperImpl implements UserMapper {

    @Override
    public UserBO map(UserAO source) {
        if (source == null) {
            return null;
        }

        UserBO target = new UserBO();

        target.setUsername(source.getUsername());
        target.setPassword(source.getPassword());
        target.setEmail(source.getEmail());

        return target;
    }

    @Override
    public UserDTO map(UserBO source) {
        if (source == null) {
            return null;
        }

        UserDTO target = new UserDTO();

        target.setUsername(source.getUsername());
        target.setPassword(source.getPassword());
        target.setEmail(source.getEmail());

        return target;
    }

    @Override
    public UserDO map(UserDTO source) {
        if (source == null) {
            return null;
        }

        UserDO target = new UserDO();

        target.setUsername(source.getUsername());
        target.setPassword(source.getPassword());
        target.setEmail(source.getEmail());

        return target;
    }
}

在这个示例中,MapStruct 自动生成了一个名为 UserMapperImpl 的类,实现了 UserMapper 接口。在这个实现类中,MapStruct 根据源对象和目标对象的属性名称和类型生成了映射代码。例如,当 UserAO 的 username 属性值被复制到 UserBO 的 username 属性时,代码会类似于 target.setUsername(source.getUsername());

由于 MapStruct 在编译时生成了这些映射代码,因此在运行时不需要使用反射,从而提高了性能。同时,生成的代码易于阅读和调试,有利于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值