springmvc项目部署包独立jar方式运行

一、开场白

1. 程序员打赌的故事

    小A、小B、小C都在一家初创公司工作,小A是系统运维,小B和小C都是后台开发,他们都是能力卓越的IT工程狮。
    某日加班后,三人聚在一起闲聊。小A向其余2人抱怨说,咱们公司开发小组维护的那个历史悠久的B项目,在测试服务上部署太麻烦了,每次更新都需要先停Tomcat服务器,再删掉旧的war和目录,然后上传war包,最后再重启服务。你们看看能不能改造下,最好能象springboot工程一样,上传一个jar,一个命令启停就搞定了,能这样就谢天谢地了!小B、小C你们俩老鸟给我出出点子,毙了这个问题!

    小B撇撇嘴说,据我所知,那个B项目是springmvc框架的,历史悠久,想把他转换为springboot框架Jar运行,基本上不太可能,除非大动。
    小C云淡风轻,不经意的飘出一句,我看有一定可行性,能整!只是需要大老板给点时间做下小小的技术预研。

    于是,3人约定将这件事情作为一个技术攻坚工作,谁能够先成功解决,另外2人请吃饭外加娱乐,具体什么项目到时候再定。而且可以上报本季度的效率提升之星,maybe 能获得2天免费的调休和物质(or 精神、荣誉)奖励。

2. 目标: 尽量在不修改代码的情况下将springmvc项目部署包以独立jar方式运行

二、出师不利

经过一段时间的技术预研,小B、小C他们提出了以下2个主要方案和备用方案

方案一、Spring Web工程转Spring Boot

步骤1:删除web.xml

步骤2:pom.xml导入springboot

步骤3:添加springboot 启动代码,保留springmvc工程xml配置文件,用ImportResource注解引入

参考案例: springmvc-dbutils-redis springmvc-dbutils-to-boot

方案二、引入内置服务器jetty

步骤1:pom.xml导入jetty相关组件,一般包含jetty-webapp、jetty-jsp、jetty-server等。

步骤2:编写启动类,设置jetty启动的各项参数。

参考案例:keta-custom

其余备用方案

自己实现一套web逻辑,可以参考的项目主要有:h2-databaseJenkins

不幸的是以上几种方案,均违背了以下前提:

  1. 代码修改变动太大,对原始代码侵入太强
  2. java web程序一般都是war,改造成boot工程未必就是Jar(其实war如果能独立运行的也是可以接受的)

事情似乎陷入了死胡同。。。

三、柳暗花明

遇见jetty-runner

大家继续寻找方案,这天小C,在查找资料时,忽然在jetty官网看到这样一句话:
Jetty Runner ,This chapter explains how to use the jetty-runner to run your webapps without needing an installation of Jetty.

Deploying a Simple Context Let’s assume we have a very simple webapp that does not need any resources from its environment, nor any configuration apart from the defaults. Starting it is as simple as performing the following:

java -jar jetty-runner.jar simple.war

官网文档:https://eclipse.dev/jetty/documentation/jetty-9/index.html#runner

测试验证

准备工作:

maven中央仓库下载jetty-runner jar
因为本地开发环境是jdk8,所以下载了支持jdk版本为1.8的9.3或者9.4系列版本。

部署工作

小C急忙将之前开发的一个springmvc工程打成的dbtool_simple.war找出来,与jetty-runner.jar放在同一级目录
在这里插入图片描述
并敲下这个命令:java -jar jetty-runner-9.4.52.v20230823.jar dbtool_simple.war
后台打印出一段日志后,dbtool_simple.war竟然成功启动了
在这里插入图片描述
小C激动的都快喊出来了。

四、再接再厉

小C冷静下来之后,看着jar、war陷入了沉思,现在jar、war其实是分离的,其实可以将war打包到jar里面运行,经过一段思考之后,小C提出了如下优化方案:

暂时将此项目命名为jetty-runner-extra,这样项目打出的jar为jetty-runner-extra.jar
我们需要完成如下优化:

  1. 执行 java -jar jetty-runner-extra.jar 搜索jar内存在的war运行,否则给出提示 war不存在,程序终止。
  2. 执行 java -jar jetty-runner-extra.jar --addwar 交互运行,列出当前目录(包含子目录)war供选择添加,添加后生成新的jar,jar执行逻辑同1。
  3. 执行 java -jar jetty-runner-extra.jar --addwar war/simple.war 如指定的war存在,直接添加,添加后生成新的jar,jar执行逻辑同1。否则给出提示 war不存在,程序终止。

说干就干!

一、新建maven工程pom.xml

pom.xml

<?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>
	<groupId>com.fly</groupId>
	<artifactId>jetty-runner-extra</artifactId>
	<version>1.0.0</version>
	<name>jetty-runner-extra</name>
	<url>http://maven.apache.org</url>
	<packaging>jar</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<log4j.version>2.12.1</log4j.version>
		<java.version>1.8</java.version>
		<skipTests>true</skipTests>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.eclipse.jetty</groupId>
			<artifactId>jetty-runner</artifactId>
			<version>9.4.52.v20230823</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.5</version>
		</dependency>
		<!-- slf4j + log4j2 begin -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>${log4j.version}</version>
		</dependency>
		<!-- log4j end -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.12</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.4.0</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<minimizeJar>false</minimizeJar>
							<filters>
								<filter>
									<artifact>*:*</artifact>
								</filter>
							</filters>
							<transformers>
								<!-- 往MANIFEST文件中写入Main-Class是可执行包的必要条件。ManifestResourceTransformer可以轻松实现。 -->
								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>com.fly.JettyExtraRunner</mainClass>
								</transformer>
								<!-- AppendingTransformer 用来处理多个jar包中存在重名的配置文件的合并,尤其是spring -->
								<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
									<resource>META-INF/services/javax.servlet.ServletContainerInitializer</resource>
								</transformer>
							</transformers>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="off" monitorInterval="0">
	<appenders>
		<console name="Console" target="system_out">
			<patternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
		</console>
	</appenders>
	<loggers>
		<root level="INFO">
			<appender-ref ref="Console" />
		</root>
	</loggers>
</configuration>

这里我们引入了jetty-runner、commons-io、log4j2、lombok还有maven-shade-plugin插件,插件主要实现可执行jar的配置。

二、编写核心逻辑

JettyExtraRunner.java

package com.fly;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.eclipse.jetty.runner.Runner;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@SuppressWarnings("deprecation")
public class JettyExtraRunner
{
    private static URL url = JettyExtraRunner.class.getProtectionDomain().getCodeSource().getLocation();
    
    /**
     * 遍历文件或Jar寻找war运行
     * 
     * @param args
     * @throws IOException
     */
    public static void main(String[] args)
        throws IOException
    {
        log.info("类加载路径: {}", url.getPath());
        boolean isJar = url.getPath().endsWith(".jar");
        if (isJar)
        {
            processInJar(args);
        }
    }
    
    /**
     * Jar遍历-寻找war-拷贝-运行
     * 
     * @param args
     * @throws IOException
     */
    private static void processInJar(String[] args)
        throws IOException
    {
        // 调用方式1: java -jar jetty-runner-extra.jar 搜索jar内存在的war运行,否则给出提示 war不存在,程序终止
        if (args.length == 0)
        {
            try (JarFile jarFile = new JarFile(url.getFile()))
            {
                int num = 0;
                Enumeration<JarEntry> entrys = jarFile.entries();
                while (entrys.hasMoreElements())
                {
                    JarEntry jar = entrys.nextElement();
                    String name = jar.getName();
                    if (name.endsWith(".war"))
                    {
                        num++;
                        log.info("即将加载运行:{}", name);
                        try (InputStream is = JettyExtraRunner.class.getResourceAsStream("/" + name))
                        {
                            File file = new File(name);
                            FileUtils.copyInputStreamToFile(is, file);
                            Runner.main(new String[] {file.getCanonicalPath()});
                            return;
                        }
                    }
                }
                if (num == 0)
                {
                    log.error("未发现war文件,程序终止");
                }
            }
            return;
        }
        
        // 调用方式2: java -jar jetty-runner-extra.jar --addwar 交互运行,列出当前目录(包含子目录)war供选择添加
        if (args.length == 1 && "--addwar".equals(args[0]))
        {
            Collection<File> files = FileUtils.listFiles(new File(url.getPath()).getParentFile(), new String[] {"war"}, true);
            if (files.isEmpty())
            {
                log.error("未发现war文件,无法添加");
                return;
            }
            File selected;
            if (files.size() == 1)
            {
                selected = files.toArray(new File[0])[0];
            }
            else
            {
                // 列出->选择
                try (Scanner sc = new Scanner(System.in))
                {
                    int input;
                    do
                    {
                        int index = 1;
                        for (File file : files)
                        {
                            log.info("序号{}: {}", index++, file.getCanonicalPath());
                        }
                        log.info("请输入序号1-{}选择war文件", files.size());
                        input = sc.nextInt();
                    } while (input < 1 || input > files.size());
                    selected = files.toArray(new File[0])[input - 1];
                    log.info("你选择了war文件:{} ", selected.getCanonicalPath());
                }
            }
            addWar(selected);
            return;
        }
        
        // 调用方式3: java -jar jetty-runner-extra.jar --addwar war/simple.war 如指定的war存在,直接添加,否则给出提示 war不存在,程序终止
        if (args.length == 2 && "--addwar".equals(args[0]))
        {
            String path = args[1];
            File war = new File(path);
            if (war.exists())
            {
                log.info("文件:{}", war.getCanonicalPath());
                addWar(war);
            }
            else
            {
                log.error("{} 不存在,程序终止", path);
            }
            return;
        }
    }
    
    /**
     * 添加war到新jar中
     * 
     * @param war
     * @see [类、类#方法、类#成员]
     */
    private static void addWar(File war)
    {
        try
        {
            File srcJar = new File(url.getPath());
            String newJar = srcJar.getCanonicalPath().replace(".jar", DateFormatUtils.format(System.currentTimeMillis(), "_HHmmssSSS") + ".jar");
            addWarToJar(war, srcJar, newJar);
        }
        catch (IOException e)
        {
            log.error(e.getMessage(), e);
        }
    }
    
    /**
     * 将war添加到srcJar并重命名为newJar
     * 
     * @param war
     * @param srcJar
     * @param newJar
     * @throws IOException
     */
    private static void addWarToJar(File war, File srcJar, String newJar)
        throws IOException
    {
        log.info("新文件: {}", newJar);
        log.info("即将添加war文件:{} 到Jar中...", war.getCanonicalPath());
        try (JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(newJar)); JarFile jarFile = new JarFile(srcJar))
        {
            // 遍历jar文件数据写入新jar
            Enumeration<JarEntry> entrys = jarFile.entries();
            while (entrys.hasMoreElements())
            {
                JarEntry jarEntry = entrys.nextElement();
                if (jarEntry != null)
                {
                    jarOutputStream.putNextEntry(jarEntry);
                    try (InputStream entryInputStream = jarFile.getInputStream(jarEntry))
                    {
                        IOUtils.copy(entryInputStream, jarOutputStream);
                    }
                }
            }
            
            // 追加war写入数据
            JarEntry warEntry = new JarEntry("war/" + war.getName());
            jarOutputStream.putNextEntry(warEntry);
            try (InputStream entryInputStream = new FileInputStream(war))
            {
                IOUtils.copy(entryInputStream, jarOutputStream);
            }
        }
    }
}

三、测试

我们在项目的根目录执行mvn clean package 便生成了 jetty-runner-extra.jar
目录结构如下:
在这里插入图片描述
我们来实际运行下:
在这里插入图片描述
在这里插入图片描述

五、遗留难题

难难难!!! 小C突然发现这个代码在开发阶段竟然是无法调试的,啊啊啊,好害怕被关小黑屋。。。。。。

各位大佬,快来帮帮他,提供思路来解决他的难题!!
把这段代码变得聪明起来!!解决问题有帮助的网友,版主会点名感谢!!
众人拾柴火焰高,一个好汉三个帮!!
谁才是聪明代码的缔造者,大家拭目以待!

六、远程调试

代码在开发阶段虽然无法调试,但是打成Jar后开启远程调试还是可以debug的。

假设jar名字为jetty-runner-extra.jar,在JDK5-8调试模式参数命令为:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 -jar jetty-runner-extra.jar --addwar

七,完整代码

springmvc-by-jetty

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值