我们在Java 8下有一个非常大的项目。我暂时需要使用Java 9中引入的feature1来调试问题,所以我的计划是使用大多数当前的工具(可能是11.0.1)构建和运行,添加最少的调用该功能的代码,然后根本不将任何调试更改合并到主干。
面临的挑战是,我不想仅仅为此而构建一个module-info.java
。 (现有的非模块代码实际上在Java SE 11下构建和运行没有任何问题,只是生产用户环境受限于Java 8.因此,我们几乎没有努力尝试模块化这个项目。)
为什么模块可能很重要:我需要使用的功能是System.LoggerFinder
,它是通过ServiceLoader
找到的。使用ServiceLoader
的新方法是通过模块声明,这就是当前服务加载器文档花费大部分时间描述的内容。将文本文件放在META-INF/services/
下的旧方法仍然有效,我们已经成功使用了这些服务和提供程序类,但当前的设置不适用于LoggerFinder
。
我们正在尝试加载的临时类
package com.example.for.stackoverflow;
public class LoggerFinder extends System.LoggerFinder
{
public LoggerFinder()
{
System.err.println("behold, a LoggerFinder");
}
@Override
public System.Logger getLogger (String name, Module module)
{
org.slf4j.Logger real = ...existing function to fetch logging facade...
return new SystemLoggerWrapper (name, real);
}
}
class SystemLoggerWrapper implements System.Logger { ... }
这种包装器实现非常简单,并已在其他地方发布。但是,已发布的示例都使用module-info.java
将ServiceLoader
发送到DTRT。
这个项目的构建系统是用Ant完成的,所以我们在<jar>
任务下添加了行:
<service type="java.lang.System.LoggerFinder">
<provider classname="com.example.for.stackoverflow.LoggerFinder"/>
</service>
这是有效的,因为最终的JAR包含一个包含正确类名的META-INF/services/java.lang.System.LoggerFinder
文本文件。
但是,在运行时,不使用提供的LoggerFinder
。通过查看-verbose:class
的输出,很明显默认值仍在使用中。 ServiceLoader
本身没有例外。运行时类路径已经在接收LoggerFinder
实现所在的JAR(它不是唯一的服务,并且正在找到其他ServiceLoader
-esque提供程序)。
关于这个主题的问题,here和here的现有问题很少,但他们也采取模块化的方式。
有没有办法了解ServiceLoader
正在做什么?这只是一个没有modules-info.java
徒劳的练习吗?
1我正在尝试激活来自sun.util.logging.PlatformLogger
系统的调试消息,该系统(至少从Java 9开始)将通过System.Logger
工作,并且如果 - 根据自己的文档 - 可以通过ServiceLoader找到System.LoggerFinder
。所有这些杂耍都是针对PlatformLogger的目标。
ServiceLoader
在类路径上加载类的方式没有改变1。它仍然通过在META-INF/services
下搜索适当的提供者配置文件来定位提供者。
LoggerFinder
的文档说它搜索system class loader可见的提供者。正如您在评论中提到的那样,提供者是通过-cp
包含的,这应该不是问题。
provider-configuration文件的名称必须是SPI的完全限定二进制名称。来自ServiceLoader
文档:
Deploying service providers on the class path
通过将provider-configuration文件放在资源目录
META-INF/services
中来标识打包为类路径的JAR文件的服务提供程序。 provider-configuration文件的名称是服务的完全限定二进制名称。 provider-configuration文件包含服务提供者的完全限定二进制名称列表,每行一个。
由LoggerFinder
返回的Class.getName
的二进制名称是java.lang.System$LoggerFinder
。基于此,provider-configuration文件的名称应为:
META-INF/services/java.lang.System$LoggerFinder
我对Ant并不熟悉,但我猜你应该改变type
的值来使用完全限定的二进制名称。
<service type="java.lang.System$LoggerFinder">
<provider classname="com.example.for.stackoverflow.LoggerFinder"/>
</service>
注意:当我自己测试时,我最初无法让系统使用我的LoggerFinder
实现。当我将提供者配置文件名更改为java.lang.System$LoggerFinder
(最后一次读取文档)时,它按预期工作。不幸的是,我没有Ant可用于测试<jar>
任务的解决方案。
1.除非你使用ServiceLoader.load(ModuleLayer,Class)
,否则它会忽略未命名的模块(即classpath)。