Note: The following words such as "bridge", "conversion", and "binding" are basically the same concept.
log4j-over-slf4j and slf4j-log4j12 are two jar packages related to the java log system. When they appear under the classpath at the same time, they may cause stack overflow exceptions. The exception information is roughly as follows (excerpted from the slf4j official website document Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path, preempting StackOverflowError):
Exception in thread "main" java.lang.StackOverflowError
at java.util.Hashtable.containsKey(Hashtable.java:306)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:36)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)
at org.apache.log4j.Category.<init>(Category.java:53)
at org.apache.log4j.Logger..<init>(Logger.java:35)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)
at org.apache.log4j.Category..<init>(Category.java:53)
at org.apache.log4j.Logger..<init>(Logger.java:35)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
subsequent lines omitted...
Existing logging system
Before analyzing the specific reasons for this exception, it is necessary to quickly understand the existing Java logging system. The following figure is a diagram of the existing Java logging system:
The above picture is not very accurate, but it can clearly display the main structure of the existing Java log system. The Java logging system can be roughly divided into three parts: log facade interface, bridge, and log framework specific implementation.
There are many types of Java logging frameworks. The simplest one is Java's own java.util.logging, and the most classic one is log4j. Later, a logback with better performance than log4j appeared, so other log frameworks are not very commonly used. It is certainly possible for applications to directly use the APIs of these specific log frameworks to meet log output requirements, but since the APIs between each log framework are usually incompatible, doing so makes the application lose the flexibility of replacing the log framework.
A more reasonable option than directly using the specific log framework API is to use the log facade interface. The log facade interface provides a set of APIs that are independent of specific log frameworks. Applications can decouple from specific log frameworks by using these independent APIs, which is similar to JDBC. The earliest log facade interface was commons-logging, but the most popular one at present is slf4j.
The log facade interface itself usually does not have the actual log output capability. It still needs to call the specific log framework API at the bottom, that is, it actually needs to be used in combination with the specific log framework. Since there are many specific log frameworks and are mostly incompatible with each other, if the log facade interface is combined with any log framework, it may require a corresponding bridge, just as the combination between JDBC and various different databases requires a corresponding JDBC driver.
It should be noted that as mentioned earlier, the above picture is not accurate, this is just the main part, and the actual situation is not always a simple one-way line of "Log Facade Interface->Bridge->Log Framework". In fact, independent bridges are sometimes not needed, and it is not just a bridge that converts the log facade API to a specific log framework API. There are also bridges that convert the log framework API to the log facade API.
To put it bluntly, the so-called "bridge" is nothing more than a pseudo implementation of a certain set of APIs. This implementation does not directly complete the functions declared by the API, but calls other APIs with similar functions. This completes the transition from "a set of APIs" to "other APIs". If there are two bridges, A-to-B.jar and B-to-A.jar, then you can imagine what will happen when the application starts calling the API of A or B. This is the basic principle of the stack overflow exception that was first introduced.
slf4j forwarding binding
The above is just a general explanation of the existing Java logging system, and it is not possible to explain the problem in detail. We need to further understand the bridging situation between slf4j and the specific log framework.
slf4j bridges to specific log frame
The following picture comes from the slf4j official website document Binding with a logging framework at deployment time:
You can see that there are many solutions for slf4j to combine with specific log frameworks. Of course, the uppermost layer (green application layer) of each solution is unified. They directly call the API provided by slf4j (light blue abstract API layer) and rely on slf4j-api.jar. Then, if you do the slf4j API downward, it will be very free, and you can use almost all the specific logging frameworks. Note that the second layer in the figure is light blue. Looking at the legend in the lower left corner, we can see that this represents the abstract log API, which means that they are not concrete implementations. If the lower layer is not combined with any specific log framework implementation like the first solution on the left, then the log cannot be output ( it is not sure if it may be output to standard output by default ).
The third layer in the picture is obviously not as neat as the first and second layers, because the specific log framework has begun to be involved here.
First, let’s look at the two lake blue blocks in the middle of the third floor, which are the adapter layer, that is, the bridge. The slf4j-log4j12.jar bridge on the left can tell that it is a bridge from slf4j to log4j based on the name. Similarly, the slf4j-jdk14.jar on the right is a bridge implemented by slf4j to Java native logs. The next layer of them is the corresponding log framework implementation. The implementation code of log4j is log4j.jar, and the jul implementation code is already included in the JVM runtime and does not require a separate jar package.
Let’s look at the remaining three dark blue blocks on the third floor. The three of them are also specific logging framework implementations, but they do not require bridges because they themselves directly implement the slf4j API. Needless to say, slf4j-simple.jar and slf4j-nop.jar, you can tell that one is a simple implementation of slf4j and the other is an empty implementation of slf4j, which is not very useful in normal times. The reason why logback also implements the slf4j API is said to be because logback and slf4j are from the same person, who is also the author of log4j.
All gray jar packages in the third layer have red boxes, which means that they all directly implement the slf4j API. However, the implementation of the slf4j API by the lake blue bridge does not directly output logs, but instead calls the API of other log frameworks.
Other log framework API calls back to slf4j
If only the above bridges from sfl4j to other log frameworks exist, there may not be any problems. But in fact, there is another type of bridge, whose functions are exactly the opposite of the above, they convert the API of other log frameworks to the slf4j API. The following picture comes from the slf4j official website document Bridge legacy APIs:
The above figure shows all three cases that can safely call back to slf4j from other log framework APIs so far.
Taking the first case in the upper left corner as an example, when the underlying slf4j is bridged to the logback framework, the log framework APIs that allow the upper layer to bridge back to slf4j include log4j and jul. Although jcl is not a specific implementation of a logging framework, its API can still be called back to slf4j. To implement the conversion, the method is to replace the original log frame jar with a specific bridge jar listed in the figure. It should be noted that the logback API to the slf4j API does not include the conversion of the logback API, because logback is originally an implementation of the slf4j API.
After reading the three situations, you will find that almost all the APIs of other log frameworks, including the jcl API, can be redirected back to slf4j at will. But there is a only limitation that the log framework that calls back to slf4j cannot be the same as the log framework that slf4j is currently bridging to. This limitation is to prevent A-to-B.jar and B-to-A.jar from appearing in the classpath at the same time, resulting in A and B constantly calling each other recursively, and finally stack overflow. At present, this restriction is not guaranteed by technology, but only by the developer themselves. This is why the official website of slf4j emphasizes that all reasonable methods are only in the three situations in the above picture.
At this point, the principle of the exception shown at the beginning has basically been clear. In addition, from the above figure, we can see that there may be combinations similar to exceptions not only log4j-over-slf4j and slf4j-log4j12. The official website of slf4j also pointed out another pair: jcl-over-slf4j.jar and slf4j-jcl.jar.
Code Example
The previous analysis is theoretical. Even if log4j-over-slf4j and slf4j-log4j12 are used at the same time in the actual code, exceptions may not necessarily occur. The following code calls slf4j's API to output the log, and slf4j is bridged to log4j:
package test;public class HelloWorld {public static void main(String[] args) {org.apache.log4j.BasicConfigurator.configure();org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HelloWorld.class);logger.info("Hello World");}}Configure the classpath to configure the jar package (note that log4j is before log4j-over-slf4j):
In this case, running the test program can output the log normally and there will be no stack overflow exception. But if you adjust the jar order on the classpath to:
Running the test program again will cause a stack overflow exception similar to the initial stack overflow in this article. You can see obvious periodic repetitions:
Sequence chart analysis
The above picture is a detailed call program column diagram of stack overflow. Starting from calling 1, call 1.1, 1.1.1... Finally, when it reaches 1.1.1.1.1 (the last call in the figure), it is found that it is exactly the same as 1, so the subsequent process is completely repeated.
It should be noted that the initial fuse is not only LoggerFactory.getLogger() shown in the figure. There are several other direct calls in the application that can trigger stack overflow exceptions. For example, in the previous example code, the first statement org.apache.log4j.BasicConfigurator.configure() is actually the first statement org.apache.log4j.BasicConfigurator.configure(), but the subsequent mutual infinite recursive call process is basically the same as the previous figure.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.