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