一、布景介绍

某运用在压测进程机器cpu运用率超过80%,经过在线确诊工具进行CPU采样生成的火焰图,看到程序中频频调用environment.getProperty()获取特点值,而其内部调用了JndiPropertySource.getProperty()

经过在线确诊工具进行CPU采样生成的火焰图

经过Environment获取特点文件的值,居然会调用到JNDI服务!!!| 京东云技能团队

问题解决

特点进行缓存,这儿经过@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";

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, boolean)

经过Environment获取特点文件的值,居然会调用到JNDI服务!!!| 京东云技能团队

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

经过Environment获取特点文件的值,居然会调用到JNDI服务!!!| 京东云技能团队

依次循环去获取,取到则回来。这儿能够看出在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`指定)。

详情见官方文档: docs.spring.io/spring-boot…

三、怎么防止运用JNDI

经过上述的剖析,能够得到以下三种办法防止运用JNDI

办法一:经过jar包布置(引荐)

办法二:war包布置,关闭JNDI服务

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

办法三:war包布置,自定义调整PropertySource次序(不引荐)

四、经验总结

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

作者:京东零售 郭宏宇

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