1. 絮絮叨叨
-
以前,在学习maven属性,就有接触过Java系统属性
-
后来,在工作中搭建开源组件本地开发环境时,有在VM Options中使用Java系统属性为应用指定properties文件
-Dconfig=etc/config.properties -Dlog.levels-file=etc/log.properties
-
自己的印象仍然停留在:与Java有关的场景中,
-Dproperty=value
就是在使用Java系统属性传递配置 -
这些场景包括,但不限于:
- 执行maven命令时,通过Java系统属性传递配置,以用于maven属性、profile的激活等
- 执行Java程序时,通过Java系统属性传递值配置,以管理或助力应用程序的运行
- 有些开源组件的代码中,有通过
System.getProperty("property_name")
获取系统属性的值,用于作为某个变量的默认值
-
最近,在阅读之前的博客《maven属性》时,发现自己纯粹就是《maven实战》一书的搬运工,根本没有加入自己的理解或实战
-
而且自己对Java系统属性的了解也很片面,这就激发了自己想要系统学习Java系统属性的想法
2. System类
- 在学习Java系统属性前,可以先关注下系统属性的基础类
System
2.1 System类无法被实例化
- System类的类声明和构造函数如下,可知System类无法被继承,也无法被其他类实例化
public final class System { private System() {} }
- 这样的定义忽然就让人想到了单例中,私有构造函数,以保证该类只有一个实例
- 但仔细一看,System类与单例类有所不同:单例类中,由于其他类无法对其进行实例化,需要自我实例化;而System类中,不存在自我实例化
2.2 System类的有用字段和方法
-
由于System类既不允许其他类对其进行实例化,也不自我实例化,因此System类的有用字段和方法都是
public static
的-
标准输入、标准输出、错误输出stream,大名鼎鼎的
System.in
、System.out
、System.err
public final static InputStream in = null; public final static PrintStream out = null; public final static PrintStream err = null;
-
访问外部定义的属性(Java系统属性)和环境变量的方法,且环境变量只能访问、无权限设置,而Java系统属性可以访问并设置
// 访问、设置Java系统属性的方法 public static Properties getProperties() public static String getProperty(String key) public static String getProperty(String key, String def) public static void setProperties(Properties props) public static String setProperty(String key, String value) // 访问环境变量的方法,环境变量 public static java.util.Map<String,String> getenv() public static String getenv(String name)
-
加载文件和library的方法(从方法注释来看,二者都可以加载native library,但前者是library文件的绝对路径,后者可以只指定library名,虚拟机会自动到
系统lib目录
去加载library)public static void load(String filename) public static void loadLibrary(String libname)
-
快速复制数组部分元素的使用方法,大名鼎鼎的的
native
方法System.arraycopy()
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length)
-
-
除此之外还有很多其他的有用方法:
// System.exit(n),用于快速终止当前正在运行的JVM public static void exit(int status) // 获取当前的时间与UTC时间的1970-01-01 00:00:00差值,计算机的本地系统时间 // 不同的机器系统时间有差异,要是想要准确的耗时,最好使用nanoTime() public static native long currentTimeMillis() // JVM的高分辨率时间源,按照各路大神的说法,以及开源组件的源代码来看,该方法比较时间先后,比currentTimeMillis()靠谱 public static native long nanoTime();
2.3 System类的初始化
-
System类无法实例化,但并不代表其不用初始化。例如,上面提到的in、out、err等重要字段,一开始就是未初始化的
-
线程初始化后(),会调用
initializeSystemClass()
方法初始化System类。注意: 这里的线程是应用程序的main线程private static void initializeSystemClass() { // 初始化与系统属性有关的props字段 props = new Properties(); // native方法,此时的props中没有任何属性,需要由JVM完成内置的系统属性的添加 initProperties(props); ... // 其他的一些初始化操作 // 获取系统属性line.separator的值,用于初始化lineSeparator字段 lineSeparator = props.getProperty("line.separator"); // 设置系统属性:java.version、java.runtime.version、java.runtime.name三个系统属性 sun.misc.Version.init(); // 通过native方法,实现in、out、err的初始化 FileInputStream fdIn = new FileInputStream(FileDescriptor.in); FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); setIn0(new BufferedInputStream(fdIn)); setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding"))); ... // 其他的一些初始化操作 // The main thread is not added to its thread group in the same way as other threads; // we must do it ourselves here. 从该注释可以看出,这里的线程是启动应用程序的main线程 Thread current = Thread.currentThread(); current.getThreadGroup().add(current); ... // 其他的一些初始化操作 }
看到这里,自己有个疑问:in、out、err都是
final static
,后续还能再赋值吗?
- 自定义Test类,模拟System类对out的初始化流程,发现IDE就已经提示错误了
- 猜测: native方法更加底层,可以完成final变量的重新赋值,且与in、out、err有关的setter方法,也佐证了这个猜想
Reassigns the “standard” output stream.// 以下方法可以重分配这些stream public static void setIn(InputStream in) { checkIO(); setIn0(in); } public static void setOut(PrintStream out) { checkIO(); setOut0(out); } public static void setErr(PrintStream err) { checkIO(); setErr0(err); }
initializeSystemClass()是private方法,如何能在线程初始化后被调用?
-
在System类的开头有如下代码
// register the natives via the static initializer. VM will invoke the initializeSystemClass method // to complete the initialization for this class separated from clinit. private static native void registerNatives(); static { registerNatives(); }
-
根据注释,推测System类的初始化流程如下:
- System类加载后,会执行静态语句块。其中,静态语句块调用了registerNatives()这个native方法
- registerNatives()方法中,存在对initializeSystemClass()方法的调用
- 至此,整个System类的初始化完成
3. Java系统属性
-
从initializeSystemClass()方法可知,System类中,使用
props
字段存储系统属性private static Properties props;
3.1 内置的Java系统属性有哪些?
-
所谓内置的系统属性,就是通过initProperties()这个native方法初始化的系统属性(笔者自创 😂 )
private static native Properties initProperties(Properties props)
-
System类注释中,有两个地方罗列了内置的Java内置系统属性:定义props字段时、定义getProperties()方法时
-
两个列表大同小异,现在展示一些笔者自认为比较重要的Java系统属性
属性名 含义 java.version JRE version,笔者的是 1.8.0_192
java.runtime.version 包含build identifier的JRE version,笔者的是`1.8.0_192-b12 java.home Java的安装目录,笔者的是 /path/to/jdk1.8.0_192.jdk/Contents/Home/jre
java.class.path Java classPath java.library.path 加载library的路径列表 os.name OS名,例如 Mac OS X
os.arch OS架构,例如 x86_64
os.version OS版本,例如 10.16
user.name 用户名,登录系统时的名字,如sunrise user.home 用户home目录,mac的 /Users/sunrise
user.dir 用户当前的working directory,笔者发现IDE运行程序时,working dir一般是项目的根目录,如 /Users/sunrise/IdeaProjects/study-project
file.separator 文件分隔符, /
on UNIX,\
on Windows。就是文件路径的分隔符,windows的C:\Documents\
,unix的/Users/sunrise
line.separator 行分隔符, \n
on UNIX,\r\n
on Windowspath.separator java.class.path中的路径分隔符, ;
for windows ,:
for Unix -
从表格罗列的部分系统属性可以看出,系统属性的值并非固定的,会受操作系统(OS)、文件系统、安装的JDK等的影响
-
以path.separator为例,mac的path.separator为
:
,而windows的则是;
,可以从安装JDK,配置环境变量时看出
3.2 获取系统属性
3.2.1 获取所有的系统属性
-
可以通过getProperties()方法,获取所有的系统属性。
public static Properties getProperties() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPropertiesAccess(); } return props; }
-
若存在SecurityManager,会先调用SecurityManager的
checkPropertiesAccess()
方法检测是否允许访问系统属性 -
若不允许访问系统属性,则会抛出
SecurityException
-
想要了解当前系统,有哪些系统属性、以及这些系统属性的值,可以通过getProperties()方法实现
public static void main(String[] args) { Properties properties = System.getProperties(); properties.list(System.out); }
3.2.2 访问指定的系统属性
-
可以通过getProperty()方法访问指定的系统属性,并且支持带默认值的getProperty()方法
public static String getProperty(String key) { checkKey(key); // 要求key不为null或空字符串 ... // 基于SecurityManager的权限校验,省略 return props.getProperty(key); } public static String getProperty(String key, String def) { ... // 前半部分代码,与getProperty(String key)方法一致,省略 return props.getProperty(key, def); }
-
开源组件Presto,通过获取os有关的系统属性,以检测运行环境是否满足要求
private static void verifyOsArchitecture() { String osName = StandardSystemProperty.OS_NAME.value(); // 等价于Syste.getPorperty("os.name") String osArch = StandardSystemProperty.OS_ARCH.value();// 等价于Syste.getPorperty("os.arch") if ("Linux".equals(osName)) { if (!"amd64".equals(osArch) && !"ppc64le".equals(osArch)) { failRequirement("Presto requires amd64 or ppc64le on Linux (found %s)", osArch); } if ("ppc64le".equals(osArch)) { warnRequirement("Support for the POWER architecture is experimental"); } } else if ("Mac OS X".equals(osName)) { if (!"x86_64".equals(osArch)) { failRequirement("Presto requires x86_64 on Mac OS X (found %s)", osArch); } } else { failRequirement("Presto requires Linux or Mac OS X (found %s)", osName); } }
3.2.3 不完善的checkKey()
- checkKey()方法的源码如下,它只校验了key为null或空字符串的情况,并未校验key包含多个空白字符的情况
private static void checkKey(String key) { if (key == null) { throw new NullPointerException("key can't be null"); } if (key.equals("")) { throw new IllegalArgumentException("key can't be empty"); } }
- 笔者认为这里的校验并不完善,包含多个空白字符的key,也不应该被允许
public static void main(String[] args) { System.setProperty(" ", "32"); System.out.println(System.getProperty(" ")); }
3.3 设置系统属性
3.1 覆盖已有的系统属性集
-
通过setProperties()方法,可以以覆盖的形式设置新的系统属性集
-
若传入的系统属性集合为null,则会调用initProperties()这个native方法,初始化为内置的系统属性集
public static void setProperties(Properties props) { ... // 基于SecurityManager的权限校验省略 if (props == null) { props = new Properties(); initProperties(props); } System.props = props; }
-
下面的代码,先设置一个非null的系统属性集,再设置一个为null的系统属性集
public static void main(String[] args) { Properties properties = new Properties(); properties.setProperty("name", "sunrise"); properties.setProperty("age", "27"); // 覆盖System初始化时,设置的内置系统属性集 System.setProperties(properties); System.getProperties().list(System.out); // 设置一个null的系统属性值,会自动转为设置内置的系统属性集 System.setProperties(null); System.getProperties().list(System.out); }
-
执行结果如下:
3.3.2 更改或新增系统属性
-
想要修改某个系统属性,或添加自定义的系统属性,可以通过setProperty()实现
public static String setProperty(String key, String value) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new PropertyPermission(key, SecurityConstants.PROPERTY_WRITE_ACTION)); // 这里的check条件发生了变化 } return (String) props.setProperty(key, value); }
-
下面的代码,通过setProperty()修改了内置系统属性
os.name
的值,并新增了系统属性log.level.config
public static void main(String[] args) { // 修改内置的系统属性 System.out.printf("os.name: %s\n", System.getProperty("os.name")); System.setProperty("os.name", "mac os"); System.out.printf("after update, os.name: %s\n", System.getProperty("os.name")); // 新增自定义的系统属性 System.setProperty("log.level.config", "etc/log.config"); System.out.printf("log.level.config: %s", System.getProperty("log.level.config")); }
3.4 移除系统属性
-
系统属性除了可以覆盖、修改、新增,还可以移除
public static String clearProperty(String key) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new PropertyPermission(key, "write")); // 不一样的权限校验 } return (String) props.remove(key); }
4. 自定义系统属性
-
刚才,setProperty()方法的使用代码示例,就展示了如何自定义系统属性
-
除了通过setProperty()方法显式地自定义系统属性,还可以通过
-Dproperty=value
这个Java命令行选项自定义系统属性 -
property表示属性名,不能带空格;value表示属性值,若value中存在空格,需要使用
" "
将其括起来 -
执行main方法时,配置VM选项,传入name和address两个自定义系统属性
-
在代码中,获取通过Java命令行选项自定义的系统属性
public static void main(String[] args) { // 获取通过-Dproperty=value自定义的系统属性的值 System.out.printf("name: %s\n", System.getProperty("name")); System.out.printf("address: %s\n", System.getProperty("address")); // 为=未定义,返回默认值 System.out.printf("tel: %s\n", System.getProperty("tel", "010-6666888")); // 修改name属性 System.setProperty("name", "jack"); System.out.printf("name: %s\n", System.getProperty("name")); }
-
执行结果如下:
5. 后记
- 感谢以下博客
- System Properties:oracle官方文档,介绍了常用的系统属性、以及如何通过System类访问系统属性
- Java System Properties:对常见的系统属性进行了分类
- Java – How to display all System properties:内置系统属性如何按照key排序并打印
- Java Options:有对
-Dproperty=value
选项的介绍