愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!

文章目录

  • 一、开场白
    • 1. 程序员打赌的故事
    • 2. 目标: 尽量在不修改代码的情况下将springmvc框架以独立jar方式运行
  • 二、出师不利
    • 方案一、Spring Web工程转Spring Boot
    • 方案二、引入内置服务器jetty
    • 其余备用方案
  • 三、柳暗花明
    • 遇见jetty-runner
    • 测试验证
      • 准备工作:
      • 部署工作
  • 四、再接再厉
    • 一、新建maven工程pom.xml
    • 二、编写核心逻辑
    • 三、测试
  • 五、遗留难题
    • 难难难!!! 小C突然发现这个代码在开发阶段竟然是无法调试的。

一、开场白

1. 程序员打赌的故事

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

    小B说,据我所知,那个B项目是springmvc框架开发的,历史悠久,想把他转换为springboot框架Jar运行,基本上不太可能,除非大动。
    小C说,我看有一定可行性,只是需要时间做技术预研。

    于是,3人约定将这件事情作为一个技术攻坚工作,谁能够先成功解决,另外2人请吃饭。而且可以上报本季度的效率提升之星(获得免费2天的调休时间以及物质奖励)

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-database、Jenkins

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

  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放在同一级目录
愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!_第1张图片
并敲下这个命令:java -jar jetty-runner-9.4.52.v20230823.jar dbtool_simple.war
后台打印出一段日志后,dbtool_simple.war竟然成功启动了
愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!_第2张图片
小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


<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.0modelVersion>
	<groupId>com.flygroupId>
	<artifactId>jetty-runner-extraartifactId>
	<version>1.0.0version>
	<name>jetty-runner-extraname>
	<url>http://maven.apache.orgurl>
	<packaging>jarpackaging>
	<properties>
		<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
		<log4j.version>2.12.1log4j.version>
		<java.version>1.8java.version>
		<skipTests>trueskipTests>
	properties>
	<dependencies>
		<dependency>
			<groupId>org.eclipse.jettygroupId>
			<artifactId>jetty-runnerartifactId>
			<version>9.4.52.v20230823version>
		dependency>
		<dependency>
			<groupId>commons-iogroupId>
			<artifactId>commons-ioartifactId>
			<version>2.5version>
		dependency>
		
		<dependency>
			<groupId>org.apache.logging.log4jgroupId>
			<artifactId>log4j-slf4j-implartifactId>
			<version>${log4j.version}version>
		dependency>
		
		<dependency>
			<groupId>org.projectlombokgroupId>
			<artifactId>lombokartifactId>
			<version>1.18.12version>
			<scope>providedscope>
		dependency>
	dependencies>
	<build>
		<finalName>${project.artifactId}finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.pluginsgroupId>
				<artifactId>maven-compiler-pluginartifactId>
				<version>3.8.1version>
				<configuration>
					<source>1.8source>
					<target>1.8target>
				configuration>
			plugin>
			<plugin>
				<groupId>org.apache.maven.pluginsgroupId>
				<artifactId>maven-shade-pluginartifactId>
				<version>3.4.0version>
				<executions>
					<execution>
						<phase>packagephase>
						<goals>
							<goal>shadegoal>
						goals>
						<configuration>
							<minimizeJar>falseminimizeJar>
							<filters>
								<filter>
									<artifact>*:*artifact>
								filter>
							filters>
							<transformers>
								
								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>com.fly.JettyExtraRunnermainClass>
								transformer>
								
								<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
									<resource>META-INF/services/javax.servlet.ServletContainerInitializerresource>
								transformer>
							transformers>
						configuration>
					execution>
				executions>
			plugin>
		plugins>
	build>
project>

这里我们引入了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("即将添加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
目录结构如下:
愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!_第3张图片
我们来实际运行下:
愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!_第4张图片
愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!_第5张图片

五、遗留难题

难难难!!! 小C突然发现这个代码在开发阶段竟然是无法调试的。

各位大佬,快来帮帮他,提供思路来解决他的难处!!
把这段代码变得聪明起来!!解决问题有帮助的网友,版主会点名感谢!!
俗话说得好: 军功章上有你的一半也有我的一半

你可能感兴趣的:(java,jetty)