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 的规范,大多数开源框架中,都采用了类似上面的方式:
- 先从线程上下文获取(需要框架或者人工传递)
- 使用当前类的类加载器
- 使用系统默认的类加载器
大多数情况下这种方式都没有问题,在有特殊 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
单独加载的所有资源。
有关类加载器的内容可以搜索双亲委派模型了解更多。
小结
本文提供的用户配置是一个抽象类,没有具体实现类的情况下无法进行测试,所以本节相关的测试会放到后续具体扩展实现时进行。
对配置文件的加载过程和普通的读取文件类似,如果没有真正的了解,许多时候读取文件都要碰运气,想要深入了解,可以从下面几点入手:
- 了解
user.dir
的含义和作用(System.getProperty("user.dir")
),在不同场景下运行查看值。 - 了解
XXX.class.getResource
参数和当前XXX.class
的关系,参数带不带/
前缀的区别。
到了下一节介绍 版本配置 时,获取到的 URL
还需要区分是 file:
或者是 jar:file:
,详细的读取各类文件是一个很复杂的场景,不一定要考虑很全,但是自己定下的规则要考虑全面。
以上是关于Java 通用配置用户配置实现的主要内容,如果未能解决你的问题,请参考以下文章