跳至主要內容

Spring Boot Maven Plugin

leo z大约 4 分钟后端Spring BootMavenDebug

Spring Boot Maven Plugin

SpringBoot executable-jaropen in new window

定制layers.xml

packaging.layers.configurationopen in new window

通过构建定制layers.xml文件,可以控制打包后的jar文件中的内容,从而实现更细粒度的控制。结合spring-boot-maven-plugin,可以实现在打包时将不同类型的文件放置在不同的层中,配合SpringBoot efficient-imagesopen in new window从而实现更高效的部署和管理。

<!-- POM.xml -->
<build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <layers>
                        <enabled>true</enabled>
                        <configuration>${basedir}/src/main/resources/layers.xml</configuration>
                    </layers>
                </configuration>
            </plugin>
        </plugins>
    </build>

<layers xmlns="http://www.springframework.org/schema/boot/layers"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
                          https://www.springframework.org/schema/boot/layers/layers-3.2.xsd">
    <!--application表示class和resource-->
    <application>
        <into layer="spring-boot-loader">
            <include>org/springframework/boot/loader/**</include>
        </into>
        <into layer="application"/>
    </application>

    <!--dependencies所有依赖-->
    <dependencies>
        <!--<into layer="application">
            <includeModuleDependencies/>
        </into>-->
        <into layer="snapshot-dependencies">
            <include>*:*:*SNAPSHOT</include>
        </into>
        <into layer="module-dependencies">
            <includeModuleDependencies/>
        </into>
        <!--<into layer="module-dependencies">
             <include>com.ruoyi:ruoyi-*:*</include>
         </into>-->
        <into layer="third-dependencies"/>
    </dependencies>

    <!--定义层顺序,会影响BOOT-INF/layers.idx-->
    <layerOrder>
        <!--这里到顺序会影响直接通过java -jar xxx.jar方式启动时的类加载顺序,而docker分层优化部署时的顺序不受影响。-->
        <layer>application</layer>
        <!--模块依赖-->
        <layer>module-dependencies</layer>
        <layer>third-dependencies</layer>
        <layer>spring-boot-loader</layer>
        <layer>snapshot-dependencies</layer>
    </layerOrder>
</layers>

classpath.idx

这是一个类路径索引文件,它包含了在构建过程中使用的所有类和资源的列表。这个文件通常由Maven插件生成,并包含在构建的JAR文件中。

官方解释:类路径索引文件可在 BOOT-INF/classpath.idx 中提供。通常,该文件由 Spring Boot 的 Maven 和 Gradle 构建插件自动生成。它提供了一个 jar 名称(包括目录)列表,并按照顺序将它们添加到 classpath 中。由构建插件生成时,该 classpath 排序与构建系统在运行和测试应用程序时使用的顺序一致。每行必须以破折号空格("--")开始,名称必须使用双引号。

该文件控制了类加载器的行为,它决定了类加载器在加载类和资源时的顺序。这个文件对于Spring Boot应用程序来说非常重要,因为它可以确保应用程序在运行时能够正确地加载类和资源。

过程分析

通过调试插件运行过程,可以发现:

  1. 在执行repackage方法时,会调用getLibraries方法,该方法会获取所有的库文件,并生成classpath.idx文件。相关代码如下:
// Packager
private void write(JarFile sourceJar, AbstractJarWriter writer, PackagedLibraries libraries) throws IOException {
		if (isLayered()) {
			writer.useLayers(this.layers, this.layersIndex);
		}
		writer.writeManifest(buildManifest(sourceJar));
		writeLoaderClasses(writer);
		writer.writeEntries(sourceJar, getEntityTransformer(), libraries.getUnpackHandler(),
				libraries.getLibraryLookup());
		Map<String, Library> writtenLibraries = libraries.write(writer);
		writeNativeImageArgFile(writer, sourceJar, writtenLibraries);
		if (isLayered()) {
			writeLayerIndex(writer);
		}
		writeSignatureFileIfNecessary(writtenLibraries, writer);
	}


Map<String, Library> write(AbstractJarWriter writer) throws IOException {
			Map<String, Library> writtenLibraries = new LinkedHashMap<>();
			for (Entry<String, Library> entry : this.libraries.entrySet()) {
				String path = entry.getKey();
				Library library = entry.getValue();
				if (library.isIncluded()) {
					String location = path.substring(0, path.lastIndexOf('/') + 1);
					writer.writeNestedLibrary(location, library);
					writtenLibraries.put(path, library);
				}
			}
      // 写入classpath.idx
			writeClasspathIndexIfNecessary(writtenLibraries.keySet(), getLayout(), writer);
			return writtenLibraries;
		}









 























  1. classpath.idx内容最初来自于maven生成的project对象中,相关代码如下:
// RepackageMojo
private void repackage() throws MojoExecutionException {
		Artifact source = getSourceArtifact(this.classifier);
		File target = getTargetFile(this.finalName, this.classifier, this.outputDirectory);
		Repackager repackager = getRepackager(source.getFile());
    // 获取所有的库文件
		Libraries libraries = getLibraries(this.requiresUnpack);
		try {
			LaunchScript launchScript = getLaunchScript();
			repackager.repackage(target, libraries, launchScript, parseOutputTimestamp());
		}
		catch (IOException ex) {
			throw new MojoExecutionException(ex.getMessage(), ex);
		}
		updateArtifact(source, target, repackager.getBackupFile());
	}

  protected final Libraries getLibraries(Collection<Dependency> unpacks) throws MojoExecutionException {
		Set<Artifact> artifacts = this.project.getArtifacts();
		Set<Artifact> includedArtifacts = filterDependencies(artifacts, getAdditionalFilters());
		return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog());
	}






 











 



// MavenProject
public Set<Artifact> getArtifacts() {
    if (artifacts == null) {
        if (artifactFilter == null || resolvedArtifacts == null) {
            artifacts = new LinkedHashSet<>();
        } else {
            artifacts = new LinkedHashSet<>(resolvedArtifacts.size() * 2);
            for (Artifact artifact : resolvedArtifacts) {
                if (artifactFilter.include(artifact)) {
                    artifacts.add(artifact);
                }
            }
        }
    }
    return artifacts;
}
  1. maven又是如何获取到这些库文件的,相关代码如下:

LifecycleDependencyResolverLifecycleDependencyResolver2

toArtifacts代码分析,maven在解析依赖时,会遍历所有的直接依赖,然后把它们的依赖分别进行递归调用toArtifacts方法:

jar的顺序和pom中依赖的顺序一致

// RepositoryUtils
public static void toArtifacts(
        Collection<org.apache.maven.artifact.Artifact> artifacts,
        Collection<? extends DependencyNode> nodes,
        List<String> trail,
        DependencyFilter filter) {
    for (DependencyNode node : nodes) {
        org.apache.maven.artifact.Artifact artifact = toArtifact(node.getDependency());

        List<String> nodeTrail = new ArrayList<>(trail.size() + 1);
        nodeTrail.addAll(trail);
        nodeTrail.add(artifact.getId());

        if (filter == null || filter.accept(node, Collections.<DependencyNode>emptyList())) {
            artifact.setDependencyTrail(nodeTrail);
            artifacts.add(artifact);
        }

        toArtifacts(artifacts, node.getChildren(), nodeTrail, filter);
    }
}

调试

准备

  • 下载插件源码,IDEA导入
git clone https://github.com/spring-projects/spring-boot
# 切换需要调试的版本
git checkout v3.2.8
  • 准备好测试项目,配置需要测试的插件 ,如spring-boot-maven-plugin,配置参考上方内容

  • 两个项目JDK和Maven版本保持一致

执行

  • 打断点

在源码项目中通过搜索@Mojo(name = "repackage"找到对应的核心类,在execute()方法打上断点

debug
debug
  • 在测试项目控制台中执行${maven_home}/mvnDebug clean package开启调试模式,等待IDEA连接,复制控制台输出的端口号
mvnDebug clean package
debug2
debug2
  • 配置远程调试

在源码项目新建远程调试并且启动,端口为上一步复制的端口号,等待将进入断点

debug3
debug3