胖瘦打包和部署

胖瘦打包比较复杂,非必要,不建议使用

简介

在现代软件开发中,如何高效地打包和部署应用程序是一个关键问题。特别是在使用 Java 开发的项目中,选择合适的打包方式可以显著影响应用程序的体积、启动速度以及部署效率。

什么是胖包和瘦包?

  • 胖包:包含所有的依赖 jar 包,包括第三方库和自身代码。这种方式生成的可执行文件较大,但独立性强,方便在不同环境下部署。
  • 瘦包:仅包含自身代码,不包含第三方库。依赖项需在目标环境中预先安装或单独管理。这种方式的优势在于打包文件体积较小,部署更为快速。

瘦包的优势

  • 体积小:瘦包只包含应用程序自身的代码和资源,体积大幅减小。
  • 部署快:由于体积更小,瘦包可以更快速地传输和部署,特别是在网络传输或持续交付场景中。

打包指南

为帮助开发者快速创建和打包 Java 应用,本文将介绍如何将 tio-boot 打包成一个胖包或瘦包的项目,并给出具体的打包和部署指南。

创建工程

创建一个 tio-boot 工程,包含底阿妈的依赖和代码

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <lombok-version>1.18.30</lombok-version>
    <tio-boot.version>1.6.2</tio-boot.version>
    <jfinal-aop.version>1.2.6</jfinal-aop.version>
    <fastjson2.version>2.0.52</fastjson2.version>
    <hotswap-classloader.version>1.2.4</hotswap-classloader.version>
    <final.name>web-hello</final.name>
    <main.class>com.litongjava.full.thin.HelloApp</main.class>
    <assembly>full</assembly>
  </properties>
  <dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>tio-boot</artifactId>
      <version>${tio-boot.version}</version>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>hotswap-classloader</artifactId>
      <version>${hotswap-classloader.version}</version>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>jfinal-aop</artifactId>
      <version>${jfinal-aop.version}</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>${fastjson2.version}</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok-version}</version>
      <optional>true</optional>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

启动类

package com.litongjava.full.thin;

import com.litongjava.jfinal.aop.annotation.AComponentScan;
import com.litongjava.tio.boot.TioApplication;

@AComponentScan
public class HelloApp {
  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    TioApplication.run(HelloApp.class, args);
    long end = System.currentTimeMillis();
    System.out.println((end - start) + "ms");
  }
}

controller

package com.litongjava.full.thin.controller;

import com.litongjava.tio.http.server.annotation.RequestPath;

@RequestPath("/")
public class IndexController {
  @RequestPath()
  public String index() {
    return "index";
  }
}

上面已经完成了一个简单的工程的创建

打包

打包配置

pom.xml 的 profiles 配置如下

pom.xml 中定义不同的 profiles,用于区分开发环境和生产环境。使用 maven-assembly-plugin 插件来创建打包配置,根据需求选择打包成胖包或瘦包。

  • 开发环境 (development):在开发环境中,使用 spring-boot-maven-plugin 插件来打包和运行应用。该配置适用于快速迭代和调试。
  • 生产环境 (assembly):在生产环境中,使用 maven-assembly-plugin 插件来打包应用程序。这种配置支持生成胖包或瘦包,根据需求生成不同的部署包。
  <profiles>
    <!-- development -->
    <profile>
      <id>development</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.4</version>
            <configuration>
              <fork>true</fork>
              <mainClass>${main.class}</mainClass>
              <excludeGroupIds>org.projectlombok</excludeGroupIds>
              <arguments>
                <argument>--mode=dev</argument>
              </arguments>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>

    <!-- assembly -->
    <profile>
      <id>assembly</id>
      <build>
        <plugins>
          <!-- 组装文件并压缩zip插件 -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.3.0</version>
            <configuration>
              <!-- not append assembly id in release file name -->
              <appendAssemblyId>false</appendAssemblyId>
              <descriptors>
                <descriptor>assembly-${assembly}.xml</descriptor>
              </descriptors>
            </configuration>
            <executions>
              <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                  <goal>single</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
        <!-- 多模块项目资源配置在classpath下 -->
        <resources>
          <resource>
            <directory>src/main/resources</directory>
            <includes>
              <include>**/*.xml</include>
              <include>**/*.properties</include>
              <include>**/*.yml</include>
              <include>**/*.conf</include>
            </includes>
          </resource>
        </resources>
      </build>
    </profile>
  </profiles>

assembly-full.xml 配置文件

  • 在项目根目录 assembly-full.xml
  • assembly-full.xml 文件用于定义项目在打包过程中如何组装成最终的部署包。具体来说,它配置了打包过程中的文件和依赖的处理方式。

主要内容

  • 格式 (formats):定义了最终生成的包的格式,这里为 zip 格式。
  • 文件集合 (fileSets):指定了需要打包的文件及其在目标路径中的存放位置,例如将所有的 .jar 文件放在 lib 目录中。
  • 依赖集合 (dependencySets):将项目的所有依赖包复制到 lib 目录下,以确保运行时所需的所有依赖均已包含在包内。
<assembly
	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
	<id>zipPackage</id>
	<formats>
		<format>zip</format>
	</formats>
	<includeBaseDirectory>true</includeBaseDirectory>
	<fileSets>
		<!--所有的jar包 -->
		<fileSet>
			<directory>${project.build.directory}/lib</directory>
			<outputDirectory>lib</outputDirectory>
			<includes>
				<include>*.jar</include>
			</includes>
		</fileSet>

		<!-- linux启动脚本 -->
		<fileSet>
			<directory>${basedir}/src/main/bin</directory>
			<lineEnding>unix</lineEnding>
			<outputDirectory></outputDirectory>
			<!-- 脚本文件在 linux 下的权限设为 755,无需 chmod 可直接运行 -->
			<fileMode>755</fileMode>
			<includes>
				<include>*.sh</include>
			</includes>
		</fileSet>

		<!-- 复制service文件 -->
		<fileSet>
			<directory>${basedir}/src/main/bin</directory>
			<lineEnding>unix</lineEnding>
			<outputDirectory>service</outputDirectory>
			<includes>
				<include>*.service</include>
			</includes>
		</fileSet>

		<!-- windows启动脚本 -->
		<fileSet>
			<directory>${basedir}/src/main/bin</directory>
			<lineEnding>windows</lineEnding>
			<outputDirectory></outputDirectory>
			<includes>
				<include>*.bat</include>
			</includes>
		</fileSet>
	</fileSets>
	<!-- 依赖的 jar 包 copy 到 lib 目录下 -->
	<dependencySets>
		<dependencySet>
			<outputDirectory>lib</outputDirectory>
		</dependencySet>
	</dependencySets>
</assembly>

项目根目录 assembly-thin.xml

<assembly
	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
	<id>zipPackage</id>
	<formats>
		<format>zip</format>
	</formats>
	<includeBaseDirectory>true</includeBaseDirectory>
	<fileSets>
		<fileSet>
			<directory>${project.build.directory}</directory>
			<outputDirectory>lib</outputDirectory>
			<includes>
				<include>*.jar</include>
			</includes>
		</fileSet>
	</fileSets>
</assembly>

打包命令

全量包

set JAVA_HOME=D:\java\jdk1.8.0_121
mvn clean package -DskipTests -Passembly -Dassembly=full

瘦子包:

set JAVA_HOME=D:\java\jdk1.8.0_121
mvn clean package -DskipTests -Passembly -Dassembly=thin

Windows 启动

java -Xverify:none -cp config;lib\*;static ${MAIN_CLASS}

eg

java -Xverify:none -cp config;lib\*;static com.litongjava.full.thin.HelloApp

linux 启动

java -Xverify:none -cp ./config:./lib/*:./static ${MAIN_CLASS}

eg

java -Xverify:none -cp ./config:./lib/*:./static com.litongjava.full.thin.HelloApp

指定启动参数

java -Xverify:none -cp ./config:./lib/*:./static com.litongjava.full.thin.HelloApp --server.port=1003

启动

启动脚本

src/main/bin/tio.sh 是一个 Shell 脚本,用于在 Unix 系统中启动、停止和管理应用程序。它提供了标准的服务管理功能,例如启动、停止、重启和查看状态。 修改 MAIN_CLASS 为工程的实际的主类名称 环境变量设置:脚本首先确定了 JAVA_HOME 和 APP_HOME 等关键变量的路径。 启动 (start):脚本检查应用是否已运行,如果未运行,则启动应用并记录其进程 ID。 停止 (stop):通过进程 ID 停止应用并清理相关的 PID 文件。 重启 (restart):先停止再启动应用。 状态 (status):查看应用的当前运行状态。

#!/bin/sh
# chkconfig: 345 99 01
# description:tio

##########################
# get app home start
###########################
PRG="$0"
while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done
##########################
# get app home end
###########################

##########################
# custom variables start
###########################
MAIN_CLASS=""
JAVA_HOME=""
if [ -z "$JAVA_HOME" ]; then
  JAVA_HOME=${JAVA_HOME:-$(dirname $(readlink -f $(which java)))/..}
fi
APP_HOME=`dirname "$PRG"`
APP_NAME=`basename "$PRG"`
PID_FILE=$APP_HOME/$APP_NAME.pid
CP=$APP_HOME/config:$APP_HOME/lib/*:$APP_HOME/static

# Java 命令行参数,根据需要开启下面的配置,改成自己需要的,注意等号前后不能有空格
# JAVA_OPTS="-Xms256m -Xmx1024m -Dundertow.port=80 -Dundertow.host=0.0.0.0"
# JAVA_OPTS="-Dundertow.port=80 -Dundertow.host=0.0.0.0"

###########################
# custom variables end
###########################

#########################
# define funcation start
##########################
lock_dir=/var/lock/subsys
lock_file=$lock_dir/$APP_NAME
createLockFile(){
  [ -w $lock_dir ] && touch $lock_file
}

start(){
  # Check if Java command exists
  if [ ! -x "$JAVA_HOME/bin/java" ]; then
    echo "Error: Java command not found at $JAVA_HOME/bin/java"
    exit 1
  fi

  # Check if MAIN_CLASS is not empty
  if [ -z "$MAIN_CLASS" ]; then
    echo "Error: MAIN_CLASS is not defined. Please provide a valid main class."
    exit 1
  fi

  [ -e $APP_HOME/logs ] || mkdir $APP_HOME/logs -p

  if [ -f $PID_FILE ]
  then
    echo 'already running...'
  else
    CMD="$JAVA_HOME/bin/java -Xverify:none ${JAVA_OPTS} -cp ${CP} ${MAIN_CLASS}"
    echo $CMD
    nohup $CMD >> $APP_HOME/logs/$APP_NAME.log 2>&1 &
    echo $! > $PID_FILE
    createLockFile
    echo "[Start OK]"
  fi
}

stop(){
  if [ -f $PID_FILE ]
  then
    kill `cat $PID_FILE`
    rm -f $PID_FILE
    echo "[Stop OK]"
  else
    echo 'not running...'
  fi
}

restart(){
  stop
  start
}

status(){
  cat $PID_FILE
}


##########################
# define function end
##########################
ACTION=$1
if [ -n "$2" ]; then
  MAIN_CLASS=$2
fi

case $ACTION in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    restart
    ;;
  status)
    status
    ;;
  *)
    echo usage "{start|stop|restart|status} [main class]"
    ;;
esac

开机启动

创建文件 tio.service,请根据需要改成你的工程的名称

[Unit]
Description=tio
After=network.target

[Service]
ExecStart=/opt/tio-hello/tio-hello-1.0/tio.sh start
ExecStop=/opt/tio-hello/tio-hello-1.0/tio.sh stop
ExecReload=/opt/tio-hello/tio-hello-1.0/tio.sh restart
Type=forking
PrivateTmp=true

[Install]
WantedBy=multi-user.target

自动部署和启动

为简化部署流程,可以使用 deploy 工具进行自动化部署。该工具支持多平台,并通过配置文件来定义不同环境的部署步骤。

配置文件

  • .build.txt:定义打包时的环境变量和命令。
  • .deploy.toml:定义部署时的服务器地址、压缩包路径、解压路径等。

.build.txt

[win.env]
set JAVA_HOME=D:\java\jdk1.8.0_121

[win.build]
mvn clean package -DskipTests -Passembly -Dassembly=full

[linux.env]
export JAVA_HOME=/usr/java/jdk1.8.0_121

[linux.build]
mvn clean package -DskipTests -Passembly -Dassembly=full

[mac.env]
set JAVA_HOME=~/java/jdk1.8.0_121

[mac.build]
mvn clean package -DskipTests -Passembly -Dassembly=full

.deploy.toml

[dev.upload-run]
url = "http://192.168.1.2:10405/deploy/file/upload-run/"
p = "123456"
b = ".build.txt"
file = "target/tio-boot-full-thin-demo01-1.0.0.zip"
d = "unzip/tio-boot-full-thin-demo01"
c1 = "mkdir -p /data/apps/tio-boot-full-thin-demo01 && mkdir -p backup/tio-boot-full-thin-demo01"
c2 = "[ -d /data/apps/tio-boot-full-thin-demo01 ] && cp -r /data/apps/tio-boot-full-thin-demo01 backup/tio-boot-full-thin-demo01/$(date +%Y%m%d_%H%M%S)"
c3 = "cp -r unzip/tio-boot-full-thin-demo01/* /data/apps/tio-boot-full-thin-demo01/"
c4 = "if [ $(docker ps -qa -f name=tio-boot-full-thin-demo01) ]; then docker stop tio-boot-full-thin-demo01 && docker rm -f tio-boot-full-thin-demo01; fi"
c5 = "cd /data/apps/tio-boot-full-thin-demo01/tio-boot-full-thin-demo01-1.0.0 && docker run -dit --name tio-boot-full-thin-demo01 --restart=always --net=host -v $(pwd):/app -w /app -e TZ=Asia/Shanghai -e LANG=C.UTF-8 litongjava/jdk:8u391-stable-slim java -Xverify:none -cp ./config:./lib/*:./static com.litongjava.full.thin.HelloApp"
c = "docker ps |grep tio-boot-full-thin-demo01"

胖包部署,执行命令

deploy

瘦包部署 修改.build.txt 将-Dassembly=full 修改为-Dassembly=thin