Spring Boot(-)Spring Boot 是如何通过jar包启动的

本文详细解析了SpringBoot FatJar的启动流程,从java-jar命令的作用开始,探讨了MANIFEST.MF文件中的Start-Class与Main-Class的区别。SpringBoot通过spring-boot-maven-plugin打包生成的jar包含BOOT-INF结构,启动类为JarLauncher。JarLauncher利用URLClassLoader加载资源,扩展了URLStreamHandler、JarFile和JarURLConnection以支持jarinjar的加载。整个启动过程涉及类加载、资源查找和处理等多个环节,确保了FatJar的正常运行。

Spring Boot 启动 jar包

我们通过java -jar demo.jar可以直接启动一个web项目,那么我们来了解下java -jar 命令是做什么,在oracle官网找到了该命令的描述:

If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

使用-jar参数时,后面的参数是的jar文件名(如springbootstarterdemo-0.0.1-SNAPSHOT.jar); 该jar文件中包含的是class和资源文件; 在manifest文件中有Main-Class的定义;
Main-Class的源码中指定了整个应用的启动类;(in its source code)

小结一下:

java -jar会去找jar中的manifest文件,在那里面找到真正的启动类;

在MANIFEST.MF文件中有这么一行内容:

Start-Class: com.it.demo.Application

前面的java官方文档中,只提到过Main-Class ,并没有提到Start-Class;
Start-Class的值是com.it.demo.Application,这是我们的java代码中的唯一类,也只真正的应用启动类;
所以问题就来了:理论上看,执行java -jar命令时JarLauncher类会被执行,但实际上是com.it.demo.Application被执行了,这其中发生了什么呢?为什么要这么做呢?

Java没有提供任何标准的方式来加载嵌套的jar文件(即,它们本身包含在jar中的jar文件)。

Spring Boot项目的pom.xml文件中默认使用如下插件进行打包:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

执行maven clean package之后,会生成两个文件:
spring-demo-0.0.1-SNAPSHOT.jar
spring-demo-SNAPSHOT.jar.original

maven插件打包流程

spring-boot-maven-plugin的repackage在代码层面调用了RepackageMojo的execute方法,而在该方法中又调用了repackage方法。repackage方法代码及操作解析如下:

private void repackage() throws MojoExecutionException {
   // maven生成的jar,最终的命名将加上.original后缀
   Artifact source = getSourceArtifact();
   // 最终为可执行jar,即fat jar
   File target = getTargetFile();
   // 获取重新打包器,将maven生成的jar重新打包成可执行jar
   Repackager repackager = getRepackager(source.getFile());
   // 查找并过滤项目运行时依赖的jar
   Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
         getFilters(getAdditionalFilters()));
   // 将artifacts转换成libraries
   Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
         getLog());
   try {
      // 获得Spring Boot启动脚本
      LaunchScript launchScript = getLaunchScript();
      // 执行重新打包,生成fat jar
      repackager.repackage(target, libraries, launchScript);
   }catch (IOException ex) {
      throw new MojoExecutionException(ex.getMessage(), ex);
   }
   // 将maven生成的jar更新成.original文件
   updateArtifact(source, target, repackager.getBackupFile());
}

执行以上命令之后,便生成了打包结果对应的两个文件。分析文件结构:
首先来看看jar的目录结构,都包含哪些目录和文件,解压jar包可以看到如下结构:
spring-boot-learn-0.0.1-SNAPSHOT
├── META-INF
│ └── MANIFEST.MF
├── BOOT-INF
│ ├── classes
│ │ └── 应用程序类
│ └── lib
│ └── 第三方依赖jar
└── org
└── springframework
└── boot
└── loader
└── springboot启动程序

META-INF内容
在上述目录结构中,META-INF记录了相关jar包的基础信息,包括入口程序等。

Manifest-Version: 1.0
Implementation-Title: spring-demo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.it.demo.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.2.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
可以看到有Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。
还有一个Start-Class是com.it.demo.Application,这个是我们应用自己的Main函数。

Archive的概念

在继续了解底层概念和原理之前,我们先来了解一下Archive的概念:
archive即归档文件,这个概念在linux下比较常见。通常就是一个tar/zip格式的压缩包。jar是zip格式。

SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),可以是一个文件目录(ExplodedArchive),可以抽象为统一访问资源的逻辑层。关于Spring Boot中Archive的源码如下:
public interface Archive extends Iterable<Archive.Entry> {
    // 获取该归档的url
    URL getUrl() throws MalformedURLException;
    // 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF
    Manifest getManifest() throws IOException;
    // 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar
    List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
}

SpringBoot定义了一个接口用于描述资源,也就是org.springframework.boot.loader.archive.Archive。该接口有两个实现,分别是org.springframework.boot.loader.archive.ExplodedArchive和org.springframework.boot.loader.archive.JarFileArchive。前者用于在文件夹目录下寻找资源,后者用于在jar包环境下寻找资源。而在SpringBoot打包的fatJar中,则是使用后者。

JarFile:对jar包的封装,每个JarFileArchive都会对应一个JarFile。JarFile被构造的时候会解析内部结构,去获取jar包里的各个文件或文件夹,这些文件或文件夹会被封装到Entry中,也存储在JarFileArchive中。如果Entry是个jar,会解析成JarFileArchive。
比如一个JarFileArchive对应的URL为:

 jar:file:/Users/format/Develop/gitrepository/springboot‐analysis/springboot‐executable‐jar/target/exec utable‐jar‐1.0‐SNAPSHOT.jar!/

它对应的JarFile为:

1 /Users/format/Develop/gitrepository/springboot‐analysis/springboot‐executable‐jar/target/executable‐ja r‐1.0‐SNAPSHOT.jar

这个JarFile有很多Entry,比如:

META-INF/
META-INF/MANIFEST.MF
spring/
spring/study/
....
spring/study/executablejar/ExecutableJarApplication.class
lib/spring-boot-starter-1.3.5.RELEASE.jar
lib/spring-boot-1.3.5.RELEASE.jar
...

JarFileArchive内部的一些依赖jar对应的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler处理器来处理这些URL):

jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/

我们看到如果有jar包中包含jar,或者jar包中包含jar包里面的class文件,那么会使用 !/ 分隔开,这种方式只有org.springframework.boot.loader.jar.Handler能处理,它是SpringBoot内部扩展出来的一种URL协议。

JarLauncher

  <!--添加依赖 loader-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-loader</artifactId>
     </dependency>

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.

按照定义,JarLauncher可以加载内部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的应用class,其实JarLauncher实现很简单:

public class JarLauncher extends ExecutableArchiveLauncher {
    public JarLauncher() {}
    
	private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";

	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
	};
    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
}

其主入口新建了JarLauncher并调用父类Launcher中的launch方法启动程序。在创建JarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive。

JarLauncher继承于org.springframework.boot.loader.ExecutableArchiveLauncher。该类的无参构造方法最主要的功能就是构建了当前main方法所在的FatJar的JarFileArchive对象。

下面来看launch方法。该方法主要是做了2个事情:
(1)以FatJar为file作为入参,构造JarFileArchive对象。获取其中所有的资源目标,取得其Url,将这些URL作为参数,构建了一个URLClassLoader。
(2)以第一步构建的ClassLoader加载MANIFEST.MF文件中Start-Class指向的业务类,并且执行静态方法main。进而启动整个程序。

public abstract class ExecutableArchiveLauncher extends Launcher {
    private final Archive archive;
    public ExecutableArchiveLauncher() {
        try {
            // 找到自己所在的jar,并创建Archive
            this.archive = createArchive();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}
public abstract class Launcher {
    protected final Archive createArchive() throws Exception {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
        String path = (location == null ? null : location.getSchemeSpecificPart());
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if (!root.exists()) {
            throw new IllegalStateException(
                    "Unable to determine code source archive from " + root);
        }
        return (root.isDirectory() ? new ExplodedArchive(root)
                : new JarFileArchive(root));
    }
}

在Launcher的launch方法中,通过以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录所对应的archive,通过这些archives的url生成LaunchedURLClassLoader,并将其设置为线程上下文类加载器,启动应用。

至此,才执行我们应用程序主入口类的main方法,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载。

URLStreamHandler

java中描述资源常使用URL。而URL有一个方法用于打开链接java.net.URL#openConnection()。由于URL用于表达各种各样的资源,打开资源的具体动作由java.net.URLStreamHandler这个类的子类来完成。根据不同的协议,会有不同的handler实现。而JDK内置了相当多的handler实现用于应对不同的协议。比如jar、file、http等等。URL内部有一个静态HashTable属性,用于保存已经被发现的协议和handler实例的映射。

获得URLStreamHandler有三种方法:
(1)实现URLStreamHandlerFactory接口,通过方法URL.setURLStreamHandlerFactory设置。该属性是一个静态属性,且只能被设置一次。
(2)直接提供URLStreamHandler的子类,作为URL的构造方法的入参之一。但是在JVM中有固定的规范要求:

子类的类名必须是Handler,同时最后一级的包名必须是协议的名称。比如自定义了Http的协议实现,则类名必然为xx.http.Handler;
JVM启动的时候,需要设置java.protocol.handler.pkgs系统属性,如果有多个实现类,那么中间用|隔开。因为JVM在尝试寻找Handler时,会从这个属性中获取包名前缀,最终使用包名前缀.协议名.Handler,使用Class.forName方法尝试初始化类,如果初始化成功,则会使用该类的实现作为协议实现。
为了实现这个目标,SpringBoot首先从支持jar in jar中内容读取做了定制,也就是支持多个!/分隔符的url路径。SpringBoot定制了以下两个方面:

(1)实现了一个java.net.URLStreamHandler的子类org.springframework.boot.loader.jar.Handler。该Handler支持识别多个!/分隔符,并且正确的打开URLConnection。打开的Connection是SpringBoot定制的org.springframework.boot.loader.jar.JarURLConnection实现。
(2)实现了一个java.net.JarURLConnection的子类org.springframework.boot.loader.jar.JarURLConnection。该链接支持多个!/分隔符,并且自己实现了在这种情况下获取InputStream的方法。而为了能够在org.springframework.boot.loader.jar.JarURLConnection正确获取输入流,SpringBoot自定义了一套读取ZipFile的工具类和方法。这部分和ZIP压缩算法规范紧密相连,就不拓展了。

Spring Boot的Jar应用启动流程总结

总结一下Spring Boot应用的启动流程:
(1)Spring Boot应用打包之后,生成一个Fat jar,包含了应用依赖的jar包和Spring Boot loader相关的类。
(2)Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。

那么,ClassLoader是如何读取到Resource,它又需要哪些能力?查找资源和读取资源的能力。对应的API:

public URL findResource(String name)
public InputStream getResourceAsStream(String name)
SpringBoot构造LaunchedURLClassLoader时,传递了一个URL[]数组。数组里是lib目录下面的jar的URL。

对于一个URL,JDK或者ClassLoader如何知道怎么读取到里面的内容的?流程如下:

LaunchedURLClassLoader.loadClass
URL.getContent()
URL.openConnection()
Handler.openConnection(URL)
最终调用的是JarURLConnection的getInputStream()函数。

//org.springframework.boot.loader.jar.JarURLConnection
 @Override
public InputStream getInputStream() throws IOException {
   connect();
   if (this.jarEntryName.isEmpty()) {
     throw new IOException("no entry name specified");
   }
   return this.jarEntryData.getInputStream();
 }

从一个URL,到最终读取到URL里的内容,整个过程是比较复杂的,总结下:
Spring boot注册了一个Handler来处理”jar:”这种协议的URL。
Spring boot扩展了JarFile和JarURLConnection,内部处理jar in jar的情况。
在处理多重jar in jar的URL时,Spring Boot会循环处理,并缓存已经加载到的JarFile。

对于多重jar in jar,实际上是解压到了临时目录来处理,可以参考JarFileArchive里的代码。
在获取URL的InputStream时,最终获取到的是JarFile里的JarEntryData。
细节很多,上面只列出比较重要的步骤。最后,URLClassLoader是如何getResource的呢?URLClassLoader在构造时,有URL[]数组参数,它内部会用这个数组来构造一个URLClassPath:

URLClassPath ucp = new URLClassPath(urls);

在URLClassPath内部会为这些URLS都构造一个Loader,然后在getResource时,会从这些Loader里一个个去尝试获取。如果获取成功的话,就像下面那样包装为一个Resource。

Resource getResource(final String name, boolean check) {
    final URL url;
    try {
        url = new URL(base, ParseUtil.encodePath(name, false));
    } catch (MalformedURLException e) {
        throw new IllegalArgumentException("name");
    }
    final URLConnection uc;
    try {
        if (check) {
            URLClassPath.check(url);
        }
        uc = url.openConnection();
        InputStream in = uc.getInputStream();
        if (uc instanceof JarURLConnection) {
            /* Need to remember the jar file so it can be closed
             * in a hurry.
             */
            JarURLConnection juc = (JarURLConnection)uc;
            jarfile = JarLoader.checkJar(juc.getJarFile());
        }
    } catch (Exception e) {
        return null;
    }
    return new Resource() {
        public String getName() { return name; }
        public URL getURL() { return url; }
        public URL getCodeSourceURL() { return base; }
        public InputStream getInputStream() throws IOException {
            return uc.getInputStream();
        }
        public int getContentLength() throws IOException {
            return uc.getContentLength();
        }
    };
}
 JarURLConnection juc = (JarURLConnection)uc;

从代码里可以看到,实际上是调用了url.openConnection()。这样完整的链条就可以连接起来了。

JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动。
SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。
SpringBoot通过扩展URLClassLoader–LauncherURLClassLoader,实现了jar in jar中class文件的加载。
WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动。

<think>我们面临的任务是将多个指定路径下的JAR安装到本地Maven仓库中。每个JAR都需要通过Maven的`install:install-file`命令来安装,并且需要提供GroupId、ArtifactId和Version(GAV坐标)。由于用户没有提供这些坐标,我们需要根据JAR文件名推断,或者用户需要自行提供。但根据常见Maven项目的命名规则,我们可以尝试从文件名中提取。 然而,由于这些JAR可能来自不同的项目,命名规则可能不一致,所以最可靠的方法是用户自己提供每个JAR的GAV坐标。如果用户无法提供,我们可以尝试按照以下规则从文件名中解析: 格式通常为:`artifactId-version.jar` 或者 `artifactId-version-classifier.jar`,但有时版本号可能含多个部分(如3.2.0)和多个连字符,这可能导致解析困难。 考虑到这一点,我们建议用户提供每个JAR的GAV坐标。如果用户坚持从文件名解析,我们可以尝试以下方法(但可能有误): 1. 去掉文件扩展名(.jar) 2. 将文件名按连字符拆分,最后一个部分如果是数字和点(表示版本号)或者有多个部分,那么从后往前取,直到遇到非版本号的部分(这很困难,因为版本号格式多样) 3. 另一种常见做法:版本号通常以数字开头,所以我们可以从后往前找第一个以数字开头的部分作为版本号,前面的部分作为artifactId。 但这种方法并不总是可靠。因此,在下面的解决方案中,我们将假设用户能够提供每个JAR的GAV坐标。如果用户不能提供,我们将尝试从文件名中解析,并给出警告。 根据用户提供的JAR文件名,我们尝试解析如下: 1. `knife4j-aggregation-spring-boot-starter-2.0.8.jar`: - 我们期望的格式是:artifactId = `knife4j-aggregation-spring-boot-starter`, version = `2.0.8` - 但注意,这个jar的groupId通常为`com.github.xiaoymin`(根据之前的引用[1])[^1] 2. `dynamic-datasource-spring-boot-starter-3.2.0.jar`: - artifactId: `dynamic-datasource-spring-boot-starter` - version: `3.2.0` 3. `spring-boot-admin-starter-client-2.2.0.jar`: - artifactId: `spring-boot-admin-starter-client` - version: `2.2.0` 4. `spring-boot-admin-starter-server-2.2.0.jar`: - artifactId: `spring-boot-admin-starter-server` - version: `2.2.0` 5. `elasticjob-lite-spring-boot-starter-3.0.1.jar`: - artifactId: `elasticjob-lite-spring-boot-starter` - version: `3.0.1` 对于groupId,不同的jar可能不同。用户需要知道这些jar的groupId,否则安装后在其他项目中引用时会出错。如果用户不知道,我们可以暂时用一个通用的groupId(如`com.example`)代替,但这不是推荐做法。 因此,我们建议用户查找这些jar的groupId。如果无法找到,可以考虑以下方案: - 对于`knife4j`,根据引用[1]可知其groupId为`com.github.xiaoymin`[^1]。 - 对于`spring-boot-admin`,通常groupId为`de.codecentric`。 - 对于`dynamic-datasource`,可能是`com.baomidou`(这是常见的,例如dynamic-datasource-spring-boot-starter是mybatis-plus的扩展)。 - 对于`elasticjob`,可能是`org.apache.shardingsphere.elasticjob`。 如果用户不确定,建议先尝试查找,否则后续使用可能会有问题。 下面,我们将为每个JAR生成安装命令。命令格式如下: ```bash mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository ``` 注意:`-Dmaven.repo.local`参数用于指定本地仓库的位置,这样就不会安装到默认的本地仓库(通常是用户目录下的.m2/repository),而是安装到用户指定的`D:\maven\MavenRepository`。 但是,请注意:`install:install-file`插件默认不会将文件安装到指定的本地仓库目录(除非通过`-Dmaven.repo.local`指定),但这里我们通过该参数指定了本地仓库的位置,因此安装时会直接安装到该目录。 然而,需要提醒的是,`-Dmaven.repo.local`是Maven全局设置,它会影响整个命令执行期间Maven的本地仓库位置。因此,每个命令都会将jar安装到`D:\maven\MavenRepository`。 下面我们根据假设的groupId(如果用户没有提供,则使用我们推断的)来生成命令。用户需要根据实际情况修改groupId。 我们假设: 1. knife4j-aggregation-spring-boot-starter-2.0.8.jar -> groupId: `com.github.xiaoymin` 2. dynamic-datasource-spring-boot-starter-3.2.0.jar -> groupId: `com.baomidou` (常见,但不确定,用户需要确认) 3. spring-boot-admin-starter-client-2.2.0.jar -> groupId: `de.codecentric` 4. spring-boot-admin-starter-server-2.2.0.jar -> groupId: `de.codecentric` 5. elasticjob-lite-spring-boot-starter-3.0.1.jar -> groupId: `org.apache.shardingsphere` (注意:ElasticJob现在是ShardingSphere的子项目) 如果用户有准确的groupId,请替换。 生成命令如下(在Windows的cmd或PowerShell中执行): ```bash # knife4j mvn install:install-file -Dfile=C:\Users\pc\Downloads\knife4j-aggregation-spring-boot-starter-2.0.8.jar -DgroupId=com.github.xiaoymin -DartifactId=knife4j-aggregation-spring-boot-starter -Dversion=2.0.8 -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository # dynamic-datasource mvn install:install-file -Dfile=C:\Users\pc\Downloads\dynamic-datasource-spring-boot-starter-3.2.0.jar -DgroupId=com.baomidou -DartifactId=dynamic-datasource-spring-boot-starter -Dversion=3.2.0 -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository # spring-boot-admin client mvn install:install-file -Dfile=C:\Users\pc\Downloads\spring-boot-admin-starter-client-2.2.0.jar -DgroupId=de.codecentric -DartifactId=spring-boot-admin-starter-client -Dversion=2.2.0 -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository # spring-boot-admin server mvn install:install-file -Dfile=C:\Users\pc\Downloads\spring-boot-admin-starter-server-2.2.0.jar -DgroupId=de.codecentric -DartifactId=spring-boot-admin-starter-server -Dversion=2.2.0 -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository # elasticjob mvn install:install-file -Dfile=C:\Users\pc\Downloads\elasticjob-lite-spring-boot-starter-3.0.1.jar -DgroupId=org.apache.shardingsphere.elasticjob -DartifactId=elasticjob-lite-spring-boot-starter -Dversion=3.0.1 -Dpackaging=jar -Dmaven.repo.local=D:\maven\MavenRepository ``` 注意:以上命令中的groupId是假设的,如果用户有准确的groupId,请务必替换。如果用户不确定,建议先查找官方文档或Maven中央仓库(https://search.maven.org/)来确认。 另外,这些命令需要Maven已经安装并配置好环境变量(即命令行中可以直接运行`mvn`命令)。 执行每个命令后,Maven会将指定的JAR安装到本地仓库的相应位置(在`D:\maven\MavenRepository`下按groupId/artifactId/version的目录结构存放)。 如果用户不想一个一个执行,可以写一个批处理文件(.bat)或shell脚本,将上述命令保存为一个文件(如`install_jars.bat`),然后运行该批处理文件。 重要提示:在运行这些命令之前,请确保关闭所有可能占用这些JAR文件的程序(如IDE),以免出现文件锁导致无法读取。 如果用户在执行过程中遇到问题(如权限不足),请尝试以管理员身份运行命令行。 最后,安装完成后,用户可以在项目中通过提供的GAV坐标引用这些依赖。 ### 相关问题 1. 如何在Maven项目中引用本地仓库中的自定义JAR? 2. 如何确认一个JAR的GroupId、ArtifactId和Version? 3. Maven安装依赖时出现权限错误如何解决? 4. 如何批量安装多个JAR到Maven本地仓库? 5. 如何修改Maven的默认本地仓库路径?</think>### 如何将多个 JAR 安装到本地 Maven 仓库 您需要将指定目录下的多个 JAR 安装到本地 Maven 仓库(`D:\maven\MavenRepository`)。以下是详细步骤和命令: #### 步骤 1:准备安装命令模板 使用 Maven 的 `install:install-file` 命令安装单个 JAR 的基本格式: ```bash mvn install:install-file \ -Dfile=<JAR文件路径> \ -DgroupId=<组ID> \ -DartifactId=<构件ID> \ -Dversion=<版本号> \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` #### 步骤 2:为每个 JAR 生成安装命令 根据文件名推断 GAV 坐标(GroupId, ArtifactId, Version),实际使用时请根据项目要求调整: 1. **knife4j-aggregation-spring-boot-starter-2.0.8.jar** ```bash mvn install:install-file \ -Dfile=C:\Users\pc\Downloads\knife4j-aggregation-spring-boot-starter-2.0.8.jar \ -DgroupId=com.github.xiaoymin \ -DartifactId=knife4j-aggregation-spring-boot-starter \ -Dversion=2.0.8 \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` 2. **dynamic-datasource-spring-boot-starter-3.2.0.jar** ```bash mvn install:install-file \ -Dfile=C:\Users\pc\Downloads\dynamic-datasource-spring-boot-starter-3.2.0.jar \ -DgroupId=com.baomidou \ -DartifactId=dynamic-datasource-spring-boot-starter \ -Dversion=3.2.0 \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` 3. **spring-boot-admin-starter-client-2.2.0.jar** ```bash mvn install:install-file \ -Dfile=C:\Users\pc\Downloads\spring-boot-admin-starter-client-2.2.0.jar \ -DgroupId=de.codecentric \ -DartifactId=spring-boot-admin-starter-client \ -Dversion=2.2.0 \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` 4. **spring-boot-admin-starter-server-2.2.0.jar** ```bash mvn install:install-file \ -Dfile=C:\Users\pc\Downloads\spring-boot-admin-starter-server-2.2.0.jar \ -DgroupId=de.codecentric \ -DartifactId=spring-boot-admin-starter-server \ -Dversion=2.2.0 \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` 5. **elasticjob-lite-spring-boot-starter-3.0.1.jar** ```bash mvn install:install-file \ -Dfile=C:\Users\pc\Downloads\elasticjob-lite-spring-boot-starter-3.0.1.jar \ -DgroupId=org.apache.shardingsphere.elasticjob \ -DartifactId=elasticjob-lite-spring-boot-starter \ -Dversion=3.0.1 \ -Dpackaging=jar \ -DlocalRepositoryPath=D:\maven\MavenRepository ``` #### 步骤 3:执行安装命令 1. 打开命令提示符(CMD)或 PowerShell 2. 逐条执行上述命令(每次执行一个) 3. 等待出现 `BUILD SUCCESS` 提示表示安装完成 #### 验证安装结果 检查仓库目录结构: ``` D:\maven\MavenRepository ├── com │ └── github │ └── xiaoymin │ └── knife4j-aggregation-spring-boot-starter │ └── 2.0.8 │ └── knife4j-aggregation-spring-boot-starter-2.0.8.jar ├── com │ └── baomidou │ └── dynamic-datasource-spring-boot-starter │ └── 3.2.0 │ └── dynamic-datasource-spring-boot-starter-3.2.0.jar └── de └── codecentric ├── spring-boot-admin-starter-client │ └── 2.2.0 │ └── spring-boot-admin-starter-client-2.2.0.jar └── spring-boot-admin-starter-server └── 2.2.0 └── spring-boot-admin-starter-server-2.2.0.jar ``` #### 注意事项 1. **GAV 坐标说明**: - `GroupId`:项目组织标识(如 `com.github.xiaoymin`) - `ArtifactId`:项目唯一标识(如 `knife4j-aggregation-spring-boot-starter`) - `Version`:版本号(如 `2.0.8`) 2. **权限问题**: - 确保对 `D:\maven\MavenRepository` 有写权限 - 建议以管理员身份运行命令提示符 3. **批量处理**: 可将所有命令保存为 `.bat` 文件批量执行: ```batch @echo off REM 保存为 install_jars.bat mvn install:install-file -Dfile=C:\Users\pc\Downloads\knife4j-aggregation-spring-boot-starter-2.0.8.jar ... mvn install:install-file -Dfile=C:\Users\pc\Downloads\dynamic-datasource-spring-boot-starter-3.2.0.jar ... ...其他命令... ``` ### 相关问题 1. 如何在 Maven 项目中引用本地仓库中的自定义 JAR ? 2. 如果 JAR 缺少 POM 文件,安装时会出现什么问题? 3. 如何将 Spring Boot Starter 组件集成到微服务项目中(参考 Knife4j 配置)[^2]? 4. Maven 本地仓库和中央仓库的依赖解析优先级是什么? 5. 如何解决 Knife4j 在生产环境的安全配置问题[^4]?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员路同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值