Maven分离lib打包,lib包自动同步,加速项目部署

  • 目标
  • 整体步骤
  • Maven配置
  • 1. 分离打包:配置打包、将依赖jar拷贝到外部
  • 2. 编辑MANIFEST.MF
  • 本地<=>服务器lib快速同步
  • 1. 使用前提
  • 2. 实现同步


目标

  1. 每次部署只打包项目本身代码,这样部署会很快,因为包非常小。
  2. 对于依赖的jar包,只有在需要更新时(pom变动时)单独上传到服务器。

整体步骤

分为两步:

  1. 将项目代码和依赖包分开打包,每次只发布项目代码的jar(依赖Jenkins,或者手动拷贝到服务器中)
  2. 每次修改pom依赖后,通过脚本同步一次lib包中的jar到服务器

Maven配置

这一块比较简单,主要目的是为了将项目依赖的jar和项目本身代码分开打包

1. 分离打包:配置打包、将依赖jar拷贝到外部

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <encoding>UTF-8</encoding>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <!--这里是为了将依赖拷贝出来,不打包到项目代码的jar中-->
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <!--存放依赖jar的文件位置,这里配置成在target/lib下-->
                <outputDirectory>${project.build.directory}/lib</outputDirectory>
                <!--不拷贝间接依赖的jar,这里配置成false,表示拷贝间接依赖-->
                <excludeTransitive>false</excludeTransitive>
                <!--去掉依赖jar的版本号,这里不去掉,方便jar的更新同步-->
                <stripVersion>false</stripVersion>
                <!--包含jar的scope范围,此处设置为compile,打击可以根据需要调整-->
                <includeScope>compile</includeScope>
            </configuration>
        </execution>
    </executions>
</plugin>

2. 编辑MANIFEST.MF

这个文件在项目代码打完包的META-INF/MANIFEST.MF。作用是记录jar的Main入口类和所依赖的jar的位置。可以通过解压打完包的jar查看。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <!--主要是为了向添加MANIFEST.MF中添加必要的内容,如下-->
            <manifest>
                <mainClass>com.xxx.xxx.Application</mainClass>
                <!-- 入口程序 -->
                <addClasspath>true</addClasspath>
                <!-- 添加依赖jar路径,这个大家解压打包后的项目jar, 可以观察一下MANIFEST.MF文件内容就明白了 -->
                <!-- 自己动手丰衣足食,实践出真知 -->
                <classpathPrefix>lib/</classpathPrefix>
            </manifest>
        </archive>
    </configuration>
</plugin>

本地<=>服务器lib快速同步

1. 使用前提

  1. 使用物理服务器部署项目的同学,为了方便在开发阶段快速部署可以参考。
  2. 对于使用k8s等方式部署的项目,还得找运维同学配置。
  3. 这个方式主要是我自己开发阶段为了快速部署写的一点代码,主要是java实现。

2. 实现同步

所谓的快速同步,其实就是通过代码自动ssh到服务器上,获取服务器上的jar列表,和本地的jar列表进行比较,将新增的jar拷贝到服务器上,并将服务器上过期的jar删掉的过程。(这里使用Java,基于jsch实现,直接上代码)

  1. 首选引入依赖(因为我只在本地使用,所以scope配置成test)
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
    <scope>test</scope>
</dependency>
  1. 代码实现
    这个代码比较简单,我也做了注释,大家直接看一下吧。
    【注意】:要本地自己先maven package打个包再运行以下代码上传
package sync_lib;

import cn.hutool.core.io.FileUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author xchen
 */
public class SyncLib {

	private static Session session;
	private static ChannelSftp channelSftp;
	//只打印日志,不做上传删除操作(debug用)
	private static final Boolean onlyPrint = true;

	public static void main(String[] args) throws Exception {
	    //初始化ssh连接
		initChannel();
		try {
		    //cd 到服务器上的jar包所在目录
			channelSftp.cd("服务器的lib目录(jar所在目录)");
			//将jar目录下的jar都ls出来,保存到oldJarList中
			Vector ls = channelSftp.ls(".");
			List<String> oldJarList = new ArrayList<>();
			for (int i = 0; i < ls.size(); i++) {
				ChannelSftp.LsEntry o = (ChannelSftp.LsEntry) ls.get(i);
				String filename = o.getFilename();
				if (filename.endsWith(".jar")) {
					oldJarList.add(filename);
				}
			}
			//这个是intellij idea中分离打包后的lib的路径, 大家可以直接改成自己本地的绝对路径
			String newLibPath = SystemUtil.get(SystemUtil.USER_DIR) + "/target/lib/";
			//获取本地最新jar的列表
			List<String> newLibList = FileUtil.listFileNames(newLibPath);

			//对比新老jar列表,找出要新增的jar,删除的jar
			List<String> addJarList = addJarList(oldJarList, newLibList);
			List<String> removeJarList = removeJarList(oldJarList, newLibList);

			//上传新增的jar
			for (String addJarName : addJarList) {
				if (onlyPrint) {
					channelSftp.put(newLibPath + addJarName, addJarName);
				}
				System.out.println("add jar ==> " + addJarName);
			}
			
			//删除过期的jar
			for (String removeJarName : removeJarList) {
				if (onlyPrint) {
					channelSftp.rm(removeJarName);
				}
				System.out.println("remove jar ==> " + removeJarName);
			}
		} finally {
			//关闭ssh连接
			closeChannel();
		}


	}

	public static List<String> removeJarList(List<String> oldLibList, List<String> newLibList) {
		Set<String> newLibSet = new HashSet<>(newLibList);
		return oldLibList.stream().filter(item -> !newLibSet.contains(item)).collect(Collectors.toList());
	}

	public static List<String> addJarList(List<String> oldLibList, List<String> newLibList) {
		Set<String> oldLigSet = new HashSet<>(oldLibList);
		return newLibList.stream().filter(item -> !oldLigSet.contains(item)).collect(Collectors.toList());
	}

	public static void initChannel() throws JSchException {
		//声明JSCH对象
		JSch jSch = new JSch();
		//获取一个Linux会话
		session = jSch.getSession("root", "服务器IP", 22);
		//设置登录密码
		session.setPassword("服务器密码");
		//关闭key的检验
		Properties sshConfig = new Properties();
		sshConfig.put("StrictHostKeyChecking", "no");
		session.setConfig(sshConfig);
		//连接Linux
		session.connect();
		//通过sftp的方式连接
		channelSftp = (ChannelSftp) session.openChannel("sftp");
		channelSftp.connect();
	}

	private static void closeChannel() {
		if (channelSftp != null) {
			channelSftp.disconnect();
		}
		if (session != null) {
			session.disconnect();
		}
	}
}