Java 通用配置设计

Posted isea533

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 通用配置设计相关的知识,希望对你有一定的参考价值。

本系列参考实现:

https://gitee.com/mybatis-mapper/config
https://github.com/mybatis-mapper/config

起因

有用户提出 mybatis-mapper 能不能不在字段上加这么多注解,很麻烦,陆陆续续有不少用户都提到了这个。

最初设计必须加就是为了防止 tk-mapper 中存在的配置问题,都按这种标准去使用,就不会遇到类似下面的问题:

  1. 加了字段,数据库不存在报错了。
  2. 为什么 int,long 类型的字段没有出现在表字段中
  3. 为什么枚举没有出现在表字段中

为了解决上面的问题,tk-mapper 中有很多对应的配置,大量的配置会使新人上手变的困难。增加配置后,在不同框架中集成时配置方式不同,这又会产生大量配置不生效的问题。

mybatis-mapper 中虽然需要大量注解,但是可以通过代码生成器自动生成,如果是全新项目还好说,已有项目想集成,如果不重新生成代码,就得手动一个个加,这种情况确实麻烦。

既然这么多人都觉得麻烦,那就允许在实体类上指定字段映射的规则,增加映射规则后还得有默认值,既然有了默认值,不同数据库可能有不同的规则,肯定要支持修改默认值吧?

到最后,为了支持不加注解,还要先考虑如何覆盖默认值?

参考 Spring 外部化配置规则

为了方便覆盖默认配置,支持云原生,直接参考了 Spring 的配置规则:

在上面的规则中,直接从命令行参数读取并不合适,所以就参考后面3个步骤,但是在这个基础上又增加了一些为了兼容版本支持的规则。

配置规则设计

结合 Spring 的规则,最终设计如下:

Spring 中因为通过 run 方法运行传递了 args[] 参数,所以他能支持命令行参数,作为一个普通的工具类,不方便让用户在 main 方法中通过参数先调用配置规则的方法,所以这里就先去掉了命令行参数(后续扩展 Spring 后,就完全支持 Spring 的所有外部化配置了)。

JVM参数和环境变量的规则保持不变,后面跟着的配置文件这里重新定义成了用户配置文件,内容没有太大变化,这个名字主要是为了和后面版本配置文件对应。

假设代码库在某个版本要对默认值进行修改,这就会出现对低版本不兼容的情况,一般的做法就直接改了,但是这里的设计为了让用户在升级新版本代码后仍然可以使用低版本的配置,配置文件也增加了版本号,此时就不是修改已有配置的值,而是增加一个新的版本配置文件,把其中变化的属性修改即可。如果新版本配置增加了新的属性,使用低版本配置会导致找不到该属性,因此这里会把指定的版本作为第一级配置,后续所有版本按照版本顺序依次初始化 Properties 时作为新版本的默认配置存在(public Properties(Properties defaults)),这个关系的图示如下:

规则看起来很复杂,而且不一定能满足所有需求,上图中,如果想优先2.0配置,然后是1.0配置,这该怎么办?虽然可以 设计 类似 xxx.version=2.0,1.0,3.0,4.0 这种方式指定,组合出来的结果也不一定是你想要的,遇到这种情况时,别考虑那么复杂,直接在 用户配置 中指定就行了,JVM和环境变量覆盖也可以。

接口定义

有了上面的逻辑设计后,先来看最简单的类定义。

io.mybatis.config.Config 接口定义:

package io.mybatis.config;

/**
 * 获取配置信息,默认系统变量高于环境变量设置。
 * <p>
 * 推荐的优先级顺序为 spring > system > env > 自定义配置文件.properties
 *
 * @author liuzh
 */
public interface Config 
  /**
   * 低优先级
   */
  int LOW_ORDER = 0;

  /**
   * 版本配置
   */
  int VERSION_ORDER = 100;

  /**
   * 用户配置
   */
  int USER_ORDER = 200;

  /**
   * 环境变量
   */
  int ENV_ORDER = 300;

  /**
   * 系统变量
   */
  int SYSTEM_ORDER = 400;

  /**
   * Spring 配置优先级更高,并且包含了 Spring 的环境变量、系统变量、运行参数
   */
  int SPRING_ORDER = 500;

  /**
   * 高优先级
   */
  int HIGH_ORDER = 1000;

  /**
   * @return 执行顺序,数字越大优先级越高,越早执行
   */
  default int getOrder() 
    return 0;
  

  /**
   * 获取配置信息
   *
   * @param key 配置键
   */
  String getStr(String key);

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  default String getStr(String key, String defaultValue) 
    String val = getStr(key);
    if (val == null) 
      return defaultValue;
    
    return val;
  

  /**
   * 获取配置信息
   *
   * @param key 配置键
   * @return 配置值
   */
  default Integer getInt(String key) 
    String val = getStr(key);
    if (val == null) 
      return null;
    
    return Integer.parseInt(val);
  

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  default Integer getInt(String key, Integer defaultValue) 
    Integer val = getInt(key);
    return val != null ? val : defaultValue;
  


  /**
   * 获取配置信息
   *
   * @param key 配置键
   * @return 配置值
   */
  default boolean getBoolean(String key) 
    return Boolean.valueOf(getStr(key));
  

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  default boolean getBoolean(String key, boolean defaultValue) 
    String val = getStr(key);
    if (val == null) 
      return defaultValue;
    
    return Boolean.valueOf(val);
  


提供一个辅助类 ConfigHelper 按照优先级规则从 SPI 加载接口实现。提供静态方法便于获取配置值。

package io.mybatis.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceLoader;

/**
 * 配置工具类,按照优先级顺序获取配置值,参考 @link Config
 *
 * @author liuzh
 */
public class ConfigHelper 
  public static final Logger log = LoggerFactory.getLogger(ConfigHelper.class);

  /**
   * 所有配置实现
   */
  private static volatile List<Config> CONFIGS;

  /**
   * 获取配置信息
   *
   * @param key 配置键
   */
  public static String getStr(String key) 
    init();
    for (Config config : CONFIGS) 
      String value = config.getStr(key);
      if (value != null) 
        return value;
      
    
    return null;
  

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  public static String getStr(String key, String defaultValue) 
    String val = getStr(key);
    if (val == null) 
      return defaultValue;
    
    return val;
  

  /**
   * 获取配置信息
   *
   * @param key 配置键
   * @return 配置值
   */
  public static Integer getInt(String key) 
    String val = getStr(key);
    return val == null ? null : Integer.parseInt(val);
  

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  public static Integer getInt(String key, Integer defaultValue) 
    Integer val = getInt(key);
    return val != null ? val : defaultValue;
  

  /**
   * 获取配置信息
   *
   * @param key 配置键
   * @return 配置值
   */
  public static boolean getBoolean(String key) 
    return Boolean.valueOf(getStr(key));
  

  /**
   * 获取配置信息
   *
   * @param key          配置键
   * @param defaultValue 默认值
   * @return 配置值
   */
  public static boolean getBoolean(String key, boolean defaultValue) 
    String val = getStr(key);
    if (val == null) 
      return defaultValue;
    
    return Boolean.valueOf(val);
  

  /**
   * 初始化
   */
  private static void init() 
    if (CONFIGS == null) 
      synchronized (ConfigHelper.class) 
        if (CONFIGS == null) 
          CONFIGS = new ArrayList<>();
          ServiceLoader<Config> serviceLoader = ServiceLoader.load(Config.class);
          for (Config config : serviceLoader) 
            CONFIGS.add(config);
          
          CONFIGS.sort(Comparator.comparing(Config::getOrder).reversed());
          CONFIGS.forEach(c -> log.debug("加载配置类: " + c.getClass().getName()));
        
      
    
  

  /**
   * 重新加载
   */
  public static void reload() 
    CONFIGS = null;
    init();
  


接口定义和方法都很简单,在 ConfigHelper 中还增加了 reload 方便后续和热部署集成(后续如果有时间就介绍和 Spring DevTools 的集成)。

本文的设计阶段就是这些内容,核心代码就这两个类,后续的内容中会针对上面设计的逻辑逐步实现接口提供对应的功能,如果有兴趣,可以关注后续的更新。

本文的内容早晚都会写,但是现在写主要是因为 CSDN 的 「笔耕不辍」生命不止,写作不息 活动,已经 9 级了,不如在努力一下看看能不能直接满级(10级)。

以上是关于Java 通用配置设计的主要内容,如果未能解决你的问题,请参考以下文章

Java 通用配置设计

Java 通用配置设计

Java 通用配置版本配置实现

Java 通用配置版本配置实现

Java 通用配置用户配置实现

Java 通用配置用户配置实现