在类路径上有多个 servlet-api 库时出错
Error while having multiple servlet-api libs on classpath
我嵌入了 Spring 启动应用程序 运行 Tomcat。我 运行 它在 docker 容器中。当我尝试按照此 page 关于 spring 在容器中启动并构建分层映像时,我在尝试启动容器时收到下面提到的错误。我知道最好的方法是从我的依赖项中排除旧版本的 servlet-api 但这是不可能的,因为在我这样做时这种依赖项停止工作。不幸的是,我也无法摆脱这种依赖。有没有办法强制 Spring Boot 使用类路径中的特定实现?我试过 Jetty 和 Undertow,docker 成功启动,但使用旧版本的库无法正常工作。
另一个问题是为什么当我只复制 jar 并启动它时它可以工作?
我正在尝试构建的 Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG DEPENDENCY=target/dep
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.test.App"]
有效的 Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
COPY /target/application.jar /app/application.jar
COPY /target/lib /app/lib
ENTRYPOINT ["java", "-jar", "app/application.jar"]
有效的方法需要 pom.xml 中的额外插件才能实现:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<finalName>ttom-osm-converter</finalName>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.tomtom.mep.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
来自 POM 的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
</dependency>
错误:
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [NonLoginAuthenticator[StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[/path]
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.apache.catalina.authenticator.AuthenticatorBase.startInternal(AuthenticatorBase.java:1220)
The following method did not exist:
'java.lang.String javax.servlet.ServletContext.getVirtualServerName()'
The method's class, javax.servlet.ServletContext, is available from the following locations:
jar:file:/app/lib/servlet-api-2.5.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/javax.servlet-api-4.0.1.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/tomcat-embed-core-9.0.29.jar!/javax/servlet/ServletContext.class
It was loaded from the following location:
file:/app/lib/servlet-api-2.5.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of javax.servlet.ServletContext
从'java.lang.String javax.servlet.ServletContext.getVirtualServerName()'的错误中可以看出,在Servlet 3.1中添加了:you should exclude servlet-api:2.5.
使用以下命令:
mvn dependency:tree -Dincludes='*:servlet-api'
这会列出所有 module/dependencies,包括 servlet-api
。
然后从依赖项中排除坏版本:com.test.lib
客户端甚至不应该首先包含它,这意味着依赖项也应该在其中提供。
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
<exclusions> <exclusion> <groupId>...</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions>
</dependency>
请注意 servlet-api
groupId 随着时间的推移发生了变化:这可能是 Maven 没有 select “好” servlet-api
.
的原因之一
我建议您使用 maven-enforcer-plugin 来锁定这些不良依赖项:
<rules>
<bannedDependencies>
<excludes>
<exclude>*:servlet-api:2.5</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
有关详细信息,请参阅 http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html。
现在你提到你的库(com.test.lib
)在没有 servlet-api 2.5 的情况下似乎无法工作,这意味着它使用的代码可能在 Servlet 2.5 和 3.1 之间被删除:你唯一的行动方案是升级您的库,使其不依赖于上述代码:
- 第一个servlet-api (2.5)加载
- Tomcat 和 Spring 引导是针对更高版本编译的
- Tomcat/Spring开机可能会尝试使用高版本新增的方法
- 你还会有另一个错误等等。
您可以访问路径:
~/.m2/repository/javax/servlet/servlet-api
并清除文件夹 serverlet。例如:
rm -rf 2.5
你终于re-run申请了,一切正常。
我嵌入了 Spring 启动应用程序 运行 Tomcat。我 运行 它在 docker 容器中。当我尝试按照此 page 关于 spring 在容器中启动并构建分层映像时,我在尝试启动容器时收到下面提到的错误。我知道最好的方法是从我的依赖项中排除旧版本的 servlet-api 但这是不可能的,因为在我这样做时这种依赖项停止工作。不幸的是,我也无法摆脱这种依赖。有没有办法强制 Spring Boot 使用类路径中的特定实现?我试过 Jetty 和 Undertow,docker 成功启动,但使用旧版本的库无法正常工作。
另一个问题是为什么当我只复制 jar 并启动它时它可以工作?
我正在尝试构建的 Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG DEPENDENCY=target/dep
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.test.App"]
有效的 Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
COPY /target/application.jar /app/application.jar
COPY /target/lib /app/lib
ENTRYPOINT ["java", "-jar", "app/application.jar"]
有效的方法需要 pom.xml 中的额外插件才能实现:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<finalName>ttom-osm-converter</finalName>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.tomtom.mep.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
来自 POM 的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
</dependency>
错误:
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [NonLoginAuthenticator[StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[/path]
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.apache.catalina.authenticator.AuthenticatorBase.startInternal(AuthenticatorBase.java:1220)
The following method did not exist:
'java.lang.String javax.servlet.ServletContext.getVirtualServerName()'
The method's class, javax.servlet.ServletContext, is available from the following locations:
jar:file:/app/lib/servlet-api-2.5.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/javax.servlet-api-4.0.1.jar!/javax/servlet/ServletContext.class
jar:file:/app/lib/tomcat-embed-core-9.0.29.jar!/javax/servlet/ServletContext.class
It was loaded from the following location:
file:/app/lib/servlet-api-2.5.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of javax.servlet.ServletContext
从'java.lang.String javax.servlet.ServletContext.getVirtualServerName()'的错误中可以看出,在Servlet 3.1中添加了:you should exclude servlet-api:2.5.
使用以下命令:
mvn dependency:tree -Dincludes='*:servlet-api'
这会列出所有 module/dependencies,包括 servlet-api
。
然后从依赖项中排除坏版本:com.test.lib
客户端甚至不应该首先包含它,这意味着依赖项也应该在其中提供。
<dependency> <!-- lib contains servlet-api-2.5 -->
<groupId>com.test.lib</groupId>
<artifactId>client</artifactId>
<version>${model.client.version}</version>
<exclusions> <exclusion> <groupId>...</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions>
</dependency>
请注意 servlet-api
groupId 随着时间的推移发生了变化:这可能是 Maven 没有 select “好” servlet-api
.
我建议您使用 maven-enforcer-plugin 来锁定这些不良依赖项:
<rules>
<bannedDependencies>
<excludes>
<exclude>*:servlet-api:2.5</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
有关详细信息,请参阅 http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html。
现在你提到你的库(com.test.lib
)在没有 servlet-api 2.5 的情况下似乎无法工作,这意味着它使用的代码可能在 Servlet 2.5 和 3.1 之间被删除:你唯一的行动方案是升级您的库,使其不依赖于上述代码:
- 第一个servlet-api (2.5)加载
- Tomcat 和 Spring 引导是针对更高版本编译的
- Tomcat/Spring开机可能会尝试使用高版本新增的方法
- 你还会有另一个错误等等。
您可以访问路径:
~/.m2/repository/javax/servlet/servlet-api
并清除文件夹 serverlet。例如:
rm -rf 2.5
你终于re-run申请了,一切正常。