short version:
as per M. Justin answer,
add @GrabConfig(systemClassLoader=true)
to load slf4j-nop
on the systemClassLoader.
However keep reading below if you plan to use this in a production environment.
long version:
from the link in the warning message:
Failed to load class org.slf4j.impl.StaticLoggerBinder
This warning message is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path.
If we look at the classpath of each class loader in the chain, we can see where spring and slf4j jars are.
We can do so adding a bit of code at the end of the provided snippet:
@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
the output would be something like:
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]
we can see that spring-web (and its dependencies) are present in groovy.lang.GroovyClassLoader
classpath.
However, slf4j-api is also present in the parent class loader classpath (org.codehaus.groovy.tools.RootLoader
).
Typically classloaders delegate their parents to load a class. Only if the parent fails the classloader tries to load the class from its classpath.
implications:
- when a spring-web class tries to access slf4j-api, it is the RootLoader that loads the class (not GroovyClassLoader)
- when slf4j is initialized, it looks for implementation jars in its classloader classpath (RootLoader) that has no knowledge of slf4j-nop
- slf4j defaults to NOP
When we add the additional configuration @GrabConfig(systemClassLoader=true)
systemClassLoader doc, we instruct to put the grapes dependencies on the RootLoader as well:
Set to true if you want to use the system classloader when loading the grape. This is normally only required when a core Java class needs to reference the grabbed classes, e.g. for a database driver accessed using 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
output:
- [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]
slf4j-api now can find an SLF4J binding on the classpath.
WARNING note: it's worth mentioning that there are now 2 versions of slf4j-api in the RootLoader and wether slf4j-api-1.7.32
or slf4j-api-1.7.36
is loaded into memory is not necessarily known. It may depend on classloader implementaiton, on the jvm implementation or even the OS.
Also if the choice of which classes are loaded in your environment is deterministic, it remains an implementation detail that may change on different systems or with a platform upgrade.
I would not suggest to keep 2 versions of the same classes into the same classpath for a reliable production environment.