Java 打包 FatJar 方法小结

在函数计算(Aliyun FC)中发布一个 Java 函数,往往需要将函数打包成一个 all-in-one 的 zip 包或者 jar 包。Java 中这种打包 all-in-one 的技术常称之为 Fatjar 技术。本文小结一下 Java 里打包 FatJar 的若干种方法。

什么是 FatJar

FatJar 又称作 uber-Jar,是包含所有依赖的 Jar 包。Jar 包中嵌入了除 java 虚拟机以外的所有依赖。我们知道 Java 的依赖分为两种, 零散的 .class 文件和把多个 .class 文件以 zip 格式打包而成 jar 文件。FatJar 是一个 all-in-one Jar 包。FatJar 技术可以让那些用于最终发布的 Jar 便于部署和运行。

三种打包方法

我们知道 .java 源码文件会被编译器编译成字节码.class 文件。Java 虚拟机执行的是 .class 文件。一个 java 程序可以有很多个 .class文件。这些 .class 文件可以由 java 虚拟机的类装载器运行期装载到内存里。java 虚拟机可以从某个目录装载所有的 .class 文件,但是这些零散的.class 文件并不便于分发。所有 java 支持把零散的.class 文件打包成 zip 格式的 .jar 文件,并且虚拟机的类装载器支持直接装载 .jar 文件。

一个正常的 java 程序会有若干个.class 文件和所依赖的第三方库的 jar 文件组成。

1. 非遮蔽方法(Unshaded)

非遮蔽是相对于遮蔽而说的,可以理解为一种朴素的办法。解压所有 jar 文件,再重新打包成一个新的单独的 jar 文件。

借助 Maven Assembly Plugin 都可以轻松实现非遮蔽方法的打包。

Maven Assembly Plugin

Maven Assembly Plugin 是一个打包聚合插件,其主要功能是把项目的编译输出协同依赖,模块,文档和其他文件打包成一个独立的发布包。使用描述符(descriptor)来配置需要打包的物料组合。并预定义了常用的描述符,可供直接使用。

预定义描述符如下

  • bin 只打包编译结果,并包含 README, LICENSE 和 NOTICE 文件,输出文件格式为 tar.gz, tar.bz2 和 zip。
  • jar-with-dependencies 打包编译结果,并带上所有的依赖,如果依赖的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar。
  • src 打包源码文件。输出格式为 tar.gz, tar.bz2 和 zip。
  • project 打包整个项目,除了部署输出目录 target 以外的所有文件和目录都会被打包。输出格式为 tar.gz, tar.bz2 和 zip。

除了预定义的描述符,用户也可以指定描述符,以满足不同的打包需求。

打包成 uber-jar,需要使用预定义的 jar-with-dependencies 描述符:

在 pom.xml 中加入如下配置

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>CHOOSE LATEST VERSION HERE</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>assemble-all</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions>
</plugin>

Gradle Java plugin

gradle 下打包一个非遮蔽的 jar 包,有不少插件可以用,但是由于 gradle 自身的灵活性,可以直接用 groove 的 dsl 实现。

apply plugin: 'java'jar {from {(configurations.runtime).collect {it.isDirectory() ? it : zipTree(it)}}
}

非遮蔽方法会把所有的 jar 包里的文件都解压到一个目录里,然后在打包同一个 fatjar 中。对于复杂应用很可能会碰到同名类相互覆盖问题。

2. 遮蔽方法(Shaded)

遮蔽方法会把依赖包里的类路径进行修改到某个子路径下,这样可以一定程度上避免同名类相互覆盖的问题。最终发布的 jar 也不会带入传递依赖冲突问题给下游。

Maven Shade Plugin

在 pom.xml 中加入如下配置

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.1.1</version><configuration><!-- put your configurations here --></configuration><executions><execution><phase>package</phase><goals><goal>shade</goal></goals></execution></executions></plugin>

Gradle Shadow plugin

Gradle shadow plugin 使用非常简单,简单声明插件后就可以生效。

plugins {id 'com.github.johnrengelman.shadow' version '2.0.4'id 'java'
}shadowJar {include '*.jar'include '*.properties'exclude 'a2.properties'
}

遮蔽方法依赖修改 class 的字节码,更新依赖文件的包路径达到规避同名同包类冲突的问题,但是改名也会带来其他问题,比如代码中使用 Class.forName 或 ClassLoader.loadClass 装载的类,Shade Plugin 是感知不到的。同名文件覆盖问题也没法杜绝,比如META-INF/services/javax.script.ScriptEngineFactory不属于类文件,但是被覆盖后会出现问题。

3. 嵌套方法(Jar of Jars)

还是一种办法就是在 jar 包里嵌套其他 jar,这个方法可以彻底避免解压同名覆盖的问题,但是这个方法不被 JVM 原生支持,因为 JDK 提供的 ClassLoader 仅支持装载嵌套 jar 包的 class 文件。所以这种方法需要自定义 ClassLoader 以支持嵌套 jar。

Onejar Maven Plugin

One-JAR 就是一个基于上面嵌套 jar 实现的工具。onejar-maven-plugin 是社区基于 onejar 实现的 maven 插件。

<plugin><groupId>com.jolira</groupId><artifactId>onejar-maven-plugin</artifactId><version>1.4.4</version><executions><execution><goals><goal>one-jar</goal></goals></execution></executions>
</plugin>

Spring boot plugin

One-JAR 有点年久失修,好久没有维护了,Spring Boot 提供的 Maven Plugin 也可以打包 Fatjar,支持非遮蔽和嵌套的混合模式,并且支持 maven 和 gradle 。

<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><layout>ZIP</layout><requiresUnpack><dependency><groupId>org.jruby</groupId><artifactId>jruby-complete</artifactId></dependency></requiresUnpack></configuration>
</plugin>
plugins {id 'org.springframework.boot' version '2.0.4.RELEASE'
}bootJar {requiresUnpack '**/jruby-complete-*.jar'
}

requiresUnpack 参数可以定制那些 jar 不希望被解压,采用嵌套的方式打包到 Fatjar 内部。

其打包后的内部结构为

example.jar|+-META-INF|  +-MANIFEST.MF+-org|  +-springframework|     +-boot|        +-loader|           +-<spring boot loader classes>+-BOOT-INF+-classes|  +-mycompany|     +-project|        +-YourClasses.class+-lib+-dependency1.jar+-dependency2.jar

应用的类文件被防止到 BOOT-INF/classes 目录,依赖包被放置到 BOOT-INF/lib 目录。

查看 META-INF/MANIFEST.MF 文件,其内容为

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication

启动类是固定的 org.springframework.boot.loader.JarLauncher,应用程序的入口类需要配置成 Start-Class。这样做的目的主要是为了支持嵌套 jar 包的类装载,替换掉默认的 ClassLoader。

但是函数计算需要的 jar 包是一种打包结构,在服务端运行时会解压开,不会调用 Main-Class。所以自定义 ClassLoader 是不生效的,所以不要使用嵌套 jar 结构,除非在入口函数指定重新定义 ClassLoader 或者 Classpath 以支持 BOOT-INF/classes 和 BOOT-INF/lib 这样的定制化的类路径。

小结

插件构建平台工作机制
maven-assembly-pluginmavenUnshaded
Gradle Java plugingradleUnshaded
maven-shade-pluginmavenShaded
com.github.johnrengelman.shadowgradleShaded
Onejarant, mavenJar of Jars
Spring boot pluginmaven, gradleUnshaded, Jar of Jars

单从 Fatjar 的角度看, Spring boot maven/gradle 做得最精致。但是 jar 包内部的自定义路径解压开以后和函数计算是不兼容的。所以如果用于函数计算打包,建议使用 Unshaded 或者 Shared 的打包方式,但是需要自己注意文件覆盖问题。

参考阅读

  1. https://imagej.net/Uber-JAR
  2. https://softwareengineering.stackexchange.com/questions/297276/what-is-a-shaded-java-dependency
  3. https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/389796.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见问题及解决方案(前端篇)

一、jquery validate 默认校验规则序号 规则 描述1 requiredtrue 必须输入的字段。2 remote "check.php" 使用 ajax 方法调用 check.php 验证输入值。3 emailtrue 必须输入正确格式的电子邮件。4 urltrue 必须输入正确格式的网址。5 datetrue 必须输入正确格式的日期…

本地搜索文件太慢怎么办?用Everything搜索秒出结果(附安装包)

每次用电脑本地的搜索都慢的一批&#xff0c;后来发现了一个搜索利器 基本上搜索任何文件都不用等待。 并且页面非常简洁&#xff0c;也没有任何广告&#xff0c;用起来非常舒服。 软件官网如下&#xff1a; voidtools 官网提供三个版本&#xff0c;用起来差别不大。 网盘链…

2024. 考试的最大困扰度

2024. 考试的最大困扰度 一位老师正在出一场由 n 道判断题构成的考试&#xff0c;每道题的答案为 true &#xff08;用 ‘T’ 表示&#xff09;或者 false &#xff08;用 ‘F’ 表示&#xff09;。老师想增加学生对自己做出答案的不确定性&#xff0c;方法是 最大化 有 连续相…

小程序入口传参:关于带参数的小程序扫码进入的方法

1.使用场景 1.医院场景&#xff1a;比如每个医生一个id&#xff0c;通过带参数二维码&#xff0c;扫码二维码就直接进入小程序医生页面 2.餐厅场景&#xff1a;比如每个菜一个二维码&#xff0c;通过扫码这个菜的二维码&#xff0c;进入小程序后&#xff0c;可以直接点这道菜&a…

python的power bi转换基础

I’ve been having a great time playing around with Power BI, one of the most incredible things in the tool is the array of possibilities you have to transform your data.我在玩Power BI方面玩得很开心&#xff0c;该工具中最令人难以置信的事情之一就是您必须转换数…

感想3-对于业务逻辑复用、模板复用的一些思考(未完)

内容概览&#xff1a; 业务逻辑复用的目的基于现有场景&#xff0c;如何抽象出初步可复用逻辑复用业务逻辑会不会产生过度设计的问题业务逻辑复用的目的 我对于业务逻辑复用的理解是忽略实际业务内容&#xff0c;从交互流程、交互逻辑的角度去归纳、总结&#xff0c;提出通用的…

Git的一些总结

.git 目录结构 |── HEAD|── branches // 分支|── config // 配置|── description // 项目的描述|── hooks // 钩子| |── pre-commit.sample| |── pre-push.sample| └── ...|── info| └── exclude // 类似.gitignore 用于排除文件|── objects // 存储了…

2025. 分割数组的最多方案数

2025. 分割数组的最多方案数 给你一个下标从 0 开始且长度为 n 的整数数组 nums 。分割 数组 nums 的方案数定义为符合以下两个条件的 pivot 数目&#xff1a; 1 < pivot < nnums[0] nums[1] … nums[pivot - 1] nums[pivot] nums[pivot 1] … nums[n -1] 同时…

您是六个主要数据角色中的哪一个

When you were growing up, did you ever play the name game? The modern data organization has something similar, and it’s called the “Bad Data Blame Game.” Unlike the name game, however, the Bad Data Blame Game is played when data downtime strikes and no…

命令查看linux主机配置

查看cpu&#xff1a; # 总核数 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 物理CPU个数 X 每颗物理CPU的核数 X 超线程数# 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l# 查看每个物理CPU中core的个数(即核数) cat /proc/cpui…

C#中全局处理异常方式

using System; using System.Configuration; using System.Text; using System.Windows.Forms; using ZB.QueueSys.Common;namespace ZB.QueueSys {static class Program{/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(){Appli…

5911. 模拟行走机器人 II

5911. 模拟行走机器人 II 给你一个在 XY 平面上的 width x height 的网格图&#xff0c;左下角 的格子为 (0, 0) &#xff0c;右上角 的格子为 (width - 1, height - 1) 。网格图中相邻格子为四个基本方向之一&#xff08;“North”&#xff0c;“East”&#xff0c;“South”…

自定义按钮动态变化_新闻价值的变化定义

自定义按钮动态变化I read Bari Weiss’ resignation letter from the New York Times with some perplexity. In particular, I found her claim that she “was hired with the goal of bringing in voices that would not otherwise appear in your pages” a bit strange: …

Linux记录-TCP状态以及(TIME_WAIT/CLOSE_WAIT)分析(转载)

1.TCP握手定理 2.TCP状态 l CLOSED&#xff1a;初始状态&#xff0c;表示TCP连接是“关闭着的”或“未打开的”。 l LISTEN &#xff1a;表示服务器端的某个SOCKET处于监听状态&#xff0c;可以接受客户端的连接。 l SYN_RCVD &#xff1a;表示服务器接收到了来自客户端请求…

677. 键值映射

677. 键值映射 实现一个 MapSum 类&#xff0c;支持两个方法&#xff0c;insert 和 sum&#xff1a; MapSum() 初始化 MapSum 对象 void insert(String key, int val) 插入 key-val 键值对&#xff0c;字符串表示键 key &#xff0c;整数表示值 val 。如果键 key 已经存在&am…

算法 从 数中选出_算法可以选出胜出的nba幻想选秀吗

算法 从 数中选出Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without …

jQuery表单校验

小小Demo&#xff1a; <script>$(function () {//给username绑定失去焦点事件$("#username").blur(function () {//得到username文本框的值var nameValue $(this).val();//每次清除数据$("table font:first").remove();//校验username是否合法if (n…

5912. 每一个查询的最大美丽值

5912. 每一个查询的最大美丽值 给你一个二维整数数组 items &#xff0c;其中 items[i] [pricei, beautyi] 分别表示每一个物品的 价格 和 美丽值 。 同时给你一个下标从 0 开始的整数数组 queries 。对于每个查询 queries[j] &#xff0c;你想求出价格小于等于 queries[j] …

django-rest-framework第一次使用使用常见问题

2019独角兽企业重金招聘Python工程师标准>>> 记录在第一次使用django-rest-framework框架使用时遇到的问题&#xff0c;为了便于理解在这里创建了Person和Grade这两个model from django.db import models class Person(models.Model):SHIRT_SIZES ((S, Small),(M, …

插入脚注把脚注标注删掉_地狱司机不应该只是英国电影历史数据中的脚注,这说明了为什么...

插入脚注把脚注标注删掉Cowritten by Andie Yam由安迪(Andie Yam)撰写 Hell Drivers”, 1957地狱司机 》电影海报 Data visualization is a great way to celebrate our favorite pieces of art as well as reveal connections and ideas that were previously invisible. Mor…