Java 通用配置用户配置实现

Posted isea533

tags:

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

Java 通用配置
(一)设计
(二)JVM和环境变量实现
(三)用户配置实现

本系列参考实现:

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

用户配置


用户和版本配置都需要读取文件,但是两者的难度相差很大,本节先来看看用户配置的设计和实现。

抽象类定义

这里的用户配置是一个抽象实现,当具体模块需要使用时需要继承该类进行实现,抽象类中定义了一定的规范和实现,下面先看抽象类的定义:

public abstract class UserConfig implements Config 
  public static final Logger     log       = LoggerFactory.getLogger(UserConfig.class);
  public static final String     FILE_TYPE = ".properties";
  protected volatile  Properties properties;

  @Override
  public int getOrder() 
    return USER_ORDER;
  

  /**
   * 获取文件名对应的 key
   */
  protected abstract String getConfigKey();

  /**
   * 获取默认配置名
   */
  protected abstract String getConfigName();

  /**
   * 跳过读取指定的 key
   *
   * @param key 属性
   */
  protected boolean skipKey(String key) 
    return getConfigKey().equals(key);
  

  /**
   * 初始化
   */
  protected void init() 
    Properties props = getUserProperties();
    if (props != null) 
      this.properties = props;
     else 
      this.properties = new Properties();
    
  

  /**
   * 获取用户配置文件
   */
  protected Properties getUserProperties() 
    //TODO 先隐去加载配置的主要实现代码
  

  @Override
  public String getStr(String key) 
    if (skipKey(key)) 
      return null;
    
    if (this.properties == null) 
      synchronized (this) 
        if (this.properties == null) 
          this.init();
        
      
    
    return properties.getProperty(key);
  


方法说明

在当前方法实现的 getStr(String key) 中,通过 skipKey 排除了一些字段的获取,在后续又通过加锁方式对用户配置进行初始化,初始化完成后,在从初始化后的 properties 中读取 key

在上面抽象类中,有两个抽象方法,这两个方法是用于具体实现的,第一个 getConfigKey() 方法,用于定义获取配置文件信息的 key,例如返回值可以是 mapper.properties,用户配置中会通过 ConfigHelper.getStr("mapper.properties") 去获取用户配置文件的名字,如果用户通过JVM设置为 -Dmapper.properties=file:/xxxx/user-config.properties 时,就会从 file:/xxxx/user-config.properties 位置读取配置,如果配置 -Dmapper.properties=/xxxx/user-config.properties 会先从文件路径获取,找不到时在从类路径中查找。

获取值过程是在初始化用户配置时调用的,用户配置也是 ConfigHelper 中一个环节,因此需要考虑在JVM和环境变量都没有配置,进入到用户配置获取该值时,通过 skipKey 跳过改值的获取( 也可以在匹配时直接通过 String getConfigName() 返回默认的用户配置文件名 ),这样就能避免进入初始化的死循环。

提供 getConfigKey() 方法就是为了给用户配置文件更大的灵活性,指定一个特殊路径时,在 K8s 中可以方便的用 ConfigMap 挂载卷进行覆盖,不指定路径时,就需要通过 String getConfigName() 返回一个默认的文件名。

上面几段内容就是 getUserProperties() 方法的实现:

protected Properties getUserProperties() 
  //读取用户配置的配置路径
  String requestedFile = System.getProperty(getConfigKey());
  //没有配置时使用默认名称
  String propFileName = requestedFile != null ? requestedFile : getConfigName();
  //默认名称允许不带后缀
  if (!propFileName.endsWith(FILE_TYPE)) 
    propFileName += FILE_TYPE;
  
  // 先读取用户目录下面的配置(指定或默认)
  File file = new File(propFileName);
  if (!file.exists()) 
    if (requestedFile != null) 
      // 类路径下的配置,指定的不存在就报错
      try 
        file = ResourceUtil.getFile(requestedFile);
       catch (FileNotFoundException e) 
        log.warn("指定的用户配置文件: " + requestedFile + " 不存在");
      
      try 
        file = ResourceUtil.getClasspathFile(requestedFile);
       catch (FileNotFoundException e) 
        log.warn("指定的用户配置文件在类路径下: " + requestedFile + " 不存在");
      
     else 
      // 默认文件,非用户指定时
      try 
        file = ResourceUtil.getClasspathFile(propFileName);
       catch (FileNotFoundException e) 
        try 
          file = ResourceUtil.getClasspathFile("/" + propFileName);
         catch (FileNotFoundException e2) 
          try 
            // 包下面的配置
            String path = getClass().getPackage().getName().replaceAll("\\\\.", "/");
            file = ResourceUtil.getClasspathFile(path + "/" + propFileName);
           catch (FileNotFoundException ignored) 

          
        
      
    
  
  Properties props = new Properties();
  if (file.exists()) 
    try (InputStream in = new FileInputStream(file)) 
      props.load(in);
     catch (IOException ignored) 

    
  
  return props;

用户配置优先级

上面逻辑主要处理了配置加载的优先级:

当用户指定了具体的文件时,如果 user.dir (程序执行目录)和类路径下面都没有,就会抛异常,其他情况找不到就是空配置。

这样设计的目的是让用户尽早发现配置相关的问题。

类加载器

读取文件时用到了 ResourceUtil 类,这个类也直接参考了 Spring 中的相关实现,这里只介绍下面一个方法:

/**
 * 获取默认类加载器,参考 Spring ClassUtils
 */
public static ClassLoader getDefaultClassLoader() 
  ClassLoader cl = null;
  try 
    cl = Thread.currentThread().getContextClassLoader();
   catch (Throwable ignored) 
  
  if (cl == null) 
    cl = ResourceUtil.class.getClassLoader();
    if (cl == null) 
      try 
        cl = ClassLoader.getSystemClassLoader();
       catch (Throwable ignored) 
      
    
  
  return Objects.requireNonNull(cl);

Java 中没有明确的获取 ClassLoader 的规范,大多数开源框架中,都采用了类似上面的方式:

  1. 先从线程上下文获取(需要框架或者人工传递)
  2. 使用当前类的类加载器
  3. 使用系统默认的类加载器

大多数情况下这种方式都没有问题,在有特殊 ClassLoader 的场景下有可能找不到会找不到资源,下图是 arthas 中获取的类加载器层次结构:

+-BootstrapClassLoader                                                                                                                                                          
+-sun.misc.Launcher$ExtClassLoader@36dfbdaf                                                                                                                                     
  +-com.taobao.arthas.agent.ArthasClassloader@19e05970                                                                                                                          
  +-sun.misc.Launcher$AppClassLoader@4e0e2f2a                                                                                                                                   
    +-org.springframework.boot.loader.LaunchedURLClassLoader@4eec7777                                

在上面的类加载器中,Spring 的 LaunchedURLClassLoader 能获取到大部分的资源,但是无法获取 ArthasClassloader 单独加载的所有资源。

有关类加载器的内容可以搜索双亲委派模型了解更多。

小结

本文提供的用户配置是一个抽象类,没有具体实现类的情况下无法进行测试,所以本节相关的测试会放到后续具体扩展实现时进行。

对配置文件的加载过程和普通的读取文件类似,如果没有真正的了解,许多时候读取文件都要碰运气,想要深入了解,可以从下面几点入手:

  1. 了解 user.dir 的含义和作用(System.getProperty("user.dir")),在不同场景下运行查看值。
  2. 了解 XXX.class.getResource 参数和当前 XXX.class 的关系,参数带不带 / 前缀的区别。

到了下一节介绍 版本配置 时,获取到的 URL 还需要区分是 file: 或者是 jar:file:,详细的读取各类文件是一个很复杂的场景,不一定要考虑很全,但是自己定下的规则要考虑全面。

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

Java 通用配置用户配置实现

Java 通用配置版本配置实现

Java 通用配置版本配置实现

Java 通用配置扩展示例

Java 通用配置扩展示例

Java 通用配置扩展示例