SLF4J/ JPA / JAX-RS 如何找到它们的实现?

How SLF4J/ JPA / JAX-RS find their implementation?

我正在学习 Java,我发现有许多标准化的功能:

让我们以 Sl4j 为例:要将其与 log4j 一起正确使用,我们必须导入 sl4j api、sl4j/log4j 桥和 log4j 实现。

问题 : 在我的class中,我只与Slf4j通信API.

我的应用程序如何知道 log4j 实现? 有人可以解释一下到底发生了什么吗?

此致

如果您正在谈论处理 slf4j 记录器,例如:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FooClass.class);

那么就很简单了:org.slf4j.Logger只是一个接口,它有几个实现。在使用库slf4j-log4j12的情况下,这个接口由class org.slf4j.impl.Log4jLoggerAdapter实现,内部包含

final transient org.apache.log4j.Logger logger;

所以它是一个简单的适配器,它包装您的日志记录请求并在 log4j 记录器对象上调用它们:

public void debug(String msg) {
    logger.log(FQCN, Level.DEBUG, msg, null);
}

更具体地说,正确的 Logger 实现是由 LoggerFactory 产生的,它首先通过

创建 Log4jLoggerFactory
StaticLoggerBinder.getSingleton().getLoggerFactory()

,后者创建所需的 Log4jLoggerAdapter 个实例。


通常它通过适应级别工作,如来自 documentation 的 img 上的图片所示:

Slf4j 可以与 log4j 或任何其他底层日志库一起使用。

log4j 的情况下,它使用 log4j-slf4j-impl.jar,其中包含与 log4j 库通信所必需的 类。

根据 documentation -

SLF4J doesn't resolve the logging implementation at execution, but directly at the compilation with a bridging API. So more than the JAR of SLF4J you need the following JARs : the bridging JAR and the JAR of the implementation. Here is what you get with Log4J :

SLF4J 手册提到了 SLF4J 如何找到要使用的实现:Binding with a logging framework at deployment time

SLF4J 将允许使用实现(Logback、Log4J 等)的东西称为 "SLF4J bindings" :

As mentioned previously, SLF4J supports various logging frameworks. The SLF4J distribution ships with several jar files referred to as "SLF4J bindings", with each binding corresponding to a supported framework.

您拥有的 SLF4J 绑定与 SLF4J 的实现一样多。 当然,一个实现 API 根据其版本可能有不同的 "SLF4J bindings" :

To switch logging frameworks, just replace slf4j bindings on your class path. For example, to switch from java.util.logging to log4j, just replace slf4j-jdk14-1.7.22.jar with slf4j-log4j12-1.7.22.jar.

与实现的绑定不是在运行时而是在 compile-time 执行:每个 SLF4J 绑定在编译时都是硬连线的,以使用一个且仅一个特定的日志记录框架。
因此,您只需在类路径中包含 SLF4J 绑定(例如 slf4j-jdk14-1.7.22.jar)以便 SLF4J 使用它:

SLF4J does not rely on any special class loader machinery. In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework. For example, the slf4j-log4j12-1.7.22.jar binding is bound at compile time to use log4j. In your code, in addition to slf4j-api-1.7.22.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path. Here is a graphical illustration of the general idea.

这就是为什么通常建议永远不要在类路径上放置一个以上的 SLF4J 绑定,因为 SLF4J 并非设计为在运行时选择实现。

OP 问了一个关于在某些不同情况下如何注入实现的一般性问题。

日志记录

如许多答案所述,SLF4J 给出接口,log4j-slf4j 给出实现。

当您使用以下语句时:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    ...
    private static final Logger LOG = LoggerFactory.getLogger(FooBarClass.class);
    ...
    LOG.debug("Foobar");

这就是正在发生的事情:

我们尝试从 LoggerFactory class 中声明的 getLogger 方法中获取 Logger :

public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        }
        ...
    }

所以神奇的事情就发生在那个陈述上:

return StaticLoggerBinder.getSingleton().getLoggerFactory();

因为类路径知道你实现的原因,StaticLoggerBinder实现is provided by log4j。 我们可以注意到,log4j 提供了自己的实现:

private final ILoggerFactory loggerFactory;
...
private StaticLoggerBinder() {
    loggerFactory = new Log4jLoggerFactory();
}

就是这样!

坚持

对于 JPA/Hibernate 部分,您必须包括 hibernate-jpa-apihibernate-*(核心、实体管理器等)。

假设您要创建一个 EntityManagerFactory:

    import javax.persitence.EntityManagerFactory
    import javax.persitence.Persistence;
    ...
    private static EntityManagerFactory EMF = Peristence.createEntityManagerFactory("foobar", null);

至于 ListArrayList,您的类路径由您导入的 JAR 提供接口和实现。

EntityManagerFactory 来自 hibernate-jpa-api,我们有一个 Persistence class。 我们可以注意到 createEntityManagerFactory 方法首先列出所有 providers 并且对于其中的每一个,一个 createEntityManagerFactory 被触发。 这就是 hibernate 的来源。它提供了一个 HibernatePersistenceProviderimplements the PersistenceProvider class.

这就是 Hibernate 的注入方式。