首页 > 其他分享 >通过Environment获取属性文件的值,竟然会调用到JNDI服务!!!

通过Environment获取属性文件的值,竟然会调用到JNDI服务!!!

时间:2023-12-26 16:14:04浏览次数:31  
标签:服务 调用 Java Environment JNDI 使用 new 属性

一、背景介绍

某应用在压测过程机器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应用来讲

打包方式如果是jar包,因嵌入式Servlet容器通常不支持JNDI,则会抛异常,返回false •打包方式如果是war包,部署到外部Servlet容器(如Tomcat)默认支持JNDI,则会成功,返回true

 

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`指定)。

详情见官方文档: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

 

三、如何避免使用JNDI

通过上述的分析,可以得到以下三种方式避免使用JNDI

方式一:通过jar包部署(推荐)

方式二:war包部署,关闭JNDI服务

java_opts环境变量中添加配置:-Dspring.jndi.ignore=true

方式三:war包部署,自定义调整PropertySource顺序(不推荐)

 

四、经验总结

强烈建议大家使用Forcebot平台压测任务配合在线诊断工具,可以方便的检查出工程中不合理的地方,进行性能优化,降本增效。

 

作者:京东零售 郭宏宇

来源:京东云开发者社区 转载请注明来源

标签:服务,调用,Java,Environment,JNDI,使用,new,属性
From: https://www.cnblogs.com/Jcloud/p/17928350.html

相关文章

  • ArcGIS API for JavaScript 4.x 免登录调用arcgis online私有服务
    APIkeys|ArcGISDevelopers 前言 本来以为普通用户调用服务只能依靠登录,仔细研究了一下可以通过key来实现免登录调用服务。背景最近在做一个BIM结合GIS的Demo,先通过arcgispro将.rvt文件配准到实际位置,然后打包成slpk文件,拖拽到arcgisonline发布出来,最后在前端加载。 ......
  • 手工调用Transaction
    除了用@Transactional外还可以手工调用PlatformTransactionManagertransactionManager=(PlatformTransactionManager)SpringBeanUtil.getBean(PlatformTransactionManager.class);DefaultTransactionDefinitiontransactionDefinition=newDefaultTransactionDe......
  • GB28181视频监控平台LiteCVR调用rtsp地址返回的IP不正确原因排查
    RTSP(Real-TimeStreamingProtocol)是一种用于控制实时流媒体传输的应用层协议。它被设计用于建立和管理客户端与媒体服务器之间的连接,以便实现实时音频、视频或其他交互式媒体内容的传输。RTSP允许客户端通过发送命令来控制流媒体服务器的播放、暂停、快进、倒带等操作。RTSP支持......
  • Windows环境 CMake 配置C++调用Python
    #CMakeLists.txtadd_library(python3STATICIMPORTED)#这里是使用python的安装路径set_target_properties(python3PROPERTIESIMPORTED_LOCATION"D:/python/libs/python39.lib")#使用python的静态库target_link_libraries(TestDemo......
  • 【木棉花】#星计划#在HarmonyOS中调用百度翻译API
    介绍通过http请求和HarmonyOS自带的加密框架,可以为移动应用实现调用百度翻译API的功能。完整示例完整示例链接开发环境要求● DevEcoStudio版本:DevEco Studio 3.1 Release● HarmonyOSSDK版本:API version 9工程要求●API9● Stage模型正文代码结构......
  • Unity3D 中正确调用CUDA程序详解
    Unity3D是一款强大的游戏开发引擎,可以实现各种各样的游戏效果。然而,在某些情况下,使用CPU来处理游戏中的复杂计算任务可能会导致性能瓶颈。为了解决这个问题,我们可以利用CUDA来使用GPU进行并行计算,从而提高游戏的性能。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的......
  • 列表循环 带有栏目的调用
    <!--foreach:{$loop$vo}--><articleclass="excerpt"><aclass="focus"href="{$vo.aurl}"><!--if:{$vo['image']==''}--><imgsrc="__TPL__static/imag......
  • 在Spring Cloud中实现Feign声明式服务调用客户端
    如果你学过SpringCloud,你应该知道我们可以通过OpenFeign从一个服务中调用另一个服务,我们一般采用的方式就是定义一个Feign接口并使用@FeignClient注解来进行标注,feign会默认为我们创建的接口生成一个代理对象。当我们在代码中调用Feign接口的方法的时候,实际上就是在调用我们Feign......
  • 基于TigerBot-13b训练其函数调用能力
    写在前面原生的tigerbot似乎并不支持函数调用,于是我来支持一下 数据集我在huggingface上找了个英文的数据集https://huggingface.co/datasets/sadmoseby/sample-function-call这里面包含了1k组的函数调用,这个数据集的特点如下:1.包含有单个/多个/没有函数调用的情形2.......
  • 慢调用链诊断利器-ARMS 代码热点
    作者:铖朴、义泊可观测技术背景从最早的Google发表的一篇名为《Dapper,aLarge-ScaleDistributedSystemsTracingInfrastructure》的论文开始,到后来以:Metrics(指标)、Tracing(链路追踪)以及Logging(日志)三大方向互为补充的可观测解决方案逐渐被业界所接受并成为事实标准。基......