基于KOCA的war包打包部署方案

war包

在相对传统的场景下,可能需要将spring boot web应用的包war包,部署到诸如Tomcat等容器中,我们可以借助maven插件完成打包。

打包

修改bootapp的打包方式为war:

<packaging>war</packaging>

在maven依赖列表中更改spring-boot-starter-tomcat 的scope为provided,这样spring-boot-maven-plugin不会将tomact相关写打入WEB-INF/lib,而是将tomcat相关包写入WEB-INF/lib-provided,这样容器在启动war包时只会加载WEB-INF/lib下的依赖,其他文件夹的依赖会被忽略。
不用从koca-boot-standalone-starterkoca-boot-cloud-starter 中显式地排除tomacat相关的包,在依赖列表中重新引入一次即可:

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

添加maven-war-plugin :

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>3.1.0</version>
</plugin>

最好指定版本(3.1.0+),较低版本在没有指定web.xml 时打包可能会报错找不到web.xml,传统的war包需要在web.xml中定义servlet、filter、contextPath等web相关的组件,但在spring boot应用中可以委托给spring来完成,不需要显式定义web.xml,低版本的maven-war-plugin 可以如下配置来避免这个问题:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>3.1.0</version>
  <configuration>
    <!-- 没有web.xml时不报错 -->
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>

如果确实有WEB-INF/web.xml配置,插件打包时会自动打入war包,也可用<webXml>配置指定路径。

对于jbosswildfly 容器,可能还需要单独的配置,因为jboss在JDK9+环境下没有暴露sun.reflectjdk.internal包,导致spring初始化没有无参构造方法的实例时报java.lang.NoSuchMethodException:......<init>():

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-war-plugin</artifactId>
	<configuration>
		<archive>
			<manifestEntries>
                                <!-- 告知jboss暴露相关包 -->
				<Dependencies>jdk.unsupported</Dependencies>
			</manifestEntries>
		</archive>
	</configuration>
</plugin>

另外jboss对日志配置检测非常严格,当classpath有多个Logger实现时,jboss会启动war包失败,比如同时引用了log4j和logback,在IDE或tomcat中最多抛出警告日志,但jboss会直接失败,因此注意排除类路径中日志系统的冲突,只保留一个日志实现。

添加spring-boot-maven-plugin ,此插件用于完成可执行war、启动时注册Servlet等操作:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>default</id>
      <phase>package</phase>
      <goals>
        <goal>build-info</goal>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
</plugin>

由于默认的war包会带上版本(my-project-1.0.0-SNAPSHOT.war ),但是tomcat等容器会直接使用war包包名作为context-path,可以在<build> 标签中指定文件名称:

<build>
	<finalName>myproject</finalName>
</build>

最后调整一下启动类,使用内嵌的tomcat时,spring boot自动将servlet等信息注册到内嵌tomcat,但是打成war包后,又没有配置web.xml,所以需要在启动时把servlet信息注册到容器中:

SpringBootApplication
public class Application extends SpringBootServletInitializer {

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

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

只需启动类继承SpringBootServletInitializer并覆盖configure方法即可。

最后的使用mvn clean package打包后,就可在编译目录输出myproject.war

整个pom配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>szkingdom.yf.koca.parent</groupId>
        <artifactId>koca-parent</artifactId>
        <version>2.4.0-RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>org.example</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <!-- your dependencies -->

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

    <build>
          <finalName>myproject</finalName>

          <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-war-plugin</artifactId>
              <version>3.1.0</version>
              <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
                <archive>
                  <manifestEntries>
                    <Dependencies>jdk.unsupported</Dependencies>
                  </manifestEntries>
                </archive>
              </configuration>
            </plugin>

            <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <executions>
                <execution>
                  <id>default</id>
                  <phase>package</phase>
                  <goals>
                    <goal>build-info</goal>
                    <goal>repackage</goal>
                  </goals>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </build>
</project>

部署

‘Fatwar’

和fatjar一样,使用spring-boot-maven-plugin 的打出的war包也可以不依赖外部servlet 容器,直接使用java -jar a.war

tomcat

将war包拷贝至webapps目录下,重启tomcat,可以通过localhost:${tomcat.port}/${war.package.name}/${api.path} 访问该服务,注意server.portserver.servlet.context-path 配置将失效。

wildfly(jboss/undertow)

将war包拷贝至standalone/deployments目录,重启jboos,可通过127.0.0.1:${jboss.port}/${war.package.name}/${api.path} 访问该服务,注意server.portserver.servlet.context-path 配置将失效。

感谢陈雷分享