commit 02c03d9b3ca9e441a3606bb1b2dc51c929ed5f61 Author: dingzhaojun Date: Sun Feb 18 20:17:49 2024 +0800 init diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4532bdd --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/Springboot 可执行 Jar 的格式 .pdf b/Springboot 可执行 Jar 的格式 .pdf new file mode 100644 index 0000000..48a35cb --- /dev/null +++ b/Springboot 可执行 Jar 的格式 .pdf @@ -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 + | +- + ++-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 + +| +- + ++-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 + diff --git a/The Executable Jar Format .pdf b/The Executable Jar Format .pdf new file mode 100644 index 0000000..9b12674 --- /dev/null +++ b/The Executable Jar Format .pdf @@ -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 + +| +- + ++-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 + +| +- + ++-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 + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..420d684 --- /dev/null +++ b/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + net.risesoft.demo + demo-car + 1.0 + + + net.risesoft.demo + demo-merceds + 1.0 + + + + net.risesoft.demo + demo-audi + 1.0 + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + net.risesoft.demo.Example + true + + true + + ../lib/ + + + + + + + \ No newline at end of file diff --git a/src/main/java/net/risesoft/demo/Example.java b/src/main/java/net/risesoft/demo/Example.java new file mode 100644 index 0000000..f959a59 --- /dev/null +++ b/src/main/java/net/risesoft/demo/Example.java @@ -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 collect = Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getName).collect(Collectors + .toList()); + if (!collect.isEmpty()) { + System.out.println("parameter type : " + collect); + } + System.out.println("------------------"); + } + } + +} diff --git a/不同的jar拥有相同全限定类名.pdf b/不同的jar拥有相同全限定类名.pdf new file mode 100644 index 0000000..c894a96 --- /dev/null +++ b/不同的jar拥有相同全限定类名.pdf @@ -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 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 +