Java 通用配置JVM和环境变量实现

Posted isea533

tags:

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

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

本系列参考实现:

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

在写系列博客时,总有一两篇内容简单到可有可无…

本篇内容先选择了最简单的 JVM 和环境变量进行实现,实现过程中可以了解一个简单的规则,SPI 的配置等等,算是复杂实现前的开胃小菜。

JVM 参数

直接实现前面定义的 Config 接口就可以,系统变量太简单,以至于没有内容可讲:

package io.mybatis.config.defaults;

import io.mybatis.config.Config;

/**
 * 读取环境变量值
 *
 * @author liuzh
 */
public class SystemConfig implements Config 

  @Override
  public String getStr(String key) 
    return System.getProperty(key);
  

  @Override
  public int getOrder() 
    return SYSTEM_ORDER;
  


系统变量 JVM 参数方式是目前优先级最高的配置方式,代码中可以看到是 System.getProperty方式读取的,所以在代码中可以通过 System.setProperty("key", "value");进行设置,只要在 getProperty 前设置了,读取都会有效。

除了代码方式外,JVM 真正常用的地方还是在执行代码时,例如 Spring Boot 可执行 Jar 包运行时:

java -Dkey=value -jar xxxx-boot.jar

假如我们通过系统服务的方式运行 java 程序,通过JVM设置的参数是比较死板的,只是相对配置文件方便了一些,如果是在容器环境运行,由于优先级顺序很高,这种方式还会导致不能通过环境变量进行覆盖,所以JVM覆盖值的用法在云原生环境使用较少。

环境变量

实现比 JVM 复杂了一点点,代码如下:

package io.mybatis.config.defaults;

import io.mybatis.config.Config;

/**
 * 读取环境变量值
 *
 * @author liuzh
 */
public class EnvConfig implements Config 

  @Override
  public String getStr(String key) 
    String value = System.getenv(key);
    if (value != null) 
      return value;
    
    key = key.toUpperCase().replaceAll("\\\\.", "_").replaceAll("-", "");
    return System.getenv(key);
  

  @Override
  public int getOrder() 
    return ENV_ORDER;
  

由于不同操作系统对环境变量有不同的限制或规范,从环境变量取值时一般都先通过原始 key 尝试获取,如果能拿到,就不需要做处理,如果没有再按下面规则转换:

  1. 转换为大写形式
  2. 替换.为下划线_
  3. 去掉所有 -

这里参考的 Spring 规则

在云原生(使用容器镜像或K8s)中,因为配置环境变量非常方便,所以非常有必要支持,而且推荐通过环境变量进行覆盖。

如果你用到了配置中心,可以优先配置中心方式。

在 K8s 中用 ConfigMap 或者挂载 ConfigMap 卷覆盖配置文件都是很方便的选择。

JAVA SPI 配置

src/main/resources 资源目录下面创建 META-INF/services 目录,在新建的目录下面创建 io.mybatis.config.Config 文件,文件名就是接口名,在文件中配置上面两个实现:

io.mybatis.config.defaults.EnvConfig
io.mybatis.config.defaults.SystemConfig

JAVA SPI 初始化

ConfigHelper 中通过 ServiceLoader.load(Config.class) 初始化了 Config 接口的实现:

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()));
      
    
  

这里通过 双重检查锁(Double-Checked Locking)对 private static volatile List<Config> CONFIGS; 进行初始化。

获取所有 Config 实现后,根据 Comparator.comparing(Config::getOrder).reversed() 进行倒序排序,所以这里的数字越大优先级越高。

简单测试

测试代码如下:

public class MainTest 
    public static void main(String[] args) 
        System.out.println(ConfigHelper.getStr("hello"));
        System.out.println(ConfigHelper.getBoolean("enabled"));
    

直接用 main 方法测试,不做任何配置的情况下,运行上面代码输出如下:

null
false

在IDE中配置JVM参数如下:

-Dhello=JVM你好 -Denabled=true

再次运行,输出如下内容:

JVM你好
true

删除JVM参数后,配置环境变量如下(IDEA启动后,直接配置系统环境变量是无效的,需要完全关闭IDEA启动才可能有效,可以直接通过IDEA提供的环境变量方式进行配置):

hello=ENV你好;ENABLED=true

输出内容如下:

ENV你好
true

接下来同时配置 JVM 和环境变量:

此时的输出结果如下:

JVM你好
true

可以看到 JVM 优先级高于环境变量。

本篇内容很简单,如果你对 SPI 不是很熟悉,可以搜索相关的文章进行了解,如果你们现有系统想支持这两种扩展方式,可以参考这里的实现在自己系统中增加这两种读取参数的方式。在后续的文章中会开始介绍用户自定义配置和版本配置,因为涉及到读取文件,相对复杂了很多,难度增加了不少,不好理解时多动手试试。

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

Java 通用配置JVM和环境变量实现

Java 通用配置用户配置实现

Java 通用配置用户配置实现

Java 通用配置用户配置实现

Java 通用配置版本配置实现

Java 通用配置版本配置实现