2019-12-19-深入理解Java的系统属性

概述

本篇的主题将围绕着Java System类的两个方法 getProperties()getenv()进行展开。

年轻人不服就看源码,没有什么是看源码解决不了的,如果有,那就再看一遍源码,老夫写代码就是一把嗦,加特林哒哒哒哒

System类

众所周知,这是Java自带的系统类库,我们可以通过这个类来获取到关于本机系统的一些信息,包括输入输出。

System.out

相信大家经常用这个输出语句,这其实是打印输出流

1
System.out.println("hello World!");

还有一个System.in,跟这个相反,是输入的。

来看一下这部分的源码

1
2
3
4
5
6
7
8
public final class System {
private static native void registerNatives();
public final static PrintStream out = null;
static {
registerNatives();
}
// 省略其它代码。。。
}

可以看到默认的out属性是null,System被加载的时候调用了一个本地方法registerNatives,至于做了什么事,咱也不敢说,咱也不敢问,因为咱也不知道,反正是初始化就对了。哈哈哈

System.getProperties()

在学习一个新东西的时候,我们首先会去看作者写的文档,这样似乎理解起来更加容易

1
2
3
4
5
6
7
8
9
// Determines the current system properties. 确定当前的系统属性
public static Properties getProperties() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertiesAccess();
}

return props;
}

这是这个方法文档的第一句话,不难看出已经表明了这个方法的作用,确定当前的系统属性,返回的是一个Properties,Properties其实跟Map差不多,他继承于HashTable

我们来看看这里面都有啥,实践是检验真理的唯一标准!

输出这段话

1
System.getProperties().forEach((k, v) -> System.out.println(k + "=============== " + v));

result: 只截取了一部分输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sun.cpu.isalist  =============== amd64
sun.desktop =============== windows
sun.io.unicode.encoding =============== UnicodeLittle
sun.cpu.endian =============== little
java.vendor.url.bug =============== http://bugreport.sun.com/bugreport/
file.separator =============== \
java.vendor =============== Oracle Corporation
sun.cpu.isalist =============== amd64
sun.desktop =============== windows
sun.io.unicode.encoding =============== UnicodeLittle
sun.cpu.endian =============== little
java.vendor.url.bug =============== http://bugreport.sun.com/bugreport/
file.separator =============== \
java.vendor =============== Oracle Corporation

可以看到上面输出的都是系统信息,cup信息啊,文件分割符啊等等

代码添加系统属性

那么如何添加系统属性呢?像下面这样,比如我想定义一个MyName的系统属性,他的值是hunybei

1
System.getProperties().setProperty("MyName","hunybei")

虚拟机参数添加系统属性

如果不用代码的形式怎么指定?比如说我程序里定义了一个方法,这个方法必须要求,系统里面要有这个MyName的变量,并且值是hunybei,才可以输出,类似于下面这样

1
2
3
4
5
6
public void printHunybeiInfo(){
String myName = System.getProperties().getProperty("MyName");
if(Objects.equals(myName,"hunybei")){
System.out.println(" My name is hunybei ");
}
}

顺着这个思路我第一时间想到了会不会是Java的命令行参数,我在控制台输入Java命令,给出一堆提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
用法: java [-options] class [args...]
(执行类)
或 java [-options] -jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
-d32 使用 32 位数据模型 (如果可用)
-d64 使用 64 位数据模型 (如果可用)
-server 选择 "server" VM
默认 VM 是 server.

-cp <目录和 zip/jar 文件的类搜索路径>
-classpath <目录和 zip/jar 文件的类搜索路径>
用 ; 分隔的目录, JAR 档案
和 ZIP 档案列表, 用于搜索类文件。
-D<名称>=<值>
设置系统属性
-verbose:[class|gc|jni]
启用详细输出
-version 输出产品版本并退出
-version:<值>
警告: 此功能已过时, 将在
未来发行版中删除。
需要指定的版本才能运行
-showversion 输出产品版本并继续
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已过时, 将在
未来发行版中删除。
在版本搜索中包括/排除用户专用 JRE
-? -help 输出此帮助消息
-X 输出非标准选项的帮助
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
按指定的粒度启用断言
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
禁用具有指定粒度的断言
-esa | -enablesystemassertions
启用系统断言
-dsa | -disablesystemassertions
禁用系统断言
-agentlib:<libname>[=<选项>]
加载本机代理库 <libname>, 例如 -agentlib:hprof
另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<选项>]
按完整路径名加载本机代理库
-javaagent:<jarpath>[=<选项>]
加载 Java 编程语言代理, 请参阅 java.lang.instrument
-splash:<imagepath>
使用指定的图像显示启动屏幕
有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。

我们很快发现了 -D<名称>=<值>这个命令,它对应的解释是设置系统属性,这也正是我们想要的

所以我们可以这样写-DMyName=hunybei

开发工具中添加系统属性

在Idea上是这样添加的

特别注意:在别的地方添加无效呦~看名字是VM options,虚拟机参数的意思

System.getEnv()

这个方法听名字是获取环境变量?没错,还真是这样

看一下这个方法文档中的第一句话

1
2
3
4
5
6
7
8
9
// Returns an unmodifiable string map view of the current system environment.
public static java.util.Map<String,String> getenv() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("getenv.*"));
}

return ProcessEnvironment.getenv();
}

使用网易翻译:返回当前系统环境的不可修改字符串映射视图。

返回的也是一个Map集合

开始实践!!看看里面都有啥

1
System.getEnv().forEach((k, v) -> System.out.println(k + "=============== " + v));

result: 只截取了一部分输出结果

1
2
3
JAVA_HOME  =============== D:\Software\Java\jdk1.8.0_131
MAVEN_HOME =============== D:\Software\Java\Maven\apache-maven-3.6.1
MYSQL_HOME =============== D:\Software\Database\MYSQL\mysql-5.7.27

可以看到,输出的都是我在系统里配置的环境变量。也就是说,这个方法获取的是系统的环境变量集合

代码添加环境变量

1
System.getenv().put("myName","hunybei");

虚拟机参数添加系统属性

此处是假标题。

这是环境变量,跟虚拟机没关系,都是获取的本机系统里的,所以不能添加。。

开发工具中添加系统属性

可以看到我们在Idea中,开发工具默认添加了很多的环境变量

深入Spring源码

UML类图

顶层接口类Environment

1
2
3
4
5
6
7
8
9
10
11
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();

String[] getDefaultProfiles();

/** @deprecated */
@Deprecated
boolean acceptsProfiles(String... var1);

boolean acceptsProfiles(Profiles var1);
}

抽象的AbstractEnvironment

只列出部分属性啥的

1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
protected final Log logger = LogFactory.getLog(this.getClass());
private final Set<String> activeProfiles = new LinkedHashSet();
private final Set<String> defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles());
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver;
}

springProfile原理

可以看到定义了很多的属性,最熟悉的还是spring.profiles.active

在多环境开发下经常使用,大家也不陌生,本地环境指定-Dspring.profiles.active=dev,测试指定

-Dspring.profiles.active=test,这也是一个虚拟机系统属性的一个重要应用,大家是不是恍然大悟了,为什么我添加了这个属性,配置了就生效了。哈哈哈哈

标准实现StandardEnvironment

看一下他的一标准实现,也是Spring中使用最多的

1
2
3
4
5
6
7
8
9
10
11
12
public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

public StandardEnvironment() {
}

protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}
}

this.getSystemProperties()其实也是获取的是System.getProperties();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Map<String, Object> getSystemProperties() {
try {
return System.getProperties();
} catch (AccessControlException var2) {
return new ReadOnlySystemAttributesMap() {
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getProperty(attributeName);
} catch (AccessControlException var3) {
if (AbstractEnvironment.this.logger.isInfoEnabled()) {
AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());
}

return null;
}
}
};
}
}

this.getSystemEnvironment()其实也是获取的是System.getenv();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Map<String, Object> getSystemEnvironment() {
if (this.suppressGetenvAccess()) {
return Collections.emptyMap();
} else {
try {
return System.getenv();
} catch (AccessControlException var2) {
return new ReadOnlySystemAttributesMap() {
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getenv(attributeName);
} catch (AccessControlException var3) {
if (AbstractEnvironment.this.logger.isInfoEnabled()) {
AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());
}

return null;
}
}
};
}
}
}

深入Spring配置属性

其实还有一个很重要的类没给大家说,其实那个UML图长这样

PropertyResolver接口

这个类是Spring封装属性解析的,从名字可以看到。

找一个他的实现类

1
2
3
4
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
@Nullable
private final PropertySources propertySources;
}

可以看到封装了PropertySources这个接口

PropertySources接口

主要实现MutablePropertySources

1
2
3
public class MutablePropertySources implements PropertySources {    
private final List<PropertySource<?>> propertySourceList;
}

PropertySource类

这个类使用来存储应用程序中所有的配置文件,回顾StandardEnvironment类中我们看到,Java的两大配置属性都变成了propertySource

1
2
3
4
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}

在Spring中一切配置文件最终都会变成propertySource类,类似于.properties,.yml结尾的。

相关注解@PropertySource(),导入properties配置文件,不少人肯定用过。

总结

是不是感觉怎么这么多类?因为Spring做了很多封装,这也是为什么Spring框架如此火爆的原因,值得我们去学习!路漫漫其修远兮,吾将上下而求索。

总结

  1. getProperties是获取虚拟机的属性,跟环境变量无关,而且虚拟机可以自行增加修改

  2. getEnv是获取系统环境变量,跟虚拟机无关,并且在虚拟机层不能直接添加

  3. Spring的环境抽象EnvironmentpropertySource一统getProperties,getEnv所有配置文件

Over ~ 谢谢大家!

-------------本文结束 感谢您的阅读-------------
湖南有北 wechat
扫码进群哦~~
你可以对我打赏哦