init
This commit is contained in:
commit
02c03d9b3c
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
|
@ -0,0 +1,28 @@
|
|||
# Build and Release Folders
|
||||
target/
|
||||
|
||||
# Eclipse
|
||||
.settings/
|
||||
*.classpath
|
||||
*.project
|
||||
*.factorypath
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# JRebel
|
||||
rebel.xml
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# svn
|
||||
.svn/
|
||||
|
||||
# vue
|
||||
kernel-standard/
|
||||
node_modules/
|
|
@ -0,0 +1,329 @@
|
|||
Springboot 可执行 Jar 的格式
|
||||
|
||||
spring-boot-loader 模块让 Spring Boot 支持可执行的 jar 和 war 文件。如果使
|
||||
用 Maven 或 Gradle 插件,则会自动生成可执行的 jar,您通常不需要了解它
|
||||
们的工作原理。
|
||||
如果您需要从不同的 build 系统创建可执行 jar,或者您只是对底层技术感到
|
||||
好奇,本附录提供了一些背景知识。
|
||||
|
||||
1. 嵌套 JAR
|
||||
|
||||
Java 没有提供任何标准的方法来加载嵌套的 jar 文件(jar 文件本身包含在另
|
||||
一个 jar 中)。如果您需要分发可以从命令行运行而无需解包的自包含应用程
|
||||
序,这可能会出现问题。如果您需要分发一个可以从命令行运行而不需要解包
|
||||
的自包含的应用程序,那么这可能会有问题。
|
||||
|
||||
为了解决这个问题,许多开发人员使用“shaded” jars。shaded jar 将所有 jar
|
||||
中的所有类打包到一个超级 jar 中。它的问题在于,很难查看应用程序中实际
|
||||
使用了哪些库。如果在多个 jar 中使用相同的文件名(但内容不同),也会出
|
||||
现问题。Spring Boot 采用了不同的方法,让您实际上可以直接嵌套 jar。
|
||||
|
||||
1.1. 可执行 Jar 文件结构
|
||||
Spring Boot Loader 兼容的 jar 文件应按以下方式构建:
|
||||
|
||||
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 目录中。
|
||||
1.2. 可执行 War 文件结构
|
||||
Spring Boot Loader 兼容的 war 文件应按以下方式构建:
|
||||
|
||||
example.war
|
||||
|
|
||||
+-META-INF
|
||||
| +-MANIFEST.MF
|
||||
+-org
|
||||
| +-springframework
|
||||
| +-boot
|
||||
| +-loader
|
||||
|
||||
| +-<spring boot loader classes>
|
||||
|
||||
+-WEB-INF
|
||||
|
||||
+-classes
|
||||
|
||||
| +-com
|
||||
|
||||
| +-mycompany
|
||||
|
||||
| +-project
|
||||
|
||||
| +-YourClasses.class
|
||||
|
||||
+-lib
|
||||
|
||||
| +-dependency1.jar
|
||||
|
||||
| +-dependency2.jar
|
||||
|
||||
+-lib-provided
|
||||
|
||||
+-servlet-api.jar
|
||||
|
||||
+-dependency3.jar
|
||||
|
||||
依赖项应该放在嵌套的 WEB-INF/lib 目录中。运行嵌入式时需要但部署到传统
|
||||
Web 容器时不需要的任何依赖项都应放在 WEB-INF/lib-provided.
|
||||
|
||||
1.3. 索引文件
|
||||
|
||||
Spring Boot Loader 兼容的 jar 和 war 档案可以在 BOOT-INF/目录下包含额外
|
||||
的索引文件。 jars 和 wars 都可以有 classpath.idx 文件,它提供了 jars 应该
|
||||
被添加到 classpath 的顺序。layers.idx 文件只能用于 jar,它允许将 jar 拆分
|
||||
为逻辑层以创建 Docker/OCI 映像。
|
||||
索引文件遵循兼容 YAML 的语法,以便第三方工具可以轻松解析它们。但
|
||||
是,在内部这些文件不会被解析为 YAML,它们必须完全按照下面描述的格式
|
||||
编写才能使用。
|
||||
1.4. Classpath 索引
|
||||
classpath 索引文件可以在 BOOT-INF/classpath.idx, 它提供了一个 jar 名称列
|
||||
表(包括目录),按照它们应该被添加到 classpath 中的顺序。每行必须以破
|
||||
折号空格 ( "-·") 开头,名称必须用双引号括起来。
|
||||
例如,给定以下 jar:
|
||||
|
||||
example.jar
|
||||
|
|
||||
+-META-INF
|
||||
| +-...
|
||||
+-BOOT-INF
|
||||
|
||||
+-classes
|
||||
| +...
|
||||
+-lib
|
||||
|
||||
+-dependency1.jar
|
||||
+-dependency2.jar
|
||||
|
||||
索引文件如下所示:
|
||||
|
||||
- "BOOT-INF/lib/dependency2.jar"
|
||||
- "BOOT-INF/lib/dependency1.jar"
|
||||
|
||||
1.5. Layer 索引
|
||||
Layer 索引文件可以在 BOOT-INF/layers.idx。 它提供了一个层列表以及应该包
|
||||
含在其中的 jar 部分。层是按照应该添加到 Docker/OCI 镜像的顺序编写的。
|
||||
Layer 名称是带双引号的字符串,前缀为破折号空格 ( "-·") ,后缀为冒号
|
||||
( ":") 。Layer 内容是一个文件名或目录名,是以空格空格破折号空格( "··-·")作
|
||||
为前缀的带双引号的字符串 。目录名以 / 结尾,文件名不用。当使用目录名称
|
||||
时,意味着该目录中的所有文件都在同一层中。
|
||||
Layer 索引的典型示例是:
|
||||
|
||||
- "dependencies":
|
||||
|
||||
- "BOOT-INF/lib/dependency1.jar"
|
||||
|
||||
- "BOOT-INF/lib/dependency2.jar"
|
||||
|
||||
- "application":
|
||||
|
||||
- "BOOT-INF/classes/"
|
||||
|
||||
- "META-INF/"
|
||||
|
||||
2. Spring Boot 的 JarFile 类
|
||||
|
||||
用于支持加载嵌套 jar 的核心类是 org.springframework.boot.loader.jar.JarFile. 它
|
||||
允许您从标准 jar 文件或嵌套的子 jar 数据中加载 jar 的内容。首次加载时,
|
||||
每个 JarEntry 的位置都被映射到外部 jar 的物理文件偏移量,如下例所示:
|
||||
|
||||
我的应用程序
|
||||
|
||||
+--------------------------------+-------------------------+
|
||||
|
||||
| /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar |
|
||||
|
||||
|+-----------------+||+-----------+---------+|
|
||||
|
||||
|| A.class ||| B.class | C.class ||
|
||||
|
||||
|+-----------------+||+-----------+---------+|
|
||||
|
||||
+--------------------------------+-------------------------+
|
||||
^^^
|
||||
|
||||
0063 3452 3980
|
||||
|
||||
上面的示例显示了如何在 myapp.jar 的 /BOOT-INF/classes 中找到位置
|
||||
为 0063 的 A.class。 嵌套 jar 的 B.class 实际上可以在 myapp.jar 的 3452 位置处
|
||||
找到,C.class 在 3980 处。
|
||||
|
||||
有了这些信息,我们就可以通过寻找外部 jar 的适当部分来加载特定的嵌套条
|
||||
目。我们不需要解压存档,也不需要将所有条目数据读入内存。
|
||||
|
||||
2.1. 与标准 Java JarFile 的兼容性
|
||||
Spring Boot Loader 努力保持与现有代码和库的兼
|
||||
容。 org.springframework.boot.loader.jar.JarFile 继承自 java.util.jar.JarFile 并应作为
|
||||
替代品。getURL() 方法返回一个 URL ,其打开一个
|
||||
与 java.net.JarURLConnection 兼容的连接,并可与 Java 的 URLClassLoader 一起
|
||||
使用。
|
||||
|
||||
3. 启动可执行的 jars
|
||||
|
||||
org.springframework.boot.loader.Launcher 是作为一个可执行 JAR 的主入口点一
|
||||
个特殊的启动类。它是 jar 文件中的实际 Main-Class,用于设置适当的
|
||||
URLClassLoader 并最终调用您的 main() 方法。
|
||||
|
||||
有三个 Launcher 子类(JarLauncher,WarLauncher,和 PropertiesLauncher)。
|
||||
它们的目的是从嵌套的 jar 文件或目录中的 war 文件中加载资源(.class 文件
|
||||
等),而不是显式地在 classpath 上加载资源。在 JarLauncher 和
|
||||
WarLauncher 的情况下,嵌套路径是固定的。
|
||||
|
||||
• JarLauncher :在 BOOT-INF/lib/ 中查找。
|
||||
|
||||
• WarLauncher 在 WEB-INF/lib/ 和 WEB-INF/lib-provided/ 查找。
|
||||
|
||||
• PropertiesLauncher:默认情况下,在 BOOT-INF/lib/ 中查找。您可以
|
||||
添加其他位置,通过设置名为 LOADER_PATH 的环境变量,或
|
||||
在 loader.properties 文件中设置 loader.path 属性 (这是一个以逗号分
|
||||
隔的目录、档案或档案中的目录的列表)。
|
||||
3.1. Launcher Manifest
|
||||
您需要指定一个适当的 Launcher 作为 的 META-INF/MANIFEST.MF 文件的 Main-
|
||||
Class 属性。您要启动的实际类(即包含 main 方法的应用程序类)应在 Start-
|
||||
Class 属性中指定。
|
||||
以下示例显示了一个典型的可执行 jar 文件的 MANIFEST.MF:
|
||||
|
||||
Main-Class: org.springframework.boot.loader.JarLauncher
|
||||
|
||||
Start-Class: com.mycompany.project.MyApplication
|
||||
|
||||
对于 war 文件,它将如下所示:
|
||||
|
||||
Main-Class: org.springframework.boot.loader.WarLauncher
|
||||
|
||||
Start-Class: com.mycompany.project.MyApplication
|
||||
|
||||
4. PropertiesLauncher 功能
|
||||
|
||||
PropertiesLauncher 有一些可以通过外部属性(系统属性、环境变量、manifest
|
||||
entries 或 loader.properties 文件)启用的特殊功能。下表描述了这些属性:
|
||||
|
||||
Key Purpose
|
||||
loader.path
|
||||
loader.home 逗号分隔的类路径,例如 lib,${HOME}/app/lib。 前面的条目优
|
||||
loader.args 先,类似于 javac 命令行 的-classpath。
|
||||
|
||||
用于解析 loader.path 中的相对路径。 例如,给定
|
||||
loader.path=lib,则 ${loader.home}/lib 是一个 classpath 位
|
||||
置(以及该目录中的所有 jar 文件)。此属性也用于定
|
||||
位 loader.properties 文件,如下例中/opt/app 默认为
|
||||
${user.dir}。
|
||||
|
||||
main 方法的默认参数(空格分隔)。
|
||||
|
||||
loader.main 要启动的主类的名称(例如,com.app.Application)。
|
||||
|
||||
loader.config.name 属性文件的名称(例如,launcher)。默认为 loader。
|
||||
Key Purpose
|
||||
|
||||
属性文件的路径(例如,classpath:loader.properties)。默认
|
||||
loader.config.location 为 loader.properties。
|
||||
|
||||
loader.system 布尔标志,指示应将所有属性添加到系统属性。默认为 false。
|
||||
|
||||
当指定为环境变量或 manifest entries 时,应使用以下名称:
|
||||
|
||||
Key Manifest entry Environment variable
|
||||
|
||||
loader.path Loader-Path LOADER_PATH
|
||||
|
||||
loader.home Loader-Home LOADER_HOME
|
||||
|
||||
loader.args Loader-Args LOADER_ARGS
|
||||
|
||||
loader.main Start-Class LOADER_MAIN
|
||||
|
||||
loader.config.location Loader-Config-Location LOADER_CONFIG_LOCATION
|
||||
|
||||
loader.system Loader-System LOADER_SYSTEM
|
||||
|
||||
Tip
|
||||
构建插件会在构建 fat jar 时自动将 Main-Class 属性移动到 Start-Class。如果使用它,请
|
||||
使用 Main-Class 属性指定要启动的类的名称并省略 Start-Class。
|
||||
|
||||
以下规则适用于使用 PropertiesLauncher:
|
||||
|
||||
• 首先在 loader.home 中搜索 loader.properties,然后在 classpath 的根
|
||||
中搜索,然后在 classpath:/BOOT-INF/classes 中。 使用最先找到的文
|
||||
件。
|
||||
|
||||
• loader.home 是仅当 loader.config.location 未指定时附加属性文件的目
|
||||
录位置(覆盖默认值)。
|
||||
|
||||
• loader.path 可以包含目录(对 jar 和 zip 文件进行递归扫描)、
|
||||
archive 路径、archive 中扫描 jar 文件的目录(例如,
|
||||
dependencies.jar!/lib)或通配符模式(对于默认的 JVM 行为)。
|
||||
archive 路径可以相对于 loader.home。或者带有 jar:file:前缀的文件
|
||||
系统中的任何位置。
|
||||
|
||||
• loader.path(如果为空)默认为 BOOT-INF/lib(意味着本地目录,或
|
||||
嵌套目录,如果从存档运行)。因此,未提供其他配置
|
||||
的 PropertiesLauncher 的行为与 JarLauncher 的行为相同。
|
||||
|
||||
• loader.path 不能用于配置 loader.properties 的位置(用于搜索后者的
|
||||
classpath 是 PropertiesLauncher 启动时的 JVM classpath )。
|
||||
• 在使用所有值之前,从系统和环境变量加上属性文件本身进行占位
|
||||
符替换。
|
||||
|
||||
• 属性的搜索顺序(在多个地方查看是有意义的)是环境变量、系统
|
||||
属性、loader.properties、分解的 archive manifest 和 archive
|
||||
manifest。
|
||||
|
||||
可执行 Jar 的限制
|
||||
|
||||
在使用 Spring Boot Loader 打包的应用程序时,您需要考虑以下限制:
|
||||
|
||||
• Zip entry compression:必须使用 ZipEntry.STORED 方法保存嵌套
|
||||
jar 的 ZipEntry。这是必需的,以便我们可以直接查找嵌套 jar 中的
|
||||
单个内容。嵌套 jar 文件本身的内容仍然可以压缩,就像外部 jar
|
||||
中的任何其他条目一样。
|
||||
|
||||
• System classLoader:启动的应用程序在加载类时应该使
|
||||
用 Thread.getContextClassLoader()(大多数库和框架默认是这样做
|
||||
的)。尝试使用 ClassLoader.getSystemClassLoader() 加载嵌套的 jar
|
||||
会失败。 java.util.Logging 始终使用系统类加载器。因此,您应该考
|
||||
虑不同的日志记录实现。
|
||||
|
||||
6. 其他的单个 Jar 的解决方案
|
||||
|
||||
如果上述限制意味着您无法使用 Spring Boot Loader,请考虑以下替代方案:
|
||||
|
||||
• Maven Shade Plugin
|
||||
• JarClassLoader
|
||||
• OneJar
|
||||
• Gradle Shadow Plugin
|
||||
|
||||
原文
|
||||
|
||||
• The Executable Jar Format
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
The Executable Jar Format
|
||||
|
||||
Back to index
|
||||
|
||||
• 1. Nested JARs
|
||||
• 2. Spring Boot’s “NestedJarFile” Class
|
||||
• 3. Launching Executable Jars
|
||||
• 4. PropertiesLauncher Features
|
||||
• 5. Executable Jar Restrictions
|
||||
• 6. Alternative Single Jar Solutions
|
||||
|
||||
The spring-boot-loader modules lets Spring Boot support executable jar and war
|
||||
files. If you use the Maven plugin or the Gradle plugin, executable jars are
|
||||
automatically generated, and you generally do not need to know the details of
|
||||
how they work.
|
||||
If you need to create executable jars from a different build system or if you are
|
||||
just curious about the underlying technology, this appendix provides some
|
||||
background.
|
||||
|
||||
1. Nested JARs
|
||||
|
||||
Java does not provide any standard way to load nested jar files (that is, jar files
|
||||
that are themselves contained within a jar). This can be problematic if you need
|
||||
to distribute a self-contained application that can be run from the command
|
||||
line without unpacking.
|
||||
To solve this problem, many developers use “shaded” jars. A shaded jar
|
||||
packages all classes, from all jars, into a single “uber jar”. The problem with
|
||||
shaded jars is that it becomes hard to see which libraries are actually in your
|
||||
application. It can also be problematic if the same filename is used (but with
|
||||
different content) in multiple jars. Spring Boot takes a different approach and
|
||||
lets you actually nest jars directly.
|
||||
|
||||
1.1. The Executable Jar File Structure
|
||||
|
||||
Spring Boot Loader-compatible jar files should be structured in the following
|
||||
|
||||
way:
|
||||
|
||||
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
|
||||
|
||||
Application classes should be placed in a nested BOOT-INF/classes directory.
|
||||
|
||||
Dependencies should be placed in a nested BOOT-INF/lib directory.
|
||||
1.2. The Executable War File Structure
|
||||
|
||||
Spring Boot Loader-compatible war files should be structured in the following
|
||||
|
||||
way:
|
||||
|
||||
example.war
|
||||
|
||||
|
|
||||
|
||||
+-META-INF
|
||||
|
||||
| +-MANIFEST.MF
|
||||
|
||||
+-org
|
||||
|
||||
| +-springframework
|
||||
|
||||
| +-boot
|
||||
|
||||
| +-loader
|
||||
|
||||
| +-<spring boot loader classes>
|
||||
|
||||
+-WEB-INF
|
||||
|
||||
+-classes
|
||||
|
||||
| +-com
|
||||
|
||||
| +-mycompany
|
||||
|
||||
| +-project
|
||||
|
||||
| +-YourClasses.class
|
||||
|
||||
+-lib
|
||||
|
||||
| +-dependency1.jar
|
||||
|
||||
| +-dependency2.jar
|
||||
|
||||
+-lib-provided
|
||||
|
||||
+-servlet-api.jar
|
||||
|
||||
+-dependency3.jar
|
||||
|
||||
Dependencies should be placed in a nested WEB-INF/lib directory. Any
|
||||
|
||||
dependencies that are required when running embedded but are not required
|
||||
|
||||
when deploying to a traditional web container should be placed in WEB-
|
||||
|
||||
INF/lib-provided.
|
||||
|
||||
1.3. Index Files
|
||||
|
||||
Spring Boot Loader-compatible jar and war archives can include additional
|
||||
index files under the BOOT-INF/ directory. A classpath.idx file can be provided
|
||||
for both jars and wars, and it provides the ordering that jars should be added
|
||||
|
||||
to the classpath. The layers.idx file can be used only for jars, and it allows a jar
|
||||
|
||||
to be split into logical layers for Docker/OCI image creation.
|
||||
|
||||
Index files follow a YAML compatible syntax so that they can be easily parsed
|
||||
|
||||
by third-party tools. These files, however, are not parsed internally as YAML
|
||||
|
||||
and they must be written in exactly the formats described below in order to be
|
||||
|
||||
used.
|
||||
|
||||
1.4. Classpath Index
|
||||
|
||||
The classpath index file can be provided in BOOT-INF/classpath.idx. Typically, it
|
||||
|
||||
is generated automatically by Spring Boot’s Maven and Gradle build plugins. It
|
||||
|
||||
provides a list of jar names (including the directory) in the order that they
|
||||
|
||||
should be added to the classpath. When generated by the build plugins, this
|
||||
|
||||
classpath ordering matches that used by the build system for running and
|
||||
|
||||
testing the application. Each line must start with dash space ("-·") and names
|
||||
|
||||
must be in double quotes.
|
||||
|
||||
For example, given the following jar:
|
||||
example.jar
|
||||
|
|
||||
+-META-INF
|
||||
| +-...
|
||||
+-BOOT-INF
|
||||
|
||||
+-classes
|
||||
| +...
|
||||
+-lib
|
||||
|
||||
+-dependency1.jar
|
||||
+-dependency2.jar
|
||||
The index file would look like this:
|
||||
- "BOOT-INF/lib/dependency2.jar"
|
||||
- "BOOT-INF/lib/dependency1.jar"
|
||||
|
||||
1.5. Layer Index
|
||||
|
||||
The layers index file can be provided in BOOT-INF/layers.idx. It provides a list of
|
||||
layers and the parts of the jar that should be contained within them. Layers are
|
||||
written in the order that they should be added to the Docker/OCI image.
|
||||
Layers names are written as quoted strings prefixed with dash space ("-·") and
|
||||
with a colon (":") suffix. Layer content is either a file or directory name written
|
||||
as a quoted string prefixed by space space dash space ("··-·"). A directory name
|
||||
ends with /, a file name does not. When a directory name is used it means that
|
||||
all files inside that directory are in the same layer.
|
||||
A typical example of a layers index would be:
|
||||
- "dependencies":
|
||||
|
||||
- "BOOT-INF/lib/dependency1.jar"
|
||||
- "BOOT-INF/lib/dependency2.jar"
|
||||
- "application":
|
||||
- "BOOT-INF/classes/"
|
||||
- "META-INF/"
|
||||
|
||||
2. Spring Boot’s “NestedJarFile”
|
||||
|
||||
Class
|
||||
|
||||
The core class used to support loading nested jars
|
||||
is org.springframework.boot.loader.jar.NestedJarFile. It lets you load jar content
|
||||
from nested child jar data. When first loaded, the location of each JarEntry is
|
||||
|
||||
mapped to a physical file offset of the outer jar, as shown in the following
|
||||
|
||||
example:
|
||||
|
||||
myapp.jar
|
||||
|
||||
+-------------------+-------------------------+
|
||||
|
||||
| /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar |
|
||||
|
||||
|+-----------------+||+-----------+----------+|
|
||||
|
||||
|| A.class ||| B.class | C.class ||
|
||||
|
||||
|+-----------------+||+-----------+----------+|
|
||||
|
||||
+-------------------+-------------------------+
|
||||
|
||||
^ ^ ^
|
||||
|
||||
0063 3452 3980
|
||||
|
||||
The preceding example shows how A.class can be found in /BOOT-
|
||||
|
||||
INF/classes in myapp.jar at position 0063. B.class from the nested jar can
|
||||
|
||||
actually be found in myapp.jar at position 3452, and C.class is at position 3980.
|
||||
|
||||
Armed with this information, we can load specific nested entries by seeking to
|
||||
|
||||
the appropriate part of the outer jar. We do not need to unpack the archive,
|
||||
|
||||
and we do not need to read all entry data into memory.
|
||||
|
||||
2.1. Compatibility With the Standard Java
|
||||
“JarFile”
|
||||
|
||||
Spring Boot Loader strives to remain compatible with existing code and
|
||||
libraries. org.springframework.boot.loader.jar.NestedJarFile extends
|
||||
from java.util.jar.JarFile and should work as a drop-in replacement.
|
||||
Nested JAR URLs of the form jar:nested:/path/myjar.jar/!BOOT-
|
||||
INF/lib/mylib.jar!/B.class are supported and open a connection compatible
|
||||
with java.net.JarURLConnection. These can be used with Java’s URLClassLoader.
|
||||
3. Launching Executable Jars
|
||||
|
||||
The org.springframework.boot.loader.launch.Launcher class is a special bootstrap
|
||||
class that is used as an executable jar’s main entry point. It is the actual Main-
|
||||
Class in your jar file, and it is used to setup an appropriate ClassLoader and
|
||||
ultimately call your main() method.
|
||||
There are three launcher subclasses (JarLauncher, WarLauncher,
|
||||
and PropertiesLauncher). Their purpose is to load resources (.class files and so
|
||||
on) from nested jar files or war files in directories (as opposed to those
|
||||
explicitly on the classpath). In the case of JarLauncher and WarLauncher, the
|
||||
nested paths are fixed. JarLauncher looks in BOOT-INF/lib/,
|
||||
and WarLauncher looks in WEB-INF/lib/ and WEB-INF/lib-provided/. You can
|
||||
add extra jars in those locations if you want more.
|
||||
The PropertiesLauncher looks in BOOT-INF/lib/ in your application archive by
|
||||
default. You can add additional locations by setting an environment variable
|
||||
called LOADER_PATH or loader.path in loader.properties (which is a comma-
|
||||
separated list of directories, archives, or directories within archives).
|
||||
|
||||
3.1. Launcher Manifest
|
||||
|
||||
You need to specify an appropriate Launcher as the Main-Class attribute
|
||||
of META-INF/MANIFEST.MF. The actual class that you want to launch (that is,
|
||||
the class that contains a main method) should be specified in the Start-
|
||||
|
||||
Class attribute.
|
||||
|
||||
The following example shows a typical MANIFEST.MF for an executable jar file:
|
||||
Main-Class: org.springframework.boot.loader.launch.JarLauncher
|
||||
Start-Class: com.mycompany.project.MyApplication
|
||||
For a war file, it would be as follows:
|
||||
Main-Class: org.springframework.boot.loader.launch.WarLauncher
|
||||
Start-Class: com.mycompany.project.MyApplication
|
||||
You need not specify Class-Path entries in your manifest file.
|
||||
The classpath is deduced from the nested jars.
|
||||
|
||||
4. PropertiesLauncher Features
|
||||
|
||||
PropertiesLauncher has a few special features that can be enabled with external
|
||||
|
||||
properties (System properties, environment variables, manifest entries,
|
||||
|
||||
or loader.properties). The following table describes these properties:
|
||||
|
||||
Key Purpose
|
||||
loader.path
|
||||
Comma-separated Classpath, such
|
||||
loader.home as lib,${HOME}/app/lib. Earlier entries take
|
||||
precedence, like a regular -classpath on
|
||||
loader.args the javac command line.
|
||||
loader.main Used to resolve relative paths in loader.path. For
|
||||
loader.config.name example, given loader.path=lib,
|
||||
then ${loader.home}/lib is a classpath location
|
||||
(along with all jar files in that directory). This
|
||||
property is also used to locate
|
||||
a loader.properties file, as in the following
|
||||
example /opt/app It defaults to ${user.dir}.
|
||||
Default arguments for the main method (space
|
||||
separated).
|
||||
|
||||
Name of main class to launch (for
|
||||
example, com.app.Application).
|
||||
Name of properties file (for example, launcher).
|
||||
It defaults to loader.
|
||||
Key Purpose
|
||||
|
||||
loader.config.location Path to properties file (for
|
||||
example, classpath:loader.properties). It defaults
|
||||
to loader.properties.
|
||||
|
||||
loader.system Boolean flag to indicate that all properties
|
||||
should be added to System properties. It defaults
|
||||
|
||||
to false.
|
||||
|
||||
When specified as environment variables or manifest entries, the following
|
||||
|
||||
names should be used:
|
||||
|
||||
Key Manifest entry Environment variable
|
||||
|
||||
loader.path Loader-Path LOADER_PATH
|
||||
|
||||
loader.home Loader-Home LOADER_HOME
|
||||
|
||||
loader.args Loader-Args LOADER_ARGS
|
||||
|
||||
loader.main Start-Class LOADER_MAIN
|
||||
|
||||
loader.config.location Loader-Config-Location LOADER_CONFIG_LOCATION
|
||||
|
||||
loader.system Loader-System LOADER_SYSTEM
|
||||
|
||||
Build plugins automatically move the Main-Class attribute
|
||||
to Start-Class when the uber jar is built. If you use that,
|
||||
specify the name of the class to launch by using the Main-
|
||||
Class attribute and leaving out Start-Class.
|
||||
The following rules apply to working with PropertiesLauncher:
|
||||
|
||||
• loader.properties is searched for in loader.home, then in the root of the
|
||||
|
||||
classpath, and then in classpath:/BOOT-INF/classes. The first location
|
||||
|
||||
where a file with that name exists is used.
|
||||
|
||||
• loader.home is the directory location of an additional properties file
|
||||
|
||||
(overriding the default) only when loader.config.location is not specified.
|
||||
• loader.path can contain directories (which are scanned recursively for jar
|
||||
and zip files), archive paths, a directory within an archive that is scanned
|
||||
for jar files (for example, dependencies.jar!/lib), or wildcard patterns (for
|
||||
the default JVM behavior). Archive paths can be relative
|
||||
to loader.home or anywhere in the file system with a jar:file: prefix.
|
||||
|
||||
• loader.path (if empty) defaults to BOOT-INF/lib (meaning a local directory
|
||||
or a nested one if running from an archive). Because of
|
||||
this, PropertiesLauncher behaves the same as JarLauncher when no
|
||||
additional configuration is provided.
|
||||
|
||||
• loader.path can not be used to configure the location
|
||||
of loader.properties (the classpath used to search for the latter is the JVM
|
||||
classpath when PropertiesLauncher is launched).
|
||||
|
||||
• Placeholder replacement is done from System and environment
|
||||
variables plus the properties file itself on all values before use.
|
||||
|
||||
• The search order for properties (where it makes sense to look in more
|
||||
than one place) is environment variables, system
|
||||
properties, loader.properties, the exploded archive manifest, and the
|
||||
archive manifest.
|
||||
|
||||
5. Executable Jar Restrictions
|
||||
|
||||
You need to consider the following restrictions when working with a Spring
|
||||
Boot Loader packaged application:
|
||||
• Zip entry compression: The ZipEntry for a nested jar must be saved by
|
||||
using the ZipEntry.STORED method. This is required so that we can seek
|
||||
directly to individual content within the nested jar. The content of the
|
||||
nested jar file itself can still be compressed, as can any other entry in the
|
||||
outer jar.
|
||||
|
||||
• System classLoader: Launched applications should
|
||||
use Thread.getContextClassLoader() when loading classes (most libraries
|
||||
and frameworks do so by default). Trying to load nested jar classes
|
||||
with ClassLoader.getSystemClassLoader() fails. java.util.Logging always uses
|
||||
the system classloader. For this reason, you should consider a different
|
||||
logging implementation.
|
||||
|
||||
6. Alternative Single Jar Solutions
|
||||
|
||||
If the preceding restrictions mean that you cannot use Spring Boot Loader,
|
||||
consider the following alternatives:
|
||||
|
||||
• Maven Shade Plugin
|
||||
• JarClassLoader
|
||||
• OneJar
|
||||
• Gradle Shadow Plugin
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<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>net.risesoft.demo</groupId>
|
||||
<artifactId>demo-car</artifactId>
|
||||
<version>1.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.risesoft.demo</groupId>
|
||||
<artifactId>demo-merceds</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.risesoft.demo</groupId>
|
||||
<artifactId>demo-audi</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>net.risesoft.demo.Example</mainClass>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<!-- 将项目的依赖信息添加到 MANIFEST.MF 中 -->
|
||||
<addClasspath>true</addClasspath>
|
||||
<!-- 将依赖的存放位置添加到 MANIFEST.MF 中-->
|
||||
<classpathPrefix>../lib/</classpathPrefix>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,28 @@
|
|||
package net.risesoft.demo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Example {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Car car = new Car();
|
||||
System.out.println("当前车辆版本: " + car.getVersion());
|
||||
System.out.println("当前 jar 包路径 : ");
|
||||
System.out.println(car.getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
Method[] declaredMethods = car.getClass().getDeclaredMethods();
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
System.out.println("------------------");
|
||||
System.out.println("method name: " + declaredMethod.getName());
|
||||
List<String> collect = Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getName).collect(Collectors
|
||||
.toList());
|
||||
if (!collect.isEmpty()) {
|
||||
System.out.println("parameter type : " + collect);
|
||||
}
|
||||
System.out.println("------------------");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
【JAVA 基础】不同的 jar 拥有相同全限定类名和不同的方法 Method 时 NoSuchMethodError,
|
||||
同名类加载问题 / 双亲委派
|
||||
|
||||
同类名加载验证
|
||||
前言
|
||||
准备工程
|
||||
demo-audi-1.0.jar 内容
|
||||
demo-audi-2.0.jar 内容
|
||||
demo-mercedes-1.0.jar 内容
|
||||
开始演示功能
|
||||
准备 Example 类
|
||||
准备 Classpath
|
||||
演示功能
|
||||
自然顺序,*来代替所有 jar
|
||||
手动改变 jar 名称,改变顺序
|
||||
手动改变 jar 名称及版本,改变顺序
|
||||
只保留一个 jar
|
||||
手动指定 Classpath 的包先后顺序( 重点)
|
||||
结论
|
||||
前言
|
||||
最近工作中遇到了一个项目工程问题,在启动 jvm 的 classpath 有两个不同版本的 jwt 的 jar
|
||||
包,在调用处报 java.lang.NoSuchMethodError: 其 Classpath 有两个不同版本的 jar 包,里面
|
||||
都有这个类,高版本的 jar 里面没有这个 Method,低版本有这个 Method。最终没有加载这
|
||||
个高版本的 Method
|
||||
猜测此问题就是 “全限定类名” 完全一样,Cloassloader 只加载了高版本的 jar 包。
|
||||
|
||||
此文就是为了验证 jvm 是如何加载 classpath 中同类同全限定类名的过程。
|
||||
|
||||
准备工程
|
||||
准备三个不同的 jar,里面都有同样一个类 Car,如下:
|
||||
|
||||
demo-audi-1.0.jar
|
||||
demo-audi-2.0.jar
|
||||
demo-mercedes-1.0.jar
|
||||
这三个工程拥有同样一个 class: com.example.demo.Car
|
||||
|
||||
demo-audi-1.0.jar 内容
|
||||
public class Car {
|
||||
|
||||
private static final String version = "A4L";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
|
||||
}
|
||||
public String getName() {
|
||||
return "audi";
|
||||
|
||||
}
|
||||
|
||||
public Integer limitSpeed() {
|
||||
return 100;
|
||||
|
||||
}
|
||||
|
||||
public String seatPerson(Integer a) {
|
||||
return "可以坐 :" + a;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
demo-audi-2.0.jar 内容
|
||||
public class Car {
|
||||
|
||||
private static final String version = "A6L";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "audi";
|
||||
|
||||
}
|
||||
|
||||
public Integer limitSpeed() {
|
||||
return 140;
|
||||
|
||||
}
|
||||
|
||||
public String seatPerson() {
|
||||
return "可以坐 :5";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
demo-mercedes-1.0.jar 内容
|
||||
public class Car {
|
||||
|
||||
private static final String version = "E300L";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
|
||||
}
|
||||
public String getName() {
|
||||
return "mercedes";
|
||||
|
||||
}
|
||||
|
||||
public Integer limitSpeed() {
|
||||
return 135;
|
||||
|
||||
}
|
||||
|
||||
public String hasPerson() {
|
||||
return "能舒服的坐 :4";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
开始演示功能
|
||||
准备 Example 类
|
||||
example.java
|
||||
|
||||
public class Example {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Car car = new Car();
|
||||
System.out.println("当前车辆版本:" + car.getVersion());
|
||||
System.out.println("当前 jar 包路径 : ");
|
||||
|
||||
System.out.println(car.getClass().getProtectionDomain().getCodeSource().getLocation().getP
|
||||
|
||||
ath());
|
||||
|
||||
Method[] declaredMethods = car.getClass().getDeclaredMethods();
|
||||
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
|
||||
System.out.println("------------------");
|
||||
|
||||
System.out.println("method name: " + declaredMethod.getName());
|
||||
|
||||
List<String> collect =
|
||||
|
||||
Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getName).collect(Collectors
|
||||
|
||||
.toList());
|
||||
|
||||
if(!collect.isEmpty()) {
|
||||
|
||||
System.out.println("parameter type : " + collect);
|
||||
|
||||
}
|
||||
|
||||
System.out.println("------------------");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
准备 Classpath
|
||||
test 目录
|
||||
|
||||
demo-audi-1.0.jar demo-audi-2.0.jar demo-example-1.0.jar demo-mercedes-
|
||||
|
||||
1.0.jar
|
||||
|
||||
1
|
||||
|
||||
演示功能
|
||||
|
||||
自然顺序,*来代替所有 jar
|
||||
|
||||
java -classpath "/test/*" com.example.demo.Example
|
||||
|
||||
当前车辆版本:A4L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-audi-1.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: seatPerson
|
||||
parameter type : [java.lang.Integer]
|
||||
------------------
|
||||
运行时,JVM 加载类 Car,根据【操作系统】的选择,本次加载了 demo-audi-1.0.jar 的
|
||||
com.example.demo.Car 类的 class 文件
|
||||
|
||||
手动改变 jar 名称,改变顺序
|
||||
当把 demo-audi-1.0.jar 重命名为 zemo-audi-1.0.jar ,“d” 改成 “z”
|
||||
|
||||
➜ test mv demo-audi-1.0.jar zemo-audi-1.0.jar
|
||||
➜ test java -classpath "/test/*" com.example.demo.Example
|
||||
当前车辆版本:A6L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-audi-2.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: seatPerson
|
||||
------------------
|
||||
运行时,JVM 加载类 Car,根据【操作系统】的选择,本次加载了 demo-audi-2.0.jar 的
|
||||
com.example.demo.Car 类的 class 文件
|
||||
|
||||
手动改变 jar 名称及版本,改变顺序
|
||||
当把 demo-audi-2.0.jar 重命名为 demo-mercedes-2.0.jar ,“audi” 改成 “mercedes”
|
||||
|
||||
[/test]# mv demo-audi-2.0.jar zemo-mercedes-2.0.jar
|
||||
[/test]# ls demo-example-1.0.jar demo-mercedes-1.0.jar demo-mercedes-2.0.jar zemo-
|
||||
audi-1.0.jar
|
||||
[/test]# java -classpath "/test/*" com.example.demo.Example
|
||||
当前车辆版本:A6L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-mercedes-2.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: seatPerson
|
||||
------------------
|
||||
运行时,JVM 加载类 Car,根据【操作系统】的选择,本次加载了 demo-mercedes-2.0.jar
|
||||
的 com.example.demo.Car 类的 class 文件
|
||||
|
||||
只保留一个 jar
|
||||
如果把 audi 的两个 jar 移动出去,classpath 里面只剩下 demo-mercedes-1.0.jar 时
|
||||
|
||||
当前车辆版本:E300L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-mercedes-1.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: hasPerson
|
||||
------------------
|
||||
运行时,JVM 加载类 Car,根据【操作系统】的选择,本次加载了 demo-mercedes-1.0.jar
|
||||
的 com.example.demo.Car 类的 class 文件
|
||||
|
||||
手动指定 Classpath 的包先后顺序( 重点)
|
||||
|
||||
# 当 demo-audi-1.0.jar 在第一个时
|
||||
java -classpath /test/demo-audi-1.0.jar:/test/demo-audi-2.0.jar:/test/demo-mercedes-
|
||||
1.0.jar:/test/demo-example-1.0.jar com.example.demo.Example
|
||||
|
||||
当前车辆版本:A4L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-audi-1.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: seatPerson
|
||||
parameter type : [java.lang.Integer]
|
||||
|
||||
# 当 demo-audi-2.0.jar 在第一个时
|
||||
java -classpath /test/demo-audi-2.0.jar:/test/demo-audi-1.0.jar:/test/demo-mercedes-
|
||||
1.0.jar:/test/demo-example-1.0.jar com.example.demo.Example
|
||||
|
||||
当前车辆版本:A6L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-audi-2.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: seatPerson
|
||||
------------------
|
||||
|
||||
# 当 demo-mercedes-1.0.jar 在第一个时
|
||||
java -classpath /test/demo-mercedes-1.0.jar:/test/demo-audi-2.0.jar:/test/demo-audi-
|
||||
1.0.jar:/test/demo-example-1.0.jar com.example.demo.Example
|
||||
当前车辆版本:E300L
|
||||
当前 jar 包路径 :
|
||||
/test/demo-mercedes-1.0.jar
|
||||
------------------
|
||||
method name: getName
|
||||
------------------
|
||||
------------------
|
||||
method name: limitSpeed
|
||||
------------------
|
||||
------------------
|
||||
method name: getVersion
|
||||
------------------
|
||||
------------------
|
||||
method name: hasPerson
|
||||
------------------
|
||||
结论
|
||||
根据 JVM 的双亲委派模型,默认情况下相同全限定类名的类只会加载一次,因此 JVM 加载
|
||||
Car 类时只会从 demo-audi-1.0.jar 或 demo-audi-2.0.jar 以及 demo-mercedes-1.0.jar 选一
|
||||
个;
|
||||
|
||||
同名的两个 Car 类来自不同的三个 Jar 包,他们是平级的,根据 JVM 的类加载机制——双亲
|
||||
委派模型,相同全限定类名的类默认只会加载一次(除非手动破坏双亲委派模型);
|
||||
|
||||
Jar 包中的类是使用 AppClassLoader 加载的,而类加载器中有一个命名空间的概念,同一个
|
||||
类加载器下,相同包名和类名的 class 只会被加载一次,如果已经加载过了,直接使用加载
|
||||
过的;
|
||||
|
||||
如果依赖中有多个全限定类名相同的类,那 JVM 会加载哪一个类呢?
|
||||
比较靠谱的说法是,操作系统本身,控制了 Jar 包的默认加载顺序;也就是说,对于我们来
|
||||
说是不明确不确定的!
|
||||
|
||||
而 Jar 包的加载顺序,是跟 classpath 这个参数有关,当使用 idea 启动 springboot 的服务时,
|
||||
可以看到 classpath 参数的;包路径越靠前,越先被加载;
|
||||
换句话说,如果靠前的 Jar 包里的类被加载了,后面 Jar 包里有同名同路径的类,就会被忽
|
||||
略掉,不会被加载
|
||||
————————————————
|
||||
|
||||
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权
|
||||
协议,转载请附上原文出处链接和本声明。
|
||||
|
||||
原文链接:https://blog.csdn.net/u013412066/article/details/129965950
|
||||
|
Loading…
Reference in New Issue