标准类库 System 是我们日常开发中使用比较多的一个工具类,该类提供了多个比较实用的api,如获取当前时间戳:
long timestamp = System.currentTimeMillis();
除此之外就是另外两个比较实用的工具方法了:
public static Properties getProperties();
public static String getProperty(String key);
public static java.util.Map<String,String> getenv();
public static String getenv(String name);
System.getProperties() 方法
System.getProperties()
方法是用于获取当前操作系统的配置属性,Java 文档对该方法的解释是:Determines the current system properties。
示例:
Properties properties = System.getProperties();
properties 会返回一个系统属性配置集合。Oracle 官网文档列出来部分 Key(如下图),我们可以使用 String getProperty(String key)
方法获取相应 key 对应的值。
文档地址:https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getProperties—
比如下面截图是获取我当前机器的属性值示例:
可以看到 properties 一共有 55 个属性值,当然这只是我当前系统上的属性值,每个人的机器可能不一样。
如果想要获取某个属性值只需要使用 System 提供的 getProperty(String)
方法即可:
public static String getProperty(String key);
比如获取当前工程的目录:
String property = System.getProperty("user.dir");
应用场景
其实 System.getProperties()
方法获取的仅仅是操作系统的属性值,其实还有虚拟机的属性值(即进程)。最典型的应用就是命令行参数,我们经常会看到在启动某些应用程序时经常会看到 java -Dxxx=xx
的命令参数。
比如 SpringBoot 程序在启动时指定激活配置文件(如下),有没有很熟悉的感觉?
java -Dspring.profile.active=dev -jar xxx.jar
或
java -jar --spring.profile.active=dev -jar xxx.jar
其中 -D
表示的是自定义虚拟机参数的意思,英文单词是 defintion。
其实这种在命令行中自定义配置属性的键值对我们就可以使用 System.getProperty("xxx")
进行获取。
比如下面是一个普通 Main 函数,在 IDEA 中调试面板中设置下 JVM 参数:
这样,在运行时就可以使用 System.getProperty("spring.profile.active")
进行获取了,如下截图:
现在似乎就有些明白 SpringBoot 指定命令行配置的原理了。
现在再来看下源码了解下原理。
源码阅读
直接看下 System#getProperties()
方法:
会看到该方法返回的是一个变量 props,而变量又是一个静态变量,并且在该变量下面有一个 native 方法,看这方法名称似乎是用于初始化 props 值的。
当然,这些都是猜的。我们重点是要看下 props 是什么时候被赋值的,找了一圈发现只有一个私有的静态方法 initializeSystemClass()
:
直觉告诉我肯定是程序调用了 initializeSystemClass()
方法进行实现初始化,但是通过搜索一圈居然发现没有调用方。
现在就可以更大胆的推断肯定是在程序启动时 “默认” 调用了 native 方法:
private static native Properties initProperties(Properties props);
但是因为是 native 方法所以无法进行调试,基本上已经确定肯定是程序启动时首先执行了 native 方法,就是无法进行调试而已。所以此路不通!!!!
但是我特别想知道是不是 native 方法 **initProperties(Properties)**
实现的赋值,所以我需要调用 **initializeSystemClass()**
方法进行测试。
所以我就想着实例化一个 System 对象,但是发现只有一个私有的构造方法:
嗯,难不倒我,使用反射破坏一下就好了:
Class<System> systemClass = System.class;
Constructor<?>[] declaredConstructors = systemClass.getDeclaredConstructors();
// 因为只有一个构造方法所以直接获取下标0
Constructor<?> constructor = declaredConstructors[0];
// 暴力一点
constructor.setAccessible(true);
System obj = (System) constructor.newInstance();
然后就利用反射实例化了一个 System 对象,现在我的目标是调用 initializeSystemClass
方法,不管三七二十一,直接暴力开整:
Method initializeSystemClass = systemClass.getDeclaredMethod("initializeSystemClass");
initializeSystemClass.setAccessible(true);
initializeSystemClass.invoke(obj);
通过一顿操作,成功 Debug 进来了:
然后使用 Step Into 操作试图进入 native 这个未知的世界,结果进入了 Hashtable 的 put 方法(Properties extends Hashtable
):
从 put 方法显示的 key value 值我们可以确定,在 native 方法中获取了系统的配置属性,然后调用 put 方法来实现收集系统属性和值。所以我使用了一个条件断点:
最后如下:
虽然路途比较艰巨,但是我们最终还是走到了终点~
props 属性值确实是 native 方法 initProperties(Properties props)
实现的初始化,同时也说明了 native 在类加载时就会被调用。
需要特别说明的一点是,系统并没有真的调用 private static void initializeSystemClass()
方法。但是笔者也没有找到该方法的调用点,既然并没有调用该方法去实现初始化为什么又去提供这个私有的静态方法呢?私有的方法就表示不对外暴露,开发人员也无法去直接使用。
当然,可能 Oracle 的开发人员也有自己的考量吧。
好了,再来看下 System.getenv()
方法:
System.getenv() 方法
getenv()
方法与 getProperties()
方法不同,该方法是用于获取操作系统的环境变量的:
同样的,也有一个 getEnv(String)
方法:
public static String getenv(String name);
在获取环境变量时其实调用的是 ProcessEnvironment 的静态方法 getenv()
:
看下该方法,然后返回的又是一个静态常量:
该值是在静态块中进行赋值:
environ()
是一个 native 方法,Debug 看下:
environ 变量是一个二进制的二维数组。之后进行循环将 key 和 value 放入 map 中,最终进行返回。
基本上呢就这些,至于应用的话与 System.getProperties()
方法基本上一致,就不啰嗦了。
完结,撒花~