这类似于 排除从父 pom 继承的子 pom 中的依赖关系,不同之处在于它与
test
与 compile
作用域有关。
我有一个包含
org.slf4j:slf4j-api
依赖项的父 POM,以便所有后代项目都将使用 SLF4J 作为日志记录 API。然后,为了让所有项目都可以有一些用于单元测试的日志记录(无论它们在主要(即非测试)项目的一部分中使用哪种 SLF4J 实现),我包含 SLF4J Simple,但仅在 test
范围内:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
(我理解父 POM 不应声明依赖项而仅使用依赖项管理的观点。虽然我一般不反对,但配置测试是另一回事。我不希望每个子项目都必须声明 JUnit, Hamcrest、Hamcrest 可选、Mockito、简单日志记录等。我们所有项目的测试框架应该是统一的,不需要为了建立一个项目而进行大量的仪式。)
这一切都很好,直到一个项目
Foo
想要使用 Logback 作为 SLF4J 实现。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.1</version>
</dependency>
这对于
Foo
应用程序本身来说效果很好,但现在对于 Foo
测试,突然出现了两个相互竞争的 SLF4J 实现:Logback 和 SLF4J simple。这出现了绑定冲突:
SLF4J: Class path contains multiple SLF4J providers.
SLF4J: Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@363ee3a2]
SLF4J: Found provider [org.slf4j.simple.SimpleServiceProvider@4690b489]
SLF4J: See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual provider is of type [ch.qos.logback.classic.spi.LogbackServiceProvider@363ee3a2]
我需要执行以下操作之一:
ch.qos.logback:logback-classic
依赖项的 POM 中,我需要从父 POM 中排除 org.slf4j:slf4j-simple
。 (这是首选解决方案。)ch.qos.logback:logback-classic
依赖项的 POM 中,我需要指定 ch.qos.logback:logback-classic
适用于所有范围 除了test
范围(以免与 org.slf4j:slf4j-simple
冲突)。我不太明白如何做其中任何一个。有什么想法吗?
一个建议是用
org.slf4j:slf4j-simple
重新声明 <scope>provided</scope>
。因此,项目 pom.xml
的 Foo
看起来像这样:
…
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>provided</scope>
</dependency>
…
不幸的是,这不起作用。 SLF4J 仍然在类路径上看到两个 SLF4J 提供程序,并且显示上面看到的消息。
provided
的范围只是防止依赖关系被传递到其他项目中;它似乎没有将其从当前项目的类路径中删除。
听起来你正在尝试使用错误的工具建造大教堂,但你得到的是异教寺庙而不是大教堂:)
system
范围来覆盖父pom强加的类路径/模块依赖,类似于:<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/../dummy.jar</systemPath>
</dependency>
但是,我不建议这样做
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>org.slf4j:slf4j-simple</classpathDependencyExcludes>
</configuration>
</plugin>
test compile
和test runtime
范围,但是可以模拟这种行为<properties>
<surefire.runtime>${project.build.directory}/surefire-runtime/slf4j-simple-2.0.1.jar</surefire.runtime>
</properties>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-surefire-runtime</id>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.1</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/surefire-runtime/</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<additionalClasspathElements>${surefire.runtime}</additionalClasspathElements>
</configuration>
</plugin>
是的,那里的字太多了,但在我看来,这只是
test runtime
依赖项的正确配置,m.b.值得向surefire项目提交相应的PR - 我相信需要编写大约10个LoC来避免maven-dependency-plugin
配置并能够通过以下方式配置test runtime
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>org.slf4j:slf4j-api:2.0.1</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
在运行时,您会遇到问题,因为 Logback 是类路径上唯一的日志记录实现。没关系。
在
test
范围内,额外的 slf4j-simple
依赖项会导致绑定冲突。这里的解决方案只是让 Maven Surefire 插件(运行测试)设置一个特殊的系统属性来显式选择正确的日志记录实现:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- Suppress warning "Class path contains multiple SLF4J providers" which is caused by additional slf4j-simple dependency in test scope -->
<slf4j.provider>org.slf4j.simple.SimpleServiceProvider</slf4j.provider>
</systemPropertyVariables>
</configuration>
</plugin>