前言

如题,在学习 Spring 入门教程 Building a RESTful Web Service,分别使用 gradle 和 maven 进行编译和打包,之后部署到外部容器 Tomcat 中报错。打包主要有 jar 包和 war 包,jar 包可以直接执行,而 war 包适合部署(在外部服务器容器中,如 Tomcat 中会被自动解析成可访问目录)。

jar 包直接运行方法: java -jar target/xxx.jar
maven 指令:

# 编译
mvn compile
# 打包
mvn package
# 运行 
mvn spring-boot:run 

gradle 指令:

# 查看任务
gradle tasks
# 编译 & 打包
gradle build
# 运行
gradle bootRun

本地环境:win7, jdk 1.8,Tomcat 7。

使用 tail -f /d/xampp/tomcat/logs/catalina.2019-12-16.log 查看当天的动态日志,在部署时输出了错误日志,详细错误如下:

...
org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/gs-rest-service-0.1.0]]
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultValidator' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.beanvalidation.LocalValidatorFactoryBean]: Factory method 'defaultValidator' threw exception; nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
...

检查 Spring boot 创建可部署 war 包步骤

整理 Spring boot 官方文档 8.1.2. Packaging Executable Jar and War Files9.17.1. Create a Deployable War File,得到如下步骤(maven 方式):

  • 第一步:改写应用入口类

Because Spring WebFlux does not strictly depend on the Servlet API and applications are deployed by default on an embedded Reactor Netty server, War deployment is not supported for WebFlux applications.

翻译:因为 Spring WebFlux 没有严格依赖 Servlet API ,并且应用被默认部署到一个内置的 Reactor Netty 服务器, webFlux 应用不支持 War 包部署(外部服务器部署)。

之前就听说了 Spring 内嵌了一个服务器,可以不借助外部服务器直接运行,方便编译、调试。内部运行可以使用一些框架定义好的东西,而需要部署到外部服务器(容器),就需要多一些配置。

src/main/java/hello/Application.java

本来的入口类

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

改写后的入口类

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • 下一步:修改构建配置,使之生成 war 包而非 jar 包

pom.xml 中添加 <packaging>war</packaging>

  • 最后一步:为确保内嵌的 servlet 容器不会干扰到 war 包部署的 servlet 容器,需要标记内嵌容器依赖为 provided

在 pom.xml 的依赖声明部分添加一下内容(如果已存在,则修改 scope 值为 provided):

<dependencies>
    <!-- … -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <!-- … -->
</dependencies>

更新 Apache Tomcat 版本

如果严格按照上面的步骤来生成 war 包的,生成过程中也没有其他错误的,可以考虑是否是 Tomcat 版本问题。

Tomcat 和 Java 版本之间存在着支持关系。Tomcat 常用的有三个版本:7.0,8.5,9.0。其中 Tomcat 7.0 支持 Java 6 及之后的版本;Tomcat 8.5 支持 Java 7 及以后的版本;Tomcat 9 支持 Java 8 及之后的版本。

所以最好的对应关系应该是 version['tomcat'] = version['java'] + 1,所以 Tomcat 应该升级到 Tomcat 9。

但世事无绝对,我下了一个 Tomcat 8.5,再部署 war 包就已经没有问题了。