Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is the “dynamic binding” functionality of SLF4J appropriate to use?

I am intrigued by SLF4J because it appears to be the only Java lib (at least that I can account for) that uses this so-called “dynamic binding” of classes at runtime to define behavior.

By this, I mean that if you include slf4j-api on your compile classpath, you can now access all the API classes (Loggers and LoggerFactories, etc.) contained in that JAR, but their actual runtime behavior is no-op (do nothing) unless you include an “SLF4J binding” on the runtime classpath, such as slf4j-simple (which sends log statements to STDOUT and STDERR), or slf4j-log4j, which then expects Log4J configurations, etc.

Like I stated above, this type of dynamic binding behavior seems to be unique to the SLF4J project.

I am wondering why? In general, outside of logging, what kinds of scenarios warrant this dynamic binding as a solution? To me, it appears to be an alternative to classic dependency injection (Spring, Guice), almost deferring injection to an on-the-fly (“JIT”) determintion of what matching classes are available on the runtime classpath.

So I ask: Is this solution uniquely warranted to only solving logging problems? If so, why? If not, then what other problems warrant this approach as their solution?

like image 736
smeeb Avatar asked Mar 16 '23 07:03

smeeb


1 Answers

Separation of interface and impl with a factory class that dynamically locates an implementation provider is not unique to SLF4J. That pattern is used by many APIs in Java and Java EE. For example, javax.xml.parsers.SAXParserFactory and javax.json.Json.

There are some aspects that are unique to SLF4J's approach:

  1. The default implementation is a no-op. For most APIs, you need the implementation to do something meaningful, so the API considers it a fatal error if there are no providers, but SLF4J chose to make their default implementation be a no-op. I personally would have chosen to make the default implementation minimally log to System.err instead.

  2. The factory implementation uses a static class name to find the implementation class (so all implementations all have the same class name) rather than using reflection. You can observe this by going to https://github.com/qos-ch/slf4j/find/master and typing the t character on the page anywhere to open the file finder and entering the StaticLoggerBinder: you'll find that all the binding implementations use the same class name.

    The disadvantage of this approach are you can only have one implementation (vs reflection where you can load multiple implementation classes), and you must package the implementation on the same classpath as the interfaces (vs reflection where you could load the implementation from the context class loader). However, the latter is actually considered an advantage because application servers tend mishandle the interface/implementation split, which causes problems if an application server and application both include copies of the same library, which was a very common issue with commons-logging, log4j, etc. That issue is not exclusive to logging since it is (or was) common for people to encounter ClassCastException for XML parsing libraries when an application server tries to do XML parsing but accidentally finds the application has packaged their own XML parsing APIs.

    The other advantage to this approach is performance. Since the factory API statically references the implementation, there is no reflection overhead to locate and a provider, and there is no virtual method overhead to invoke the provider. In practice, the JIT will usually end up recognizing that the factory API only ever invokes a single implementation anyway, so it will optimize it. However, this is perhaps why logging differs from other APIs: for logging, you typically call the factory API from your class static initializer, which means you call the factory API many times when your application first starts and then never again. That's a worst case for the JIT: by the time it realizes it needs to optimize the call to the provider, your application is already done calling the logger factory API.

like image 176
Brett Kail Avatar answered Apr 06 '23 12:04

Brett Kail