分享一个超好用的Java对象比较模块

58 阅读7分钟

Compare 模块设计文档

1. 模块概述

Compare 模块是一个基于 Java 反射机制的对象差异比较组件,提供了灵活的对象属性比较功能,支持自定义比较规则、字段映射和国际化展示。该模块适用于需要记录对象变更、生成变更日志或展示数据差异的场景。

1.1 主要功能特性

  • 基于注解的对象属性比较
  • 支持多种比较规则(字符串映射、枚举映射、注解映射)
  • 支持国际化字段名称展示
  • 支持对象列表比较
  • 支持自定义忽略字段或类
  • 缓存机制提高性能
  • 与 Spring Boot 无缝集成

2. 架构设计

2.1 整体架构

Compare 模块采用了清晰的分层架构设计,主要包含以下几个部分:

  • 注解层:提供 @CompareProperty@CompareIgnore@CompareMapping 等注解,用于标记需要比较的字段和配置比较规则
  • 核心功能层CompareCore 类实现核心的对象比较逻辑
  • 工具层CompareUtil 提供便捷的工具方法,采用单例模式管理 CompareCore 实例
  • 国际化层CompareI18n 提供国际化支持,使比较结果可以根据用户语言进行展示
  • 配置层ComparePropertiesCompareAutoConfiguration 提供自动配置支持
  • 数据模型层CompareNode 用于存储字段的比较信息

2.2 模块结构

├── annotation/              # 注解定义
│   ├── CompareProperty.java  # 标记需要比较的字段
│   ├── CompareIgnore.java    # 标记需要忽略的类或字段
│   └── CompareMapping.java   # 定义映射规则
├── config/                  # 配置相关
│   ├── CompareAutoConfiguration.java  # 自动配置类
│   └── CompareProperties.java         # 配置属性类
├── enums/                   # 枚举定义
│   └── CompareRule.java     # 比较规则枚举
├── CompareCore.java         # 核心比较实现类
├── CompareNode.java         # 比较节点数据模型
├── CompareUtil.java         # 工具类
└── CompareI18n.java         # 国际化支持类

3. 核心类与接口

3.1 CompareCore

CompareCore 是整个模块的核心实现类,负责实现对象和列表的比较功能。

主要方法:
  • compareObj(Object beforeObj, Object afterObj):比较两个对象的差异
  • compareList(List<?> beforeList, List<?> afterList, String mappingKey):基于映射规则比较两个列表
  • compareListByIndex(List<?> beforeList, List<?> afterList):按索引顺序比较两个列表
  • formatCompareDiff(CompareNode before, CompareNode after):将字段差异格式化为字符串
关键特性:
  • 使用反射获取对象属性
  • 支持缓存反射结果提高性能
  • 实现多种比较规则的处理逻辑
  • 支持字段值映射转换

3.2 CompareNode

CompareNode 是一个数据模型类,用于存储字段的比较信息。

主要字段:
  • fieldKey:字段名称
  • fieldValue:字段值
  • fieldDesc:字段描述(支持国际化)

3.3 CompareUtil

CompareUtil 是一个工具类,提供获取 CompareCore 实例的静态方法,采用了单例模式和缓存机制。

主要方法:
  • forClazz(Class<?> clazz):获取指定类对应的 CompareCore 实例
关键特性:
  • 维护 CompareCore 实例池,避免重复创建
  • 提供类和字段的有效性校验

3.4 CompareI18n

CompareI18n 提供国际化支持,负责获取字段的多语言描述。

主要方法:
  • getMessage(String i18nKey):获取指定键的国际化消息
关键特性:
  • 支持从请求头获取语言偏好
  • 支持通过配置文件自定义国际化资源

4. 注解说明

4.1 @CompareProperty

标记一个字段是否需要进行比较,并可配置比较规则。

主要属性:
  • value():字段名称(用于国际化展示)
  • ruleType():映射规则类型(STRENUMMAPPING
  • ruleStr():字符串规则数组,格式如 {"1->男", "2->女"}
  • ruleEnum():枚举规则类
  • ruleMappings():映射规则数组

4.2 @CompareIgnore

标记一个类或字段是否需要被忽略比较。

4.3 @CompareMapping

定义映射规则,用于 @ComparePropertyruleMappings 属性。

主要属性:
  • source():源值
  • target():目标值

5. 配置说明

5.1 CompareProperties

配置类,用于设置模块的相关属性。

主要配置项:
  • primaryLanguage:主语言,默认为 "zh"
  • messageSourceBasename:国际化文件前缀,默认为 "compare"

5.2 CompareAutoConfiguration

自动配置类,用于集成 Spring Boot 应用。

主要功能:
  • 自动加载 CompareProperties 配置
  • 创建 CompareI18n 实例
  • 打印模块启动 Banner

6. 枚举说明

6.1 CompareRule

定义比较规则类型的枚举。

枚举值:
  • STR:使用字符串规则进行比较
  • ENUM:使用枚举规则进行比较
  • MAPPING:使用映射规则进行比较

7. 核心功能实现机制

7.1 对象比较流程

  1. 通过 CompareUtil.forClazz() 获取 CompareCore 实例
  2. 调用 compareObj() 方法比较两个对象
  3. 使用反射获取对象的属性信息
  4. 根据注解配置处理字段值的映射转换
  5. 比较转换后的字段值,生成差异描述
  6. 将所有差异描述合并为最终结果

7.2 列表比较机制

模块支持两种列表比较模式:

  1. 基于映射规则的列表比较:通过指定映射键,根据键值匹配两个列表中的对象进行比较
  2. 按索引顺序的列表比较:直接按列表索引顺序比较对应位置的对象

7.3 国际化处理

  • 通过 CompareI18n 类获取字段的国际化描述
  • 支持从 HTTP 请求头的 Accept-Language 获取用户语言偏好
  • 支持通过配置文件自定义国际化资源

7.4 性能优化

  • 使用静态缓存存储类的字段信息,避免重复反射操作
  • 使用实例池管理 CompareCore 实例,避免重复创建
  • 支持忽略不需要比较的字段或类,减少比较操作

8. 类图描述

classDiagram
    class CompareCore {
        -static final Map<Class<?>, List<Field>> CLAZZ_FIELD_CACHE
        -static final String BLANK_STR
        -static final String BLANK_FILED_VALUE
        -static final String SPLITTER_ARROW
        -static final String DEFUALT_IGNORE_FIELDS[]
        +compareObj(Object beforeObj, Object afterObj)
        +compareList(List<?> beforeList, List<?> afterList, String mappingKey)
        +compareListByIndex(List<?> beforeList, List<?> afterList)
        +formatCompareDiff(CompareNode before, CompareNode after)
        -getCompareNodeMap(T obj)
        -getFieldFromCache(Class<?> objClazz)
        -mapFiledValue(Object fieldValue, Field field)
        -isJdkType(Class<?> clazz)
        +parseStrRuleToMap(String[] input)
        +praseStrToPair(String str)
    }
    
    class CompareNode {
        -String fieldKey
        -Object fieldValue
        -String fieldDesc
        +getFieldKey()
        +setFieldKey(String fieldKey)
        +getFieldValue()
        +setFieldValue(Object fieldValue)
        +getFieldDesc()
        +setFieldDesc(String fieldDesc)
    }
    
    class CompareUtil {
        -static final Map<Class<?>, CompareCore> INSTANCE_POOL
        -CompareUtil()
        +forClazz(Class<?> clazz)
    }
    
    class CompareI18n {
        -static final ResourceBundleMessageSource messageSource
        -static CompareProperties compareProperties
        +setApplicationContext(ApplicationContext applicationContext)
        +getMessage(String i18nKey)
    }
    
    class CompareProperty {
        +String value()
        +CompareRule ruleType()
        +String[] ruleStr()
        +Class<? extends Enum<? extends Function>> ruleEnum()
        +CompareMapping[] ruleMappings()
    }
    
    class CompareIgnore {
    }
    
    class CompareMapping {
        +String source()
        +String target()
    }
    
    class CompareRule {
        <<enumeration>>
        STR
        ENUM
        MAPPING
    }
    
    class CompareProperties {
        -static final String PREFIX
        -String primaryLanguage
        -String messageSourceBasename
        +getPrimaryLanguage()
        +setPrimaryLanguage(String primaryLanguage)
        +getMessageSourceBasename()
        +setMessageSourceBasename(String messageSourceBasename)
    }
    
    class CompareAutoConfiguration {
        +String getDefaultBanner()
        +void postConstruct()
        +CompareI18n compareI18n()
    }
    
    CompareUtil --> CompareCore
    CompareCore --> CompareNode
    CompareCore --> CompareI18n
    CompareProperty --> CompareRule
    CompareProperty --> CompareMapping
    CompareI18n --> CompareProperties
    CompareAutoConfiguration --> CompareI18n
    CompareAutoConfiguration --> CompareProperties

9. 流程图

9.1 对象比较流程

flowchart TB
  A["开始比较"] --> B["获取对象的字段信息"]
  B --> C{"字段是否有@CompareProperty注解"}
  C -->|"否"| E["跳过此字段"]
  C -->|"是"| D{"字段是否有@CompareIgnore注解"}
  D -->|"是"| E
  D -->|"否"| F["处理字段值映射"]
  F --> G{"字段类型是否为JDK基础类型"}
  G -->|"否"| E
  G -->|"是"| H["创建CompareNode"]
  H --> I["比较前后对象的相同字段"]
  I --> J{"字段值是否不同"}
  J -->|"否"| L["跳过此字段"]
  J -->|"是"| K["格式化差异描述"]
  K --> M["继续比较下一个字段"]
  L --> M
  E --> M
  M --> N{"所有字段比较完成"}
  N -->|"否"| B
  N -->|"是"| O["返回比较结果"]

10. 使用示例

10.1 基本使用示例

1. 定义需要比较的类
public class User {
    @CompareProperty("user.id")
    private Long id;
    
    @CompareProperty("user.name")
    private String name;
    
    @CompareProperty(value = "user.status", ruleStr = {"0->禁用", "1->启用"})
    private Integer status;
    
    @CompareIgnore
    private String password;
    
    // getter and setter
}
2. 执行对象比较
// 获取CompareCore实例
CompareCore compareCore = CompareUtil.forClazz(User.class);

// 创建两个用户对象
User beforeUser = new User();
beforeUser.setId(1L);
beforeUser.setName("张三");
beforeUser.setStatus(1);

beforeUser.setPassword("123456");

User afterUser = new User();
afterUser.setId(1L);
afterUser.setName("李四");
afterUser.setStatus(0);

afterUser.setPassword("123456");

// 比较对象差异
String diff = compareCore.compareObj(beforeUser, afterUser);
System.out.println(diff);
// 输出: [用户姓名] 修改前:张三 ——> 修改后:李四
//       [用户状态] 修改前:启用 ——> 修改后:禁用

10.2 列表比较示例

基于映射规则的列表比较
List<User> beforeList = Arrays.asList(beforeUser);
List<User> afterList = Arrays.asList(afterUser);

// 基于id字段比较列表
String diff = compareCore.compareList(beforeList, afterList, "id");
按索引顺序的列表比较
String diff = compareCore.compareListByIndex(beforeList, afterList);

10.3 自定义枚举规则示例

// 定义枚举映射规则
public enum StatusEnum implements Function<Object, String> {
    ENABLED(1, "启用"),
    DISABLED(0, "禁用");
    
    private final Integer code;
    private final String desc;
    
    StatusEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    @Override
    public String apply(Object o) {
        Integer status = (Integer) o;
        for (StatusEnum e : values()) {
            if (e.code.equals(status)) {
                return e.desc;
            }
        }
        return "未知";
    }
}

// 使用枚举规则
public class User {
    @CompareProperty(value = "user.status", ruleType = CompareRule.ENUM, ruleEnum = StatusEnum.class)
    private Integer status;
}

10.4 使用@CompareMapping示例

public class User {
    @CompareProperty(value = "user.status", ruleType = CompareRule.MAPPING, ruleMappings = {
        @CompareMapping(source = "0", target = "禁用"),
        @CompareMapping(source = "1", target = "启用")
    })
    private Integer status;
}

11. 注意事项

  1. 性能考虑

    • 对于频繁比较的对象,模块已实现缓存机制提高性能
    • 对于不需要比较的字段,建议使用 @CompareIgnore 注解忽略
  2. 数据类型支持

    • 目前主要支持 JDK 基础类型的比较
    • 对于复杂对象类型,需要确保正确配置比较规则
  3. 国际化配置

    • 需要创建对应的国际化资源文件(如 compare_zh.properties
    • 字段描述的键名应与 @CompareProperty 注解的 value 属性一致
  4. Spring Boot 集成

    • 模块已实现自动配置,只需添加依赖即可使用
    • 可通过 application.propertiesapplication.yml 自定义配置项

12. 配置项参考

application.properties 文件中可配置以下属性:

# 设置主语言
spring.ai.framework.compare.primary-language=zh

# 设置国际化资源文件前缀
spring.ai.framework.compare.message-source-basename=compare

application.yml 文件中可配置以下属性:

    framework:
      compare:
        primary-language: zh
        message-source-basename: compare

13. 总结

Compare 模块提供了一个灵活、强大的对象差异比较解决方案,通过简单的注解配置,即可实现复杂的对象比较需求。该模块适用于需要记录数据变更、生成操作日志或展示数据差异的场景,与 Spring Boot 框架无缝集成,使用方便,扩展性强。