java通用配置用户配置实现(代码片段)

isea533 isea533     2022-11-30     584

关键词:

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通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring(六)扩展示例本系列参考实现:https://gitee.com/mybatis-mapper/config 查看详情

java通用配置扩展示例(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring(六)扩展示例本系列参考实现:https://gitee.com/mybatis-mapper/config 查看详情

java通用配置集成spring(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/my 查看详情

java通用配置集成spring(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/my 查看详情

java通用配置1.1.0版本发布(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring(六)扩展示例本系列参考实现:https://gitee.com/mybatis-mapper/config 查看详情

java通用配置1.1.0版本发布(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring(六)扩展示例本系列参考实现:https://gitee.com/mybatis-mapper/config 查看详情

java通用配置设计(代码片段)

本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config起因有用户提出mybatis-mapper能不能不在字段上加这么多注解,很麻烦,陆陆续续有不少用户都提到了这个。最初设计必须加就是为了防止tk-... 查看详情

java通用配置设计(代码片段)

本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config起因有用户提出mybatis-mapper能不能不在字段上加这么多注解,很麻烦,陆陆续续有不少用户都提到了这个。最初设计必须加就是为了防止tk-... 查看详情

java通用配置设计(代码片段)

本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config起因有用户提出mybatis-mapper能不能不在字段上加这么多注解,很麻烦,陆陆续续有不少用户都提到了这个。最初设计必须加就是为了防止tk-... 查看详情

java通用配置集成spring(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现(五)集成Spring本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config作... 查看详情

java通用配置版本配置实现(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现(三)用户配置实现(四)版本配置实现本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config版本配置设计版本配置是... 查看详情

java通用配置jvm和环境变量实现(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config在写系列博客时,总有一两篇内容简单到可有可无…本篇内容先选择了最简单... 查看详情

java通用配置jvm和环境变量实现(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config在写系列博客时,总有一两篇内容简单到可有可无…本篇内容先选择了最简单... 查看详情

java通用配置jvm和环境变量实现(代码片段)

Java通用配置(一)设计(二)JVM和环境变量实现本系列参考实现:https://gitee.com/mybatis-mapper/confighttps://github.com/mybatis-mapper/config在写系列博客时,总有一两篇内容简单到可有可无…本篇内容先选择了最简单... 查看详情

.netcore中的通用主机——系统配置(代码片段)

ASP.NETCore2.0中的 WebHost(实现 IWebHost 的基类)是用于为进程提供HTTP服务器功能的基础结构项目,例如,如果正在实现MVCWeb应用或WebAPI服务。 它提供ASP.NETCore中所有新的基础结构优点,使用户能够使用依赖关系注入... 查看详情

mybatisplus总结(代码片段)

...询用户根据多个id查询用户根据map集合作为条件查询用户通用Service接口一些操作 查询总记录数批量添加数据MP常用注解雪花算法前言垂直分表水平分表条件构造器继承结构使用条件构造器实现查询操作查询所有用户根据构造器... 查看详情

一个通用的通过触发器实现的,可配置的表修改日志解决方案(代码片段)

在MIS系统中,系统审计功能是很重要的一部分,审计的一部分就是记录数据修改日志。记录数据修改日志有很多种实现方案,有通过后台程序实现的,在修改程序中增加日志代码,也有通过数据库实现的,使用触发器来记录修改... 查看详情

centos7实现vsftpd虚拟用户配置(代码片段)

...理且对系统安全形成隐患,因此可以使用Vsftpd的虚拟用户配置来实现把虚拟用户映射到真实用户并设置相应权限的效果前期准备准备一台Centos7虚拟机,配置IP地址,同步系统时间,关闭防火墙和selinux安装Vsftpd[root@localhost~]#yuminsta... 查看详情