通过Environment获取属性文件的值,竟然会调用到JNDI服务!!!
一、背景介绍
某应用在压测过程机器cpu使用率超过80%,通过在线诊断工具进行CPU采样生成的火焰图,看到程序中频繁调用environment.getProperty()获取属性值,而其内部调用了JndiPropertySource.getProperty()
通过在线诊断工具进行CPU采样生成的火焰图

问题解决
属性进行缓存,这里通过@Value+set方法注入到静态变量。后使用Forcebot平台进行单机压测(结果):cpu在70%左右,改造前qps550,改造后qps900,性能提升63%
两个疑问
1)为什么从properties文件获取属性值会使用到JNDI服务?
2)如何解决,避免使用JNDI服务获取属性值?
二、为什么会使用到JNDI
1、什么是JNDI
JNDI(Java Naming and Directory Interface)是Java的一种命名和目录服务接口,提供了查找和访问由名称命名的对象的方法。这些对象可以是任何类型的Java对象,例如数据源、消息队列、邮件会话等。
JNDI服务的主要用途有:
1. 资源管理:在Java EE环境中,JNDI通常用于管理和配置资源,如数据库连接池、JMS队列/主题、邮件会话等。这些资源会被配置在服务器级别,并在JNDI环境中注册。然后,应用程序可以通过查找这些资源的JNDI名称来使用它们,而不需要自己管理这些资源的生命周期。
2. 远程对象查找:在分布式系统中,JNDI可以用于查找远程对象,如EJB(Enterprise JavaBeans)对象。这些远程对象会在JNDI环境中注册,然后客户端可以通过查找这些对象的JNDI名称来获取对它们的引用。
3. 目录服务:JNDI可以用于访问各种目录服务,如LDAP(Lightweight Directory Access Protocol)和DNS(Domain Name System)。你可以使用JNDI来查询、更新和删除目录中的条目。
总的来说,JNDI是一种灵活的服务,它可以帮助Java应用程序与各种环境和资源交互,而无需知道这些资源的具体实现细节。
然而对于一些现代的、轻量级的、微服务架构的应用,人们可能会倾向于不使用JNDI,原因主要有以下几点:
1. 复杂性:JNDI是一个强大且灵活的服务,但它也相当复杂。使用JNDI通常需要对Java EE、命名服务和目录服务有深入的了解。对于一些简单的应用,使用JNDI可能会引入不必要的复杂性。
2. 环境依赖:JNDI通常需要运行在Java EE服务器上。这意味着如果你的应用使用了JNDI,那么它可能就无法在没有Java EE服务器的环境(如简单的Java SE环境或轻量级的容器环境)中运行。
3. 测试难度:由于JNDI依赖于运行环境,所以在单元测试或集成测试中模拟JNDI环境可能会很困难。
4. 现代替代方案:许多现代的Java框架,如Spring和Micronaut,提供了更简单、更灵活的方式来管理和配置资源。例如,你可以使用Spring的依赖注入和外部配置功能来配置数据库连接池,而无需使用JNDI。
因此,虽然JNDI仍然在某些场合有其用处,但在许多现代Java应用中,人们可能会选择使用更简单、更灵活的替代方案。
2、JNDI属性源如何被添加的
调用过程:SpringApplication#run(java.lang.String...) -> SpringApplication#prepareEnvironment -> SpringApplication#getOrCreateEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
webApplicationType如果是SERVLET,则创建一个StandardServletEnvironment对象,它继承类StandardEnvironment,而类StandardEnvironment又继承类AbstractEnvironment,构造方法中调用方法customizePropertySources
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
if (logger.isDebugEnabled()) {
logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}
StandardServletEnvironment重写了方法customizePropertySources,此方法中判断如果JNDI服务可用,则会添加JndiPropertySource
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
判断JNDI服务是否可用的方法JndiLocatorDelegate#isDefaultJndiEnvironmentAvailable
public static boolean isDefaultJndiEnvironmentAvailable() {
if (shouldIgnoreDefaultJndiEnvironment) {
return false;
}
try {
new InitialContext().getEnvironment();
return true;
}
catch (Throwable ex) {
return false;
}
}
private static final boolean shouldIgnoreDefaultJndiEnvironment = SpringProperties.getFlag(IGNORE_JNDI_PROPERTY_NAME);
public static final String IGNORE_JNDI_PROPERTY_NAME spring.jndi.ignore默认为null,变量shouldIgnoreDefaultJndiEnvironment则为false。
主要看new InitialContext().getEnvironment()是否会抛异常,对于使用Spring Boot构建的web应用来讲
3、为什么会使用到JNDI属性源
通过environment.getProperty(key)获取属性值,首先会进入AbstractEnvironment#getProperty(String key),解析器是PropertySourcesPropertyResolver,调用方法PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)

ConfigurationPropertySourcesPropertySource会添加到首位(具体在org.springframework.boot.context.properties.source.ConfigurationPropertySources#attach),它其实是一种特殊的属性源,将Environment中所有其他属性源转化为ConfigurationPropertySource并作为自己的属性源。具体在ConfigurationPropertySourcesPropertySource#findConfigurationProperty()方法中获取属性值

依次循环去获取,取到则返回。这里可以看出在PropertySourceList中JndiPropertySource比OriginTrackedMapPropertySource(application.properties)靠前,由于是顺序读取,所以会先从JndiPropertySource中取值,取不到后才会从OriginTrackedMapPropertySource取值。
而JndiPropertySource需要在JNDI服务器查询属性,可能会进行网络通信。如果你的应用没有相关的JNDI配置,那主要在于初始化JNDI上下文以及进行无效的查询操作,这个耗时也会高于从OriginTrackedMapPropertySource的Map内存数据结构中获取。
PropertySource的优先级
Spring Boot中的`PropertySource`的优先级从高到低如下:
1. Devtools全局设置属性(`spring.devtools.*`)(只有在开发者工具存在的情况下)
2. `@TestPropertySource`注解在测试中的属性。
3. `@SpringBootTest#properties`注解在测试中的属性。
4. 命令行参数。
5. `SPRING_APPLICATION_JSON`中的属性(内联JSON嵌入在环境变量中)。
6. `ServletConfig`初始化参数。
7. `ServletContext`初始化参数。
8. `JNDI`属性从`java:comp/env`。
9. Java系统属性(`System.getProperties()`)。
10. 操作系统环境变量。
11. `RandomValuePropertySource`属性只有`random.*`的属性存在。
12. JAR包外部的应用程序配置文件(`application-{profile}.properties`和`YAML`变体)。
13. JAR包内部的应用程序配置文件(`application-{profile}.properties`和`YAML`变体)。
14. 在配置类上的`@PropertySource`注解。
15. 默认属性(使用`SpringApplication.setDefaultProperties`指定)。
三、如何避免使用JNDI
通过上述的分析,可以得到以下三种方式避免使用JNDI
方式一:通过jar包部署(推荐)
方式二:war包部署,关闭JNDI服务
java_opts环境变量中添加配置:-Dspring.jndi.ignore=true
方式三:war包部署,自定义调整PropertySource顺序(不推荐)
四、经验总结
强烈建议大家使用Forcebot平台压测任务配合在线诊断工具,可以方便的检查出工程中不合理的地方,进行性能优化,降本增效。
作者:京东零售 郭宏宇
来源:京东云开发者社区 转载请注明来源
通过Environment获取属性文件的值,竟然会调用到JNDI服务!!!的更多相关文章
- Java实现获取属性文件的参数值
Java实现获取属性文件的参数值 1,属性文件内容(analysis.properties),路径必须在:src根目录下: #client data path analysis.client.data ...
- SCRIPT5007:无法获取属性“show”的值,对象为null或未定义
1.错误描述 SCRIPT5007:无法获取属性"show"的值,对象为null或未定义 dojo.js,行15.字符11808 2.错误原因 requ ...
- 在datagrid中,IE浏览器报错:SCRIPT5007: 无法获取属性“rowspan”的值: 对象为 null 或未定义
项目总采用datagird时,产生界面如下图原本标题上有功能按钮,此时消失 错误:SCRIPT5007: 无法获取属性"rowspan"的值: 对象为 null 或未定义, 造 ...
- XML序列化 判断是否是手机 字符操作普通帮助类 验证数据帮助类 IO帮助类 c# Lambda操作类封装 C# -- 使用反射(Reflect)获取dll文件中的类型并调用方法 C# -- 文件的压缩与解压(GZipStream)
XML序列化 #region 序列化 /// <summary> /// XML序列化 /// </summary> /// <param name="ob ...
- C# -- 使用反射(Reflect)获取dll文件中的类型并调用方法
使用反射(Reflect)获取dll文件中的类型并调用方法 需引用:System.Reflection; 1. 使用反射(Reflect)获取dll文件中的类型并调用方法(入门案例) static v ...
- @value 注解获取属性文件中的值
一.属性文件 db.properties name=jack 二.配置文件 applicationContext.xml <!-- 加载配置文件,该节点只能存在一个,所以用 * ,加载所有属性文 ...
- C# 获取config文件的值
自定义配置文件帮助类 利用ExeConfigurationFileMap类将自定义配置文件转换为Configuration类进行数据读取 代码很简单,就不做扼要说明 /// <summary&g ...
- SpringBoot的配置属性文件*.properties值如何映射到类中使用
想要在JAVA Bean中读取配置文件中的内容有两种方式,可以进行获取到 第一种方式: 1.在默认的配置文件application.properties 中进行设置 Key-Value键值对 com. ...
- Cuba获取属性文件中的配置
最直接的办法是,使用AppContext.getProperty("cuba.trustedClientPassword"); 可以获取到系统中的web模块下的wep-app.pr ...
- spring 通过编程来获取属性文件
配置可以读取属性: <beans profile="dev"> <context:property-placeholder ignore-resource-not ...
随机推荐
- Open Harmony移植:build lite编译构建过程
摘要:本文介绍了build lite 轻量级编译构建系统编译构建过程,调用依赖关系等等. 本文分享自华为云社区<移植案例与原理 - build lite编译构建过程>,作者: zhushy ...
- 【已解决】Uiautomatorviewer报错:Unexpected error while obtaining UI hierarchy java.lang.reflect.InvocationT...
报错 使用Uiautomatorviewer连接真机报错Unexpected error while obtaining UI hierarchy (获取UI层次结构时出现意外错误) android8 ...
- Python使用pandas库读取csv文件,并分组统计的一个例子
代码: # coding=gbk # 从HostWrites.csv读取数据并分组统计 import pandas import datetime print "\r\n从 HostWrit ...
- 【QT Tools】软件多语言国际化翻译的方法与步骤
在Qt的项目开发过程中,有时软件要翻译成多语言版本,这就涉及到国际化方面的操作.虽然Qt对这方面集成了很多工具,操作起来比较方便,本文还是总结一下国际化的方法和步骤,用以备忘和参考. 我们通常在写程序 ...
- Codeforces Round #710 (Div. 3)个人简单题解
补题链接:Here Proble-A. Strange Table 根据 x 确定坐标确定的行数和列数. int main() { ios_base::sync_with_stdio(false), ...
- Android 原生 SQLite 数据库的一次封装实践
本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/CL4MsQEsrWS8n7lhXCOQ_g作者:Li Bingyan 本文主要讲述原生SQLi ...
- 深入浅出开源监控系统Prometheus(上)
本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/4NC4spF6cEvXuIBKVbIU7A作者:ZhangShuo Prometheus是继 ...
- 红黑树是什么?红黑树 与 B+树区别和应用场景?
红黑树是什么?怎么实现?应用场景? 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉树. 意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小 ...
- 特色国风数字孪生智慧大坝 3D 可视化
前言 水利兴,五谷丰.水利作为国民经济稳定和谐的重要部分,不仅有防洪减灾.农业灌溉.城市供水调水.渔业外贸.旅游航运.生态环境等综合应用,水电资源也是至关重要的可持续能源之一.大坝与水库.水电站等水利 ...
- nginx安装 没有网络且缺少基础包的环境下
一.安装 [root@oracle ~]# cd /etc/yum.repos.d/ [root@oracle yum.repos.d]# rm -rf * [root@oracle yum.repo ...