This commit is contained in:
dzj 2024-02-18 20:17:49 +08:00
commit 02c03d9b3c
7 changed files with 99 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

28
.gitignore vendored Normal file
View File

@ -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/

View File

@ -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 子类JarLauncherWarLauncher和 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

View File

@ -0,0 +1,409 @@
The Executable Jar Format
Back to index
• 1. Nested JARs
• 2. Spring Boots “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 Boots 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 Boots “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 Javas URLClassLoader.
3. Launching Executable Jars
The org.springframework.boot.loader.launch.Launcher class is a special bootstrap
class that is used as an executable jars 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

42
pom.xml Normal file
View File

@ -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>

View File

@ -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("------------------");
}
}
}

View File

@ -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