为什么SLF 4J在使用Groovy脚本和@Grab时会输出StaticLoggerBinder警告

ryevplcw  于 2023-04-29  发布在  其他
关注(0)|答案(2)|浏览(144)

我有一个Groovy脚本,它使用Grape@Grab注解指定依赖项。指定的依赖项是spring-web以使用RestTemplate,并依赖于slf4j-nop以避免Failed to load class "org.slf4j.impl.StaticLoggerBinder"警告。

#!/usr/bin/env groovy

@Grab('org.springframework:spring-web:5.3.18')
@Grab('org.slf4j:slf4j-nop:1.7.36')
import org.springframework.web.client.RestTemplate

new RestTemplate().getForObject('http://www.example.com', String)

然而,尽管如此,我仍然得到SLF4J警告:

$ ./restTemplateLoggingTest.groovy 
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

由于它是一个脚本,因此不要输出无关的噪声是很重要的,因为脚本输出可以通过编程方式使用和操作。
当我运行脚本时,我可以做些什么来防止输出此日志记录警告?

ljo96ir5

ljo96ir51#

实验表明,使用@GrabConfig(systemClassLoader=true)将依赖项附加到系统类加载器会导致不再发出日志:

#!/usr/bin/env groovy

@GrabConfig(systemClassLoader=true)
@Grab('org.springframework:spring-web:5.3.18')
@Grab('org.slf4j:slf4j-nop:1.7.36')
import org.springframework.web.client.RestTemplate

new RestTemplate().getForObject('http://www.example.com', String)

我不知道为什么这是原因,虽然我有一些模糊的猜测。
请注意,尽管解决了这个问题,但这并不是GrabConfig#systemClassLoader的Javadocs所描述的用途:
如果您想在加载葡萄时使用系统类加载器,则设置为true。这通常只在核心Java类需要引用抓取的类时才需要,例如例如,用于使用DriverManager访问的数据库驱动程序。

z6psavjg

z6psavjg2#

简短版本:

根据M. Justin的回答,添加@GrabConfig(systemClassLoader=true)以在systemClassLoader上加载slf4j-nop
但是,如果您计划在生产环境中使用它,请继续阅读下面的内容。

长版本:

从警告消息中的链接:
无法加载类org。slf4j.impl.StaticLoggerBinder当org.无法将slf4j.impl.StaticLoggerBinder类加载到内存中。当在类路径上找不到适当的SLF 4J绑定时,就会发生这种情况。
如果我们查看链中每个类加载器的类路径,我们可以看到spring和slf 4j jar在哪里。我们可以在提供的代码片段的末尾添加一段代码来实现:

@Grab('org.springframework:spring-web:5.3.18')
@Grab('org.slf4j:slf4j-nop:1.7.36')
import org.springframework.web.client.RestTemplate

new RestTemplate().getForObject('http://www.example.com', String)

def printClassPath(classLoader) {
  println classLoader.class
  try{
    classLoader.getURLs().each {url-> println "- ${url.toString()}" }
    if (classLoader.parent) { printClassPath(classLoader.parent) }
  } catch(Exception e){
    println "$classLoader ignored"
  }
}
printClassPath this.class.classLoader

输出将类似于:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

- [removed lines for readability]
class groovy.lang.GroovyClassLoader
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-nop/jars/slf4j-nop-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-api/jars/slf4j-api-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.springframework/spring-web/jars/spring-web-5.3.18.jar
- [removed lines for readability]
class org.codehaus.groovy.tools.RootLoader
- file:/Users/[...]/bin/groovy-3.0.13/lib/slf4j-api-1.7.32.jars
- [removed lines for readability]

我们可以看到spring-web(及其依赖项)存在于groovy.lang.GroovyClassLoader classpath中。然而,slf 4j-api存在于父类加载器类路径(org.codehaus.groovy.tools.RootLoader)中。
典型的类加载器委托它们的父类来加载一个类。只有当父类失败时,类加载器才会尝试从类路径加载该类。影响:

  • 当spring-web类尝试访问slf 4j-api时,加载该类的是RootLoader(而不是GroovyClassLoader)
  • 当slf 4j初始化时,它在其类加载器类路径(RootLoader)中查找不知道slf 4j-nop的实现jar
  • slf 4j默认为NOP

当我们添加额外的配置@GrabConfig(systemClassLoader=true) systemClassLoader文档时,我们也指示将葡萄依赖项放在RootLoader上:
如果您想在加载葡萄时使用系统类加载器,则设置为true。这通常只在核心Java类需要引用抓取的类时才需要,例如例如,用于使用DriverManager访问的数据库驱动程序。

@GrabConfig(systemClassLoader=true)
@Grab('org.springframework:spring-web:5.3.18')
@Grab('org.slf4j:slf4j-nop:1.7.36')
import org.springframework.web.client.RestTemplate

new RestTemplate().getForObject('http://www.example.com', String)

def printClassPath(classLoader) {
  println classLoader.class
  try{
    classLoader.getURLs().each {url-> println "- ${url.toString()}" }
    if (classLoader.parent) { printClassPath(classLoader.parent) }
  } catch(Exception e){
    println "$classLoader ignored"
  }
}
printClassPath this.class.classLoader

输出:

- [removed lines for readability]
class groovy.lang.GroovyClassLoader
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-nop/jars/slf4j-nop-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-api/jars/slf4j-api-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.springframework/spring-web/jars/spring-web-5.3.18.jar
- [removed lines for readability]
class org.codehaus.groovy.tools.RootLoader
- file:/Users/[...]/bin/groovy-3.0.13/lib/slf4j-api-1.7.32.jar
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-nop/jars/slf4j-nop-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.slf4j/slf4j-api/jars/slf4j-api-1.7.36.jar
- file:/Users/[...]/.groovy/grapes/org.springframework/spring-web/jars/spring-web-5.3.18.jar
- [removed lines for readability]

slf 4j-api现在可以在类路径上找到SLF 4J绑定。

警告说明:值得一提的是,RootLoader中现在有两个版本的slf 4j-api,slf4j-api-1.7.32slf4j-api-1.7.36是否加载到内存中并不一定知道。它可能依赖于类加载器实现,依赖于jvm实现甚至依赖于操作系统。

此外,如果在环境中加载哪些类的选择是确定性的,那么它仍然是一个实现细节,可能会在不同的系统上或随着平台升级而改变。
我不建议为了一个可靠的生产环境而在同一个类路径中保留相同类的两个版本。

相关问题