不止于 API 调用:解锁 Java 工具类设计的三重境界 —— 可复用性、线程安全与性能优化

在 Java 开发的世界里,工具类就像瑞士军刀,看似简单却蕴含着开发者的设计智慧。每个项目中都充斥着各种 Utils 类,但能真正做到 "一次编写,到处复用" 且兼顾线程安全与性能的却寥寥无几。本文将带你超越简单的 API 封装,深入探索 Java 工具类设计的精髓所在,从可复用性构建、线程安全保障到性能极致优化,全方位剖析如何打造经得起考验的高质量工具类。

一、工具类设计的基石:理解本质与原则

工具类(Utility Class)是 Java 开发中最常见的类之一,它通常包含一组静态方法,用于提供通用功能。但优秀的工具类绝不仅仅是静态方法的简单堆砌,而是需要遵循特定的设计原则和模式。

1.1 工具类的本质特征

一个设计良好的工具类应具备以下特征:

  • 无状态性:不保存实例变量,所有方法都基于输入参数完成操作

  • 不可实例化:通过私有构造方法防止被实例化

  • 功能单一性:专注于某一领域的功能实现

  • 高内聚性:相关功能紧密结合,减少外部依赖

    /**

    • 字符串处理工具类

    • 遵循工具类设计基本原则:不可实例化、无状态、功能单一

    • @author ken
      */
      @Slf4j
      public final class StringProcessUtils {

      /**

      • 私有构造方法,防止类被实例化
        */
        private StringProcessUtils() {
        // 抛出异常防止通过反射实例化
        throw new AssertionError("Utility class should not be instantiated");
        }

      // 工具方法实现...
      }

1.2 工具类设计的核心原则

工具类设计需遵循以下核心原则,这些原则将指导我们后续的可复用性、线程安全和性能优化实践:

  • 单一职责原则:一个工具类应专注于解决某一领域的问题,如日期处理、加密解密等
  • 最小知识原则:减少与其他类的交互,降低耦合度
  • 不可变性原则:避免维护内部状态,确保方法调用的结果只依赖于输入参数
  • 防御性编程原则:对输入参数进行校验,确保方法在各种情况下的稳定性
  • 命名直观原则:方法名应清晰表达其功能,让使用者一目了然

二、可复用性设计:让工具类真正 "通用"

可复用性是衡量工具类质量的首要标准。一个高可复用性的工具类能够在不同项目、不同场景下被轻松使用,显著提高开发效率。

2.1 接口与实现分离:隐藏细节,暴露契约

通过接口定义工具类的功能契约,具体实现可以灵活变化,这是提高可复用性的关键策略。

复制代码
/**
 * 数据加密工具接口
 * 定义加密解密功能契约
 * @author ken
 */
public interface EncryptionUtils {
    
    /**
     * 对字符串进行加密
     * @param content 待加密的内容
     * @param key 加密密钥
     * @return 加密后的字符串
     * @throws EncryptionException 加密过程中发生的异常
     */
    String encrypt(String content, String key) throws EncryptionException;
    
    /**
     * 对字符串进行解密
     * @param encryptedContent 加密后的内容
     * @param key 解密密钥
     * @return 解密后的原始字符串
     * @throws EncryptionException 解密过程中发生的异常
     */
    String decrypt(String encryptedContent, String key) throws EncryptionException;
}

/**
 * AES加密算法实现类
 * @author ken
 */
@Slf4j
public final class AesEncryptionUtils implements EncryptionUtils {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
    
    private AesEncryptionUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    @Override
    public String encrypt(String content, String key) throws EncryptionException {
        StringUtils.hasText(content, "加密内容不能为空");
        StringUtils.hasText(key, "加密密钥不能为空");
        
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            log.error("AES加密失败", e);
            throw new EncryptionException("AES加密失败: " + e.getMessage(), e);
        }
    }
    
    @Override
    public String decrypt(String encryptedContent, String key) throws EncryptionException {
        StringUtils.hasText(encryptedContent, "解密内容不能为空");
        StringUtils.hasText(key, "解密密钥不能为空");
        
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedContent));
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("AES解密失败", e);
            throw new EncryptionException("AES解密失败: " + e.getMessage(), e);
        }
    }
}

/**
 * 加密工具工厂类
 * 提供不同加密算法的实例
 * @author ken
 */
public final class EncryptionUtilsFactory {
    
    private EncryptionUtilsFactory() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 获取AES加密工具实例
     * @return AES加密工具实例
     */
    public static EncryptionUtils getAesEncryptionUtils() {
        return AesEncryptionUtils.INSTANCE;
    }
    
    // 可以添加更多加密算法的获取方法
}

2.2 配置化与参数化:适应不同场景

将工具类中可能变化的部分通过参数或配置文件进行控制,而不是硬编码,能极大提高工具类的灵活性和复用性。

复制代码
/**
 * 日期处理工具类
 * 支持多种日期格式,通过参数化配置适应不同场景
 * @author ken
 */
@Slf4j
public final class DateUtils {
    
    // 默认日期格式
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    
    private DateUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 将日期转换为字符串
     * @param date 日期对象,不能为null
     * @param pattern 日期格式,若为null则使用默认格式
     * @return 格式化后的日期字符串
     */
    public static String format(Date date, String pattern) {
        ObjectUtils.requireNonNull(date, "日期对象不能为空");
        String usePattern = StringUtils.hasText(pattern) ? pattern : DEFAULT_DATE_FORMAT;
        
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(usePattern);
            return sdf.format(date);
        } catch (IllegalArgumentException e) {
            log.error("日期格式化失败,格式: {}", usePattern, e);
            throw new InvalidDateFormatException("无效的日期格式: " + usePattern, e);
        }
    }
    
    /**
     * 将字符串转换为日期
     * @param dateStr 日期字符串,不能为null或空
     * @param pattern 日期格式,若为null则使用默认格式
     * @return 解析后的日期对象
     */
    public static Date parse(String dateStr, String pattern) {
        StringUtils.hasText(dateStr, "日期字符串不能为空");
        String usePattern = StringUtils.hasText(pattern) ? pattern : DEFAULT_DATE_FORMAT;
        
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(usePattern);
            return sdf.parse(dateStr);
        } catch (ParseException e) {
            log.error("日期解析失败,字符串: {}, 格式: {}", dateStr, usePattern, e);
            throw new DateParseException("日期解析失败: " + dateStr, e);
        }
    }
    
    /**
     * 使用预设的格式转换日期
     * @param date 日期对象
     * @param formatType 格式类型,如"date"或"datetime"
     * @return 格式化后的日期字符串
     */
    public static String formatWithPreset(Date date, String formatType) {
        String pattern = "datetime".equals(formatType) ? DEFAULT_DATETIME_FORMAT : DEFAULT_DATE_FORMAT;
        return format(date, pattern);
    }
}

2.3 异常处理标准化:提升容错能力

统一的异常处理机制能让工具类在面对错误时表现得更加健壮,同时也方便使用者进行异常处理。

复制代码
/**
 * 工具类通用异常基类
 * 所有工具类抛出的异常都应继承此类
 * @author ken
 */
public class UtilityException extends RuntimeException {
    
    /**
     * 构造函数
     * @param message 异常信息
     */
    public UtilityException(String message) {
        super(message);
    }
    
    /**
     * 构造函数
     * @param message 异常信息
     * @param cause 原始异常
     */
    public UtilityException(String message, Throwable cause) {
        super(message, cause);
    }
}

/**
 * 日期解析异常
 * @author ken
 */
public class DateParseException extends UtilityException {
    
    public DateParseException(String message) {
        super(message);
    }
    
    public DateParseException(String message, Throwable cause) {
        super(message, cause);
    }
}

/**
 * 无效日期格式异常
 * @author ken
 */
public class InvalidDateFormatException extends UtilityException {
    
    public InvalidDateFormatException(String message) {
        super(message);
    }
    
    public InvalidDateFormatException(String message, Throwable cause) {
        super(message, cause);
    }
}

2.4 通用验证工具类实战

下面是一个综合应用了上述原则的通用验证工具类,它具有高度的可复用性:

复制代码
/**
 * 通用数据验证工具类
 * 提供常见数据类型的验证功能
 * @author ken
 */
@Slf4j
public final class ValidationUtils {
    
    // 邮箱正则表达式
    private static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
    // 手机号正则表达式(中国大陆)
    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
    // 身份证号正则表达式(18位)
    private static final String ID_CARD_REGEX = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
    
    private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
    private static final Pattern PHONE_PATTERN = Pattern.compile(PHONE_REGEX);
    private static final Pattern ID_CARD_PATTERN = Pattern.compile(ID_CARD_REGEX);
    
    private ValidationUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 验证邮箱格式
     * @param email 邮箱地址
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isEmail(String email) {
        if (!StringUtils.hasText(email)) {
            return false;
        }
        return EMAIL_PATTERN.matcher(email).matches();
    }
    
    /**
     * 验证手机号格式
     * @param phone 手机号
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isPhone(String phone) {
        if (!StringUtils.hasText(phone)) {
            return false;
        }
        return PHONE_PATTERN.matcher(phone).matches();
    }
    
    /**
     * 验证身份证号格式
     * @param idCard 身份证号
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isIdCard(String idCard) {
        if (!StringUtils.hasText(idCard)) {
            return false;
        }
        return ID_CARD_PATTERN.matcher(idCard).matches();
    }
    
    /**
     * 验证字符串长度是否在指定范围内
     * @param str 待验证字符串
     * @param min 最小长度(包含)
     * @param max 最大长度(包含)
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isLengthBetween(String str, int min, int max) {
        if (str == null) {
            return min <= 0 && 0 <= max;
        }
        int length = str.length();
        return length >= min && length <= max;
    }
    
    /**
     * 验证集合大小是否在指定范围内
     * @param collection 待验证集合
     * @param min 最小大小(包含)
     * @param max 最大大小(包含)
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isCollectionSizeBetween(Collection<?> collection, int min, int max) {
        if (CollectionUtils.isEmpty(collection)) {
            return min <= 0 && 0 <= max;
        }
        int size = collection.size();
        return size >= min && size <= max;
    }
    
    /**
     * 验证对象是否为指定类型或其子类型
     * @param obj 待验证对象
     * @param clazz 目标类型
     * @return 验证通过返回true,否则返回false
     */
    public static boolean isInstanceOf(Object obj, Class<?> clazz) {
        ObjectUtils.requireNonNull(clazz, "目标类型不能为空");
        return obj != null && clazz.isInstance(obj);
    }
    
    /**
     * 执行验证并在失败时抛出异常
     * @param condition 验证条件
     * @param message 验证失败时的异常信息
     * @throws ValidationException 验证失败时抛出
     */
    public static void validate(boolean condition, String message) {
        if (!condition) {
            log.error("验证失败: {}", message);
            throw new ValidationException(message);
        }
    }
}

三、线程安全:构建并发环境下的可靠工具

在多线程环境下,工具类的线程安全至关重要。一个线程不安全的工具类可能会导致数据错乱、程序崩溃等严重问题。

3.1 线程安全的根源与解决方案

线程安全问题通常源于共享状态的修改。工具类实现线程安全的主要策略有:

  • 无状态设计:工具类不保存任何状态信息,所有方法仅依赖输入参数
  • 不可变对象:使用 final 修饰字段,确保对象创建后状态不再改变
  • 同步机制:使用 synchronized 或 Lock 保证临界区代码的原子性
  • ThreadLocal 隔离:每个线程拥有独立的变量副本,避免共享
  • 原子操作:使用 java.util.concurrent.atomic 包下的原子类

3.2 从线程不安全到线程安全的改造

以日期处理工具类为例,展示如何将线程不安全的实现改造为线程安全的版本:

复制代码
/**
 * 线程不安全的日期工具类示例
 * 问题在于SimpleDateFormat是非线程安全的
 * @author ken
 */
@Slf4j
@Deprecated
public final class UnsafeDateUtils {
    
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    private UnsafeDateUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 格式化日期
     * 此方法在多线程环境下会出现线程安全问题
     */
    public static String format(Date date) {
        ObjectUtils.requireNonNull(date, "日期对象不能为空");
        return SDF.format(date);
    }
}

/**
 * 线程安全的日期工具类(方案一:每次创建新实例)
 * 优点:简单直接
 * 缺点:频繁创建对象可能影响性能
 * @author ken
 */
@Slf4j
public final class SafeDateUtilsV1 {
    
    private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    
    private SafeDateUtilsV1() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 格式化日期
     * 每次调用创建新的SimpleDateFormat实例,保证线程安全
     */
    public static String format(Date date) {
        return format(date, DEFAULT_PATTERN);
    }
    
    /**
     * 格式化日期
     * @param date 日期对象
     * @param pattern 日期格式
     * @return 格式化后的字符串
     */
    public static String format(Date date, String pattern) {
        ObjectUtils.requireNonNull(date, "日期对象不能为空");
        StringUtils.hasText(pattern, "日期格式不能为空");
        
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        return sdf.format(date);
    }
}

/**
 * 线程安全的日期工具类(方案二:ThreadLocal)
 * 优点:性能好,每个线程只创建一次对象
 * 缺点:实现稍复杂,需要注意内存泄漏
 * @author ken
 */
@Slf4j
public final class SafeDateUtilsV2 {
    
    private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    
    // 使用ThreadLocal存储SimpleDateFormat,每个线程一个实例
    private static final ThreadLocal<SimpleDateFormat> SDF_THREAD_LOCAL = ThreadLocal.withInitial(
        () -> new SimpleDateFormat(DEFAULT_PATTERN)
    );
    
    private SafeDateUtilsV2() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 格式化日期(默认格式)
     */
    public static String format(Date date) {
        return format(date, DEFAULT_PATTERN);
    }
    
    /**
     * 格式化日期(指定格式)
     */
    public static String format(Date date, String pattern) {
        ObjectUtils.requireNonNull(date, "日期对象不能为空");
        StringUtils.hasText(pattern, "日期格式不能为空");
        
        SimpleDateFormat sdf = SDF_THREAD_LOCAL.get();
        // 修改格式
        sdf.applyPattern(pattern);
        String result = sdf.format(date);
        
        // 清除ThreadLocal,防止内存泄漏
        SDF_THREAD_LOCAL.remove();
        
        return result;
    }
}

/**
 * 线程安全的日期工具类(方案三:使用Java 8的DateTimeFormatter)
 * 优点:最简单,性能好,推荐使用
 * @author ken
 */
@Slf4j
public final class SafeDateUtilsV3 {
    
    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    private SafeDateUtilsV3() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 格式化日期(默认格式)
     */
    public static String format(LocalDateTime dateTime) {
        ObjectUtils.requireNonNull(dateTime, "日期时间对象不能为空");
        return DEFAULT_FORMATTER.format(dateTime);
    }
    
    /**
     * 格式化日期(指定格式)
     */
    public static String format(LocalDateTime dateTime, String pattern) {
        ObjectUtils.requireNonNull(dateTime, "日期时间对象不能为空");
        StringUtils.hasText(pattern, "日期格式不能为空");
        
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return formatter.format(dateTime);
    }
}

3.3 线程安全的 ID 生成工具类

ID 生成器是分布式系统中常用的工具,需要保证在高并发环境下的线程安全和唯一性:

复制代码
/**
 * 分布式ID生成工具类
 * 基于雪花算法(Snowflake)实现
 * 保证高并发环境下的线程安全和ID唯一性
 * @author ken
 */
@Slf4j
public final class IdGeneratorUtils {
    
    /**
     * 开始时间戳 (2023-01-01 00:00:00)
     */
    private static final long START_TIMESTAMP = 1672502400000L;
    
    /**
     * 机器ID所占的位数
     */
    private static final int WORKER_ID_BITS = 5;
    
    /**
     * 数据中心ID所占的位数
     */
    private static final int DATA_CENTER_ID_BITS = 5;
    
    /**
     * 支持的最大机器ID,结果是31 (0b11111)
     */
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    
    /**
     * 支持的最大数据中心ID,结果是31 (0b11111)
     */
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
    
    /**
     * 序列在ID中占的位数
     */
    private static final int SEQUENCE_BITS = 12;
    
    /**
     * 机器ID向左移12位
     */
    private static final int WORKER_ID_SHIFT = SEQUENCE_BITS;
    
    /**
     * 数据中心ID向左移17位(12+5)
     */
    private static final int DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    
    /**
     * 时间戳向左移22位(5+5+12)
     */
    private static final int TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    
    /**
     * 生成序列的掩码,这里为4095 (0b111111111111)
     */
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    
    /**
     * 机器ID
     */
    private final long workerId;
    
    /**
     * 数据中心ID
     */
    private final long dataCenterId;
    
    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;
    
    /**
     * 上次生成ID的时间戳
     */
    private long lastTimestamp = -1L;
    
    /**
     * 单例实例
     */
    private static volatile IdGeneratorUtils instance;
    
    /**
     * 私有构造函数
     * @param workerId 机器ID (0~31)
     * @param dataCenterId 数据中心ID (0~31)
     */
    private IdGeneratorUtils(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
        log.info("初始化ID生成器,workerId: {}, dataCenterId: {}", workerId, dataCenterId);
    }
    
    /**
     * 获取单例实例
     * 双重检查锁定确保线程安全
     * @param workerId 机器ID
     * @param dataCenterId 数据中心ID
     * @return 单例实例
     */
    public static IdGeneratorUtils getInstance(long workerId, long dataCenterId) {
        if (instance == null) {
            synchronized (IdGeneratorUtils.class) {
                if (instance == null) {
                    instance = new IdGeneratorUtils(workerId, dataCenterId);
                }
            }
        }
        return instance;
    }
    
    /**
     * 生成下一个ID
     * 同步方法确保线程安全
     * @return 生成的ID
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,抛出异常
        if (timestamp < lastTimestamp) {
            log.error("系统时钟回退,拒绝生成ID,上次时间戳: {}, 当前时间戳: {}", lastTimestamp, timestamp);
            throw new ClockBackwardsException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        
        // 如果是同一时间生成的,则进行毫秒内序列自增
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变,毫秒内序列重置
            sequence = 0L;
        }
        
        // 更新上次生成ID的时间戳
        lastTimestamp = timestamp;
        
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT)
                | (dataCenterId << DATA_CENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }
    
    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间戳
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
    
    /**
     * 生成字符串类型的ID
     * @return 字符串ID
     */
    public String nextStringId() {
        return String.valueOf(nextId());
    }
}

3.4 线程安全的缓存工具类

缓存工具类在多线程环境下需要特别注意并发问题,下面是一个线程安全的本地缓存实现:

复制代码
/**
 * 线程安全的本地缓存工具类
 * 基于ConcurrentHashMap实现,支持过期时间设置
 * @author ken
 */
@Slf4j
public final class LocalCacheUtils {
    
    /**
     * 缓存项实体类
     */
    private static class CacheEntry {
        // 缓存值
        private final Object value;
        // 过期时间戳(毫秒),0表示永不过期
        private final long expireTimestamp;
        
        public CacheEntry(Object value, long expireTimestamp) {
            this.value = value;
            this.expireTimestamp = expireTimestamp;
        }
        
        /**
         * 判断缓存项是否已过期
         * @return 过期返回true,否则返回false
         */
        public boolean isExpired() {
            return expireTimestamp > 0 && System.currentTimeMillis() > expireTimestamp;
        }
        
        public Object getValue() {
            return value;
        }
    }
    
    // 缓存容器,使用ConcurrentHashMap保证线程安全
    private static final ConcurrentMap<String, CacheEntry> CACHE = new ConcurrentHashMap<>();
    
    // 定时清理过期缓存的线程
    private static final ScheduledExecutorService CLEANER = Executors.newSingleThreadScheduledExecutor(runnable -> {
        Thread thread = new Thread(runnable, "local-cache-cleaner");
        thread.setDaemon(true); // 守护线程,当主线程退出时自动关闭
        return thread;
    });
    
    static {
        // 每5分钟清理一次过期缓存
        CLEANER.scheduleAtFixedRate(LocalCacheUtils::cleanExpiredEntries, 5, 5, TimeUnit.MINUTES);
        
        // 注册钩子,JVM关闭时清理资源
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            CLEANER.shutdown();
            try {
                if (!CLEANER.awaitTermination(1, TimeUnit.SECONDS)) {
                    CLEANER.shutdownNow();
                }
            } catch (InterruptedException e) {
                CLEANER.shutdownNow();
            }
            log.info("本地缓存已关闭");
        }));
    }
    
    private LocalCacheUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 添加缓存项,永不过期
     * @param key 缓存键
     * @param value 缓存值
     */
    public static void put(String key, Object value) {
        put(key, value, 0);
    }
    
    /**
     * 添加缓存项,设置过期时间
     * @param key 缓存键
     * @param value 缓存值
     * @param expireTime 过期时间
     * @param timeUnit 时间单位
     */
    public static void put(String key, Object value, long expireTime, TimeUnit timeUnit) {
        StringUtils.hasText(key, "缓存键不能为空");
        ObjectUtils.requireNonNull(value, "缓存值不能为空");
        ObjectUtils.requireNonNull(timeUnit, "时间单位不能为空");
        
        long expireMillis = expireTime <= 0 ? 0 : timeUnit.toMillis(expireTime);
        long expireTimestamp = expireMillis > 0 ? System.currentTimeMillis() + expireMillis : 0;
        
        CACHE.put(key, new CacheEntry(value, expireTimestamp));
        log.debug("添加缓存,key: {}, 过期时间: {}", key, expireTime <= 0 ? "永不过期" : expireTime + timeUnit.name());
    }
    
    /**
     * 添加缓存项,设置过期时间(毫秒)
     * @param key 缓存键
     * @param value 缓存值
     * @param expireMillis 过期时间(毫秒),0表示永不过期
     */
    public static void put(String key, Object value, long expireMillis) {
        StringUtils.hasText(key, "缓存键不能为空");
        ObjectUtils.requireNonNull(value, "缓存值不能为空");
        
        long expireTimestamp = expireMillis > 0 ? System.currentTimeMillis() + expireMillis : 0;
        CACHE.put(key, new CacheEntry(value, expireTimestamp));
        log.debug("添加缓存,key: {}, 过期时间: {}毫秒", key, expireMillis <= 0 ? "永久" : expireMillis);
    }
    
    /**
     * 获取缓存项
     * @param key 缓存键
     * @param <T> 缓存值类型
     * @return 缓存值,如果不存在或已过期则返回null
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        StringUtils.hasText(key, "缓存键不能为空");
        
        CacheEntry entry = CACHE.get(key);
        if (entry == null) {
            log.debug("缓存不存在,key: {}", key);
            return null;
        }
        
        if (entry.isExpired()) {
            CACHE.remove(key);
            log.debug("缓存已过期,key: {}", key);
            return null;
        }
        
        return (T) entry.getValue();
    }
    
    /**
     * 获取缓存项,如果不存在则执行加载函数并缓存结果
     * @param key 缓存键
     * @param loader 加载函数,当缓存不存在时执行
     * @param expireMillis 过期时间(毫秒)
     * @param <T> 缓存值类型
     * @return 缓存值
     */
    public static <T> T getOrLoad(String key, Supplier<T> loader, long expireMillis) {
        StringUtils.hasText(key, "缓存键不能为空");
        ObjectUtils.requireNonNull(loader, "加载函数不能为空");
        
        T value = get(key);
        if (value != null) {
            return value;
        }
        
        // 双重检查,减少并发时的重复加载
        synchronized (key.intern()) {
            value = get(key);
            if (value != null) {
                return value;
            }
            
            // 执行加载函数
            value = loader.get();
            ObjectUtils.requireNonNull(value, "加载函数返回值不能为空");
            
            // 缓存结果
            put(key, value, expireMillis);
            return value;
        }
    }
    
    /**
     * 移除缓存项
     * @param key 缓存键
     */
    public static void remove(String key) {
        StringUtils.hasText(key, "缓存键不能为空");
        
        CACHE.remove(key);
        log.debug("移除缓存,key: {}", key);
    }
    
    /**
     * 清空所有缓存
     */
    public static void clear() {
        CACHE.clear();
        log.debug("清空所有缓存");
    }
    
    /**
     * 获取缓存大小
     * @return 缓存项数量
     */
    public static int size() {
        return CACHE.size();
    }
    
    /**
     * 清理过期的缓存项
     */
    private static void cleanExpiredEntries() {
        log.debug("开始清理过期缓存,当前缓存大小: {}", CACHE.size());
        
        int removedCount = 0;
        long now = System.currentTimeMillis();
        
        for (Map.Entry<String, CacheEntry> entry : CACHE.entrySet()) {
            CacheEntry cacheEntry = entry.getValue();
            if (cacheEntry.expireTimestamp > 0 && now > cacheEntry.expireTimestamp) {
                CACHE.remove(entry.getKey());
                removedCount++;
            }
        }
        
        log.debug("清理过期缓存完成,移除缓存项数量: {}", removedCount);
    }
}

四、性能优化:让工具类高效运行

高性能的工具类能够在不增加系统负担的情况下提供所需功能。性能优化需要基于实际测试数据,而不是凭空猜测。

4.1 性能优化的基本原则

工具类性能优化应遵循以下原则:

  • 避免过早优化:先确保功能正确,再考虑性能优化
  • 基于数据优化:通过基准测试确定性能瓶颈,有针对性地优化
  • 空间换时间:在内存允许的情况下,使用缓存等机制提高速度
  • 时间换空间:在内存受限的情况下,适当增加计算时间减少内存占用
  • 减少对象创建:避免频繁创建短期对象,减少 GC 压力
  • 选择合适数据结构:根据使用场景选择最优的数据结构

4.2 集合操作工具类的性能优化

集合操作是工具类中常见的功能,优化集合操作能显著提升性能:

复制代码
/**
 * 高性能集合操作工具类
 * 提供高效的集合转换、过滤、分组等操作
 * @author ken
 */
@Slf4j
public final class CollectionUtils {
    
    private CollectionUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 将集合转换为Map
     * 使用指定的键提取器和值提取器
     * 优化点:预先计算初始容量,避免Map扩容
     * @param collection 源集合
     * @param keyExtractor 键提取器
     * @param valueExtractor 值提取器
     * @param <K> 键类型
     * @param <V> 值类型
     * @param <T> 集合元素类型
     * @return 转换后的Map
     */
    public static <K, V, T> Map<K, V> toMap(Collection<T> collection, 
                                           Function<? super T, ? extends K> keyExtractor,
                                           Function<? super T, ? extends V> valueExtractor) {
        org.springframework.util.CollectionUtils.isEmpty(collection, "源集合不能为空");
        ObjectUtils.requireNonNull(keyExtractor, "键提取器不能为空");
        ObjectUtils.requireNonNull(valueExtractor, "值提取器不能为空");
        
        // 计算初始容量,避免Map扩容
        int initialCapacity = computeInitialCapacity(collection.size());
        Map<K, V> map = Maps.newHashMapWithExpectedSize(initialCapacity);
        
        for (T element : collection) {
            K key = keyExtractor.apply(element);
            V value = valueExtractor.apply(element);
            map.put(key, value);
        }
        
        return map;
    }
    
    /**
     * 将集合分组
     * 优化点:使用并行流处理大数据量集合
     * @param collection 源集合
     * @param classifier 分组依据提取器
     * @param <T> 集合元素类型
     * @param <K> 分组键类型
     * @return 分组后的Map
     */
    public static <T, K> Map<K, List<T>> groupBy(Collection<T> collection, 
                                                Function<? super T, ? extends K> classifier) {
        org.springframework.util.CollectionUtils.isEmpty(collection, "源集合不能为空");
        ObjectUtils.requireNonNull(classifier, "分组依据提取器不能为空");
        
        // 大数据量时使用并行流提高效率
        if (collection.size() > 1000) {
            return collection.parallelStream()
                    .collect(Collectors.groupingBy(classifier, Collectors.toList()));
        } else {
            return collection.stream()
                    .collect(Collectors.groupingBy(classifier, Collectors.toList()));
        }
    }
    
    /**
     * 过滤集合元素
     * 优化点:根据过滤条件预估结果大小,减少中间集合扩容
     * @param collection 源集合
     * @param predicate 过滤条件
     * @param <T> 集合元素类型
     * @return 过滤后的集合
     */
    public static <T> List<T> filter(Collection<T> collection, Predicate<? super T> predicate) {
        org.springframework.util.CollectionUtils.isEmpty(collection, "源集合不能为空");
        ObjectUtils.requireNonNull(predicate, "过滤条件不能为空");
        
        List<T> result = Lists.newArrayListWithCapacity(collection.size() / 2); // 预估一半元素会被保留
        
        for (T element : collection) {
            if (predicate.test(element)) {
                result.add(element);
            }
        }
        
        // 调整容量为实际大小,节省内存
        result.trimToSize();
        return result;
    }
    
    /**
     * 提取集合元素的某个属性并去重
     * 优化点:使用HashSet去重,同时转换为List
     * @param collection 源集合
     * @param mapper 属性提取器
     * @param <T> 集合元素类型
     * @param <R> 提取的属性类型
     * @return 去重后的属性列表
     */
    public static <T, R> List<R> extractDistinct(Collection<T> collection, 
                                                Function<? super T, ? extends R> mapper) {
        org.springframework.util.CollectionUtils.isEmpty(collection, "源集合不能为空");
        ObjectUtils.requireNonNull(mapper, "属性提取器不能为空");
        
        // 使用HashSet去重,同时指定初始容量
        int initialCapacity = computeInitialCapacity(collection.size());
        Set<R> set = Sets.newHashSetWithExpectedSize(initialCapacity);
        
        for (T element : collection) {
            R value = mapper.apply(element);
            set.add(value);
        }
        
        return Lists.newArrayList(set);
    }
    
    /**
     * 计算Map的初始容量
     * 基于HashMap的加载因子(0.75)计算
     * @param size 预计元素数量
     * @return 初始容量
     */
    private static int computeInitialCapacity(int size) {
        if (size < 3) {
            return size + 1;
        }
        if (size < 1073741824) {
            return (int) ((float) size / 0.75F + 1.0F);
        }
        return Integer.MAX_VALUE;
    }
    
    /**
     * 检查两个集合是否有交集
     * 优化点:使用较小的集合作为基准,减少比较次数
     * @param collection1 第一个集合
     * @param collection2 第二个集合
     * @param <T> 集合元素类型
     * @return 有交集返回true,否则返回false
     */
    public static <T> boolean hasIntersection(Collection<T> collection1, Collection<T> collection2) {
        org.springframework.util.CollectionUtils.isEmpty(collection1, "第一个集合不能为空");
        org.springframework.util.CollectionUtils.isEmpty(collection2, "第二个集合不能为空");
        
        // 选择较小的集合作为基准,提高效率
        Collection<T> smaller = collection1.size() <= collection2.size() ? collection1 : collection2;
        Collection<T> larger = collection1.size() > collection2.size() ? collection1 : collection2;
        
        // 如果较小的集合是Set,直接使用containsAll
        if (smaller instanceof Set) {
            return larger.stream().anyMatch(smaller::contains);
        }
        
        // 否则将较小的集合转换为Set,提高查询效率
        Set<T> smallerSet = Sets.newHashSet(smaller);
        return larger.stream().anyMatch(smallerSet::contains);
    }
}

4.3 序列化工具类的性能优化

序列化和反序列化是常见的操作,优化其性能能显著提升系统响应速度:

复制代码
/**
 * 高性能序列化工具类
 * 提供多种序列化方式,并选择最优实现
 * @author ken
 */
@Slf4j
public final class SerializationUtils {
    
    // 使用ThreadLocal缓存ObjectMapper实例,避免重复创建
    private static final ThreadLocal<ObjectMapper> OBJECT_MAPPER_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
        ObjectMapper mapper = new ObjectMapper();
        // 配置序列化特性,提高性能
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return mapper;
    });
    
    // FastJson2序列化器
    private static final JSONWriter.Feature[] FASTJSON_WRITE_FEATURES = {
        JSONWriter.Feature.WriteNulls,
        JSONWriter.Feature.PrettyFormat
    };
    
    private SerializationUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 将对象序列化为JSON字符串
     * 根据对象大小选择最优序列化方式
     * @param obj 要序列化的对象
     * @return JSON字符串
     */
    public static String toJson(Object obj) {
        ObjectUtils.requireNonNull(obj, "序列化对象不能为空");
        
        // 对于小对象,使用FastJson2提高速度
        if (isSmallObject(obj)) {
            return JSON.toJSONString(obj, FASTJSON_WRITE_FEATURES);
        }
        
        // 对于大对象,使用Jackson,内存占用更优
        try {
            ObjectMapper mapper = OBJECT_MAPPER_THREAD_LOCAL.get();
            String json = mapper.writeValueAsString(obj);
            OBJECT_MAPPER_THREAD_LOCAL.remove(); // 清理ThreadLocal
            return json;
        } catch (JsonProcessingException e) {
            log.error("Jackson序列化失败", e);
            throw new SerializationException("对象序列化失败", e);
        }
    }
    
    /**
     * 将JSON字符串反序列化为对象
     * @param json JSON字符串
     * @param clazz 目标类
     * @param <T> 目标类型
     * @return 反序列化后的对象
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        StringUtils.hasText(json, "JSON字符串不能为空");
        ObjectUtils.requireNonNull(clazz, "目标类不能为空");
        
        // 对于小JSON,使用FastJson2
        if (json.length() < 1024) {
            return JSON.parseObject(json, clazz);
        }
        
        // 对于大JSON,使用Jackson
        try {
            ObjectMapper mapper = OBJECT_MAPPER_THREAD_LOCAL.get();
            T result = mapper.readValue(json, clazz);
            OBJECT_MAPPER_THREAD_LOCAL.remove(); // 清理ThreadLocal
            return result;
        } catch (IOException e) {
            log.error("Jackson反序列化失败,JSON: {}", json, e);
            throw new SerializationException("JSON反序列化失败", e);
        }
    }
    
    /**
     * Java原生序列化
     * 注意:仅在必要时使用,性能较差
     * @param obj 要序列化的对象
     * @return 序列化后的字节数组
     */
    public static byte[] serialize(Object obj) {
        ObjectUtils.requireNonNull(obj, "序列化对象不能为空");
        
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(obj);
            oos.flush();
            return bos.toByteArray();
        } catch (IOException e) {
            log.error("Java原生序列化失败", e);
            throw new SerializationException("对象序列化失败", e);
        }
    }
    
    /**
     * Java原生反序列化
     * @param data 序列化后的字节数组
     * @param <T> 目标类型
     * @return 反序列化后的对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T deserialize(byte[] data) {
        ObjectUtils.requireNonNull(data, "序列化数据不能为空");
        ObjectUtils.checkArgument(data.length > 0, "序列化数据不能为空");
        
        try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            return (T) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            log.error("Java原生反序列化失败", e);
            throw new SerializationException("对象反序列化失败", e);
        }
    }
    
    /**
     * 判断对象是否为小对象
     * 基于对象类型和估计大小
     * @param obj 要判断的对象
     * @return 是小对象返回true,否则返回false
     */
    private static boolean isSmallObject(Object obj) {
        if (obj instanceof String) {
            return ((String) obj).length() < 1024;
        }
        if (obj instanceof Collection<?>) {
            return ((Collection<?>) obj).size() < 100;
        }
        if (obj instanceof Map<?, ?>) {
            return ((Map<?, ?>) obj).size() < 50;
        }
        return true;
    }
}

4.4 性能测试与对比

为了验证性能优化的效果,我们需要进行基准测试:

复制代码
/**
 * 工具类性能测试
 * 使用JMH进行基准测试
 * @author ken
 */
@State(Scope.Benchmark)
public class UtilityPerformanceTest {
    
    private List<User> userList;
    private String jsonString;
    
    /**
     * 测试准备工作
     */
    @Setup(Level.Trial)
    public void setup() {
        // 初始化测试数据
        userList = Lists.newArrayList();
        for (int i = 0; i < 1000; i++) {
            User user = new User();
            user.setId(i);
            user.setName("User_" + i);
            user.setAge(20 + i % 30);
            user.setEmail("user_" + i + "@example.com");
            user.setCreateTime(LocalDateTime.now().minusDays(i));
            userList.add(user);
        }
        
        // 生成测试JSON字符串
        jsonString = SerializationUtils.toJson(userList);
    }
    
    /**
     * 测试集合转换性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testToMapPerformance() {
        CollectionUtils.toMap(userList, User::getId, Function.identity());
    }
    
    /**
     * 测试分组性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testGroupByPerformance() {
        CollectionUtils.groupBy(userList, user -> user.getAge() / 10);
    }
    
    /**
     * 测试JSON序列化性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testJsonSerialization() {
        SerializationUtils.toJson(userList);
    }
    
    /**
     * 测试JSON反序列化性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testJsonDeserialization() {
        SerializationUtils.fromJson(jsonString, new TypeReference<List<User>>() {});
    }
    
    /**
     * 测试ID生成性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testIdGeneration() {
        IdGeneratorUtils.getInstance(1, 1).nextId();
    }
    
    /**
     * 测试缓存性能
     */
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testCachePerformance() {
        String key = "test_key_" + Thread.currentThread().getId();
        LocalCacheUtils.put(key, "test_value", 1000);
        LocalCacheUtils.get(key);
        LocalCacheUtils.remove(key);
    }
    
    /**
     * 测试主方法
     */
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(UtilityPerformanceTest.class.getSimpleName())
                .warmupIterations(3)
                .measurementIterations(5)
                .threads(8)
                .forks(1)
                .build();
        
        new Runner(options).run();
    }
    
    /**
     * 测试用用户类
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class User {
        private Integer id;
        private String name;
        private Integer age;
        private String email;
        private LocalDateTime createTime;
    }
}

五、企业级工具类库的设计与实践

构建一个企业级的工具类库需要综合考虑可复用性、线程安全和性能,同时还要注重易用性和扩展性。

5.1 工具类库的整体架构

一个完善的工具类库应包含以下几个部分:

5.2 工具类库的依赖管理

使用 Maven 管理工具类库的依赖,确保依赖的稳定性和兼容性:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>enterprise-utils</artifactId>
    <version>1.0.0</version>
    <name>Enterprise Utility Library</name>
    <description>A comprehensive utility library for enterprise Java applications</description>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- 依赖版本 -->
        <lombok.version>1.18.30</lombok.version>
        <spring.version>6.1.2</spring.version>
        <fastjson2.version>2.0.45</fastjson2.version>
        <guava.version>33.1.0-jre</guava.version>
        <jackson.version>2.15.2</jackson.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <springdoc.version>2.2.0</springdoc.version>
        <junit.version>5.10.1</junit.version>
        <jmh.version>1.37</jmh.version>
    </properties>

    <dependencies>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- Spring Core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        
        <!-- Fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        
        <!-- Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        
        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        
        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        
        <!-- Swagger3/SpringDoc -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        
        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- JMH 基准测试 -->
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>${jmh.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>${jmh.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <testFailureIgnore>false</testFailureIgnore>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.3 企业级通用脱敏工具类

在企业应用中,数据脱敏是常见需求,下面是一个功能完善的脱敏工具类:

复制代码
/**
 * 数据脱敏工具类
 * 支持多种敏感信息的脱敏处理
 * @author ken
 */
@Slf4j
public final class DesensitizationUtils {
    
    /**
     * 脱敏类型枚举
     */
    public enum DesensitizationType {
        /** 姓名 */
        NAME,
        /** 身份证号 */
        ID_CARD,
        /** 手机号 */
        PHONE,
        /** 邮箱 */
        EMAIL,
        /** 银行卡号 */
        BANK_CARD,
        /** 地址 */
        ADDRESS,
        /** 密码 */
        PASSWORD
    }
    
    private DesensitizationUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * 对敏感信息进行脱敏
     * @param content 原始内容
     * @param type 脱敏类型
     * @return 脱敏后的内容
     */
    public static String desensitize(String content, DesensitizationType type) {
        StringUtils.hasText(content, "脱敏内容不能为空");
        ObjectUtils.requireNonNull(type, "脱敏类型不能为空");
        
        switch (type) {
            case NAME:
                return desensitizeName(content);
            case ID_CARD:
                return desensitizeIdCard(content);
            case PHONE:
                return desensitizePhone(content);
            case EMAIL:
                return desensitizeEmail(content);
            case BANK_CARD:
                return desensitizeBankCard(content);
            case ADDRESS:
                return desensitizeAddress(content);
            case PASSWORD:
                return desensitizePassword(content);
            default:
                log.warn("未知的脱敏类型: {}", type);
                return content;
        }
    }
    
    /**
     * 对姓名进行脱敏
     * 规则:只显示第一个字符,其余用*代替
     * 示例:张**,李*
     * @param name 姓名
     * @return 脱敏后的姓名
     */
    public static String desensitizeName(String name) {
        StringUtils.hasText(name, "姓名不能为空");
        
        if (name.length() == 1) {
            return name;
        }
        
        return name.substring(0, 1) + "*".repeat(name.length() - 1);
    }
    
    /**
     * 对身份证号进行脱敏
     * 规则:显示前6位和后4位,中间用*代替
     * 示例:110101********1234
     * @param idCard 身份证号
     * @return 脱敏后的身份证号
     */
    public static String desensitizeIdCard(String idCard) {
        StringUtils.hasText(idCard, "身份证号不能为空");
        ValidationUtils.validate(idCard.length() >= 15, "身份证号格式不正确");
        
        if (idCard.length() == 15) {
            return idCard.substring(0, 6) + "*****" + idCard.substring(11);
        } else {
            return idCard.substring(0, 6) + "********" + idCard.substring(14);
        }
    }
    
    /**
     * 对手机号进行脱敏
     * 规则:显示前3位和后4位,中间4位用*代替
     * 示例:138****5678
     * @param phone 手机号
     * @return 脱敏后的手机号
     */
    public static String desensitizePhone(String phone) {
        StringUtils.hasText(phone, "手机号不能为空");
        ValidationUtils.validate(phone.length() == 11, "手机号格式不正确");
        
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }
    
    /**
     * 对邮箱进行脱敏
     * 规则:显示第一个字符和域名,中间用*代替
     * 示例:a**@example.com
     * @param email 邮箱
     * @return 脱敏后的邮箱
     */
    public static String desensitizeEmail(String email) {
        StringUtils.hasText(email, "邮箱不能为空");
        ValidationUtils.validate(ValidationUtils.isEmail(email), "邮箱格式不正确");
        
        int atIndex = email.indexOf('@');
        if (atIndex <= 1) {
            return email;
        }
        
        return email.substring(0, 1) + "*".repeat(atIndex - 1) + email.substring(atIndex);
    }
    
    /**
     * 对银行卡号进行脱敏
     * 规则:显示前6位和后4位,中间用*代替
     * 示例:622260********1234
     * @param bankCard 银行卡号
     * @return 脱敏后的银行卡号
     */
    public static String desensitizeBankCard(String bankCard) {
        StringUtils.hasText(bankCard, "银行卡号不能为空");
        ValidationUtils.validate(bankCard.length() >= 16, "银行卡号格式不正确");
        
        return bankCard.substring(0, 6) + "********" + bankCard.substring(bankCard.length() - 4);
    }
    
    /**
     * 对地址进行脱敏
     * 规则:只显示前6个字符,其余用*代替,最多显示10个字符
     * 示例:北京市海淀区***
     * @param address 地址
     * @return 脱敏后的地址
     */
    public static String desensitizeAddress(String address) {
        StringUtils.hasText(address, "地址不能为空");
        
        if (address.length() <= 6) {
            return address;
        }
        
        int showLength = Math.min(address.length(), 10);
        return address.substring(0, 6) + "*".repeat(showLength - 6);
    }
    
    /**
     * 对密码进行脱敏
     * 规则:全部用*代替,长度保持不变
     * 示例:******
     * @param password 密码
     * @return 脱敏后的密码
     */
    public static String desensitizePassword(String password) {
        StringUtils.hasText(password, "密码不能为空");
        
        return "*".repeat(password.length());
    }
    
    /**
     * 自定义脱敏
     * @param content 原始内容
     * @param prefixLength 前缀保留长度
     * @param suffixLength 后缀保留长度
     * @return 脱敏后的内容
     */
    public static String desensitizeCustom(String content, int prefixLength, int suffixLength) {
        StringUtils.hasText(content, "脱敏内容不能为空");
        ValidationUtils.validate(prefixLength >= 0, "前缀保留长度不能为负数");
        ValidationUtils.validate(suffixLength >= 0, "后缀保留长度不能为负数");
        ValidationUtils.validate(prefixLength + suffixLength <= content.length(), 
                "前缀保留长度与后缀保留长度之和不能超过内容长度");
        
        if (prefixLength + suffixLength >= content.length()) {
            return content;
        }
        
        String prefix = content.substring(0, prefixLength);
        String suffix = content.substring(content.length() - suffixLength);
        String mask = "*".repeat(content.length() - prefixLength - suffixLength);
        
        return prefix + mask + suffix;
    }
}

5.4 工具类的文档与测试

完善的文档和测试是企业级工具类库不可或缺的部分:

复制代码
/**
 * 脱敏工具类测试
 * @author ken
 */
@SpringBootTest
public class DesensitizationUtilsTest {
    
    @Test
    public void testDesensitizeName() {
        Assertions.assertEquals("张*", DesensitizationUtils.desensitizeName("张三"));
        Assertions.assertEquals("李**", DesensitizationUtils.desensitizeName("李四光"));
        Assertions.assertEquals("王", DesensitizationUtils.desensitizeName("王"));
    }
    
    @Test
    public void testDesensitizeIdCard() {
        Assertions.assertEquals("110101********1234", 
                DesensitizationUtils.desensitizeIdCard("110101199001011234"));
        Assertions.assertEquals("110101*****123", 
                DesensitizationUtils.desensitizeIdCard("110101900101123"));
    }
    
    @Test
    public void testDesensitizePhone() {
        Assertions.assertEquals("138****5678", 
                DesensitizationUtils.desensitizePhone("13812345678"));
    }
    
    @Test
    public void testDesensitizeEmail() {
        Assertions.assertEquals("a**@example.com", 
                DesensitizationUtils.desensitizeEmail("abc@example.com"));
        Assertions.assertEquals("t*@test.com", 
                DesensitizationUtils.desensitizeEmail("to@test.com"));
    }
    
    @Test
    public void testDesensitizeBankCard() {
        Assertions.assertEquals("622260********1234", 
                DesensitizationUtils.desensitizeBankCard("62226000123456781234"));
    }
    
    @Test
    public void testDesensitizeAddress() {
        Assertions.assertEquals("北京市海淀区***", 
                DesensitizationUtils.desensitizeAddress("北京市海淀区中关村大街1号"));
        Assertions.assertEquals("上海市徐汇区", 
                DesensitizationUtils.desensitizeAddress("上海市徐汇区"));
    }
    
    @Test
    public void testDesensitizePassword() {
        Assertions.assertEquals("******", 
                DesensitizationUtils.desensitizePassword("123456"));
        Assertions.assertEquals("********", 
                DesensitizationUtils.desensitizePassword("abcdefgh"));
    }
    
    @Test
    public void testDesensitizeCustom() {
        Assertions.assertEquals("ab***yz", 
                DesensitizationUtils.desensitizeCustom("abcdefyz", 2, 2));
        Assertions.assertEquals("test", 
                DesensitizationUtils.desensitizeCustom("test", 2, 2));
    }
    
    @Test
    public void testDesensitizeWithType() {
        Assertions.assertEquals("张*", 
                DesensitizationUtils.desensitize("张三", DesensitizationUtils.DesensitizationType.NAME));
        Assertions.assertEquals("138****5678", 
                DesensitizationUtils.desensitize("13812345678", DesensitizationUtils.DesensitizationType.PHONE));
    }
    
    @Test
    public void testDesensitizeWithInvalidInput() {
        Assertions.assertThrows(IllegalArgumentException.class, 
                () -> DesensitizationUtils.desensitizeName(null));
        Assertions.assertThrows(IllegalArgumentException.class, 
                () -> DesensitizationUtils.desensitizePhone("12345"));
    }
}

六、总结:工具类设计的艺术与平衡

Java 工具类设计看似简单,实则蕴含着深厚的技术功底和设计智慧。一个优秀的工具类不仅要提供实用的功能,更要在可复用性、线程安全和性能之间找到完美的平衡。

6.1 工具类设计的核心要点

  • 可复用性:通过单一职责、接口与实现分离、参数化配置等手段,让工具类能够在不同场景下被复用
  • 线程安全:采用无状态设计、不可变对象、同步机制或 ThreadLocal 等方式,确保工具类在并发环境下的安全性
  • 性能优化:基于实际测试数据,选择合适的数据结构,减少对象创建,合理使用缓存,提升工具类的执行效率

6.2 工具类设计的最佳实践

  • 遵循 "高内聚、低耦合" 原则,每个工具类专注于特定领域的功能
  • 对输入参数进行严格校验,采用防御性编程
  • 提供清晰的文档和丰富的测试用例
  • 避免过度设计,保持工具类的简洁性和易用性
  • 定期进行性能测试和优化,持续改进工具类的质量

工具类作为 Java 开发中最基础也最常用的组件,其设计质量直接影响整个项目的代码质量和开发效率。希望本文所探讨的工具类设计精髓,能够帮助你打造出更加优秀、可靠、高效的 Java 工具类,让代码不仅能够工作,更能优雅地工作。

相关推荐
serendipity_hky16 小时前
互联网大厂Java面试故事:核心技术栈与场景化业务问题实战解析
java·spring boot·redis·elasticsearch·微服务·消息队列·内容社区
我真不会起名字啊16 小时前
C、C++中的sprintf和stringstream的使用
java·c语言·c++
十点摆码16 小时前
Spring Boot2 使用 Flyway 管理数据库版本
java·flyway·数据库脚本·springboo2·数据库脚本自动管理
毕设源码-钟学长16 小时前
【开题答辩全过程】以 基于Javaweb的电动汽车充电桩管理系统为例,包含答辩的问题和答案
java·spring boot
多敲代码防脱发16 小时前
为何引入Spring-cloud以及远程调用(RestTemplate)
java·开发语言
毕设源码-邱学长16 小时前
【开题答辩全过程】以 基于JavaWeb的家庭理财管理系统的设计与实现为例,包含答辩的问题和答案
java
sailing-data16 小时前
【SE】接口标准化
java·开发语言
t***p93517 小时前
idea创建springBoot的五种方式
java
+VX:Fegn089517 小时前
计算机毕业设计|基于springboot+vue的学校课程管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
S***267517 小时前
【监控】spring actuator源码速读
java·spring boot·spring