直观理解依赖关系!

举手示意一下,我们中有多少人真正理解构建自动化工具是如何构建其依赖树的?现在,如果你是因为从事构建自动化工具的工作而理解这一点,请放下你的手。我猜就是这样!

软件工程师一个令人头疼的责任就是理解项目的依赖关系:哪些传递依赖被引入以及由谁引入;为什么使用v1.3.1而不是声明的v1.2.10;当传递依赖发生变化时产生了什么结果;同一构件的多个版本是如何出现的?

每位软件工程师都曾将依赖树输出到文本文件中,搜索特定的构件,然后追溯其来源。对于任何非微不足道的项目,要在脑海中构建出依赖关系的图谱极其困难,甚至是不可能的。

I faced this problem when starting a new job with a mature code base, presenting a challenge to assemble the puzzle pieces.  I’ve previously worked with graph databases and thought a graphical view of the dependency artifacts could be created using Neo4J, which resulted in DependencyLoader.

注意:这不是关于图数据库的教程,也不要求有图数据库的背景知识。如果感兴趣,Neo4J提供了教程白皮书来帮助你入门。

设置环境

安装Java

需要Java 11或更高版本。如尚未安装,请安装您偏好的OpenJDK版本。

安装Neo4J

本教程需使用一个Neo4J数据库,用于加载依赖信息,最好是不共享的,因为每次运行前加载器会清空数据库特此提醒!

Neo4J 提供个人沙箱环境,非常适合此类短期教程项目。

或者,您也可以在您的台式机或笔记本上本地安装Neo4J。Homebrew简化了MacOS上的安装过程:

Shell

 

brew install neo4j && brew services start neo4j

继续之前,请通过浏览器确认您能访问Neo4J数据库,可以使用Neo4J沙箱的链接和凭证,或本地访问http://localhost:7474。本地安装的默认凭证是neo4j/neo4j;成功登录后,系统会强制您更改密码。

克隆仓库

neo4j-gradle-dependencies仓库包含用于将依赖项加载到Neo4J的工具。本教程将生成spring-boot的依赖关系图。您必须克隆这两个仓库。

Shell

 

Scott.Sosna@mymachine src% git clone [email protected]:scsosna99/neo4j-gradle-dependencies.git
Scott.Sosna@mymachine src% git clone [email protected]:spring-projects/spring-boot.git

注意:本地无需安装Gradle,因为两个仓库都使用了Gradle Wrapper,该Wrapper首次使用时会自动下载所有必需组件。

生成依赖项

DependencyLoaderGradle生成的依赖树作为输入。虽然可以同时加载多个配置——例如compileClasspathruntimeClasspathtestCompileClasspathtestRuntimeClasspath——但针对教程而言,从一个配置开始更容易理解。

要为所有配置生成依赖项:

  • gradle dependencies
  • ./gradlew dependencies

要为单个配置生成依赖项:

  • gradle dependencies --configuration <configuration>
  • ./gradlew dependencies --configuration <configuration>

生成Spring Boot依赖项

本教程使用Spring Boot的compileClasspath依赖项在Neo4J中创建一个依赖关系图。从克隆仓库的目录中,执行以下命令:

Shell

 

Scott.Sosna@mymachine src% cd spring-boot/spring-boot-project/spring-boot
Scott.Sosna@mymachine spring-boot% ./gradlew dependencies --configuration compileClasspath > dependencies.out

文件dependencies.out包含了Spring Boot的编译时类路径依赖项。

加载依赖项

首先,确认DependencyLoader.java中的连接URL和认证凭据,并在必要时进行修改。

执行以下命令将Spring Boot的依赖项加载到Neo4j中:

Shell

 

Scott.Sosna@mymachine spring-boot% cd ../../../neo4j-gradle-dependencies
Scott.Sosna@mymachine neo4j-gradle-dependencies% ./gradlew clean run  --args="../spring-boot/spring-boot-project/spring-boot/dependencies.out"

成功后,gradle的输出行将显示:

Shell

 

Scott.Sosna@PVHY32M6KG neo4j-gradle-dependencies % ./gradlew clean run  --args="../spring-boot/spring-boot-project/spring-boot/dependencies.out"

> Task :compileJava
Note: /Users/Scott.Sosna/data/src/github/neo4j-gradle-dependencies/src/main/java/dev/scottsosna/neo4j/gradle/relationship/DependsOn.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

> Task :run
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Jun 02, 2023 6:19:22 AM org.neo4j.driver.internal.logging.JULogger info
INFO: Direct driver instance 1606286799 created for server address localhost:7687
dependencies.out completed.
Jun 02, 2023 6:19:23 AM org.neo4j.driver.internal.logging.JULogger info
INFO: Closing driver instance 1606286799
Jun 02, 2023 6:19:23 AM org.neo4j.driver.internal.logging.JULogger info
INFO: Closing connection pool towards localhost:7687

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.5.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 3s

查看依赖项

有多种工具可用于显示Neo4J图,但本教程内置的浏览器工具已足够。

显示完整树

查询MATCH(a) RETURN a相当于关系型数据库中的SELECT * FROM <table>

查看构件详情

找到的每个构件都会创建一个节点,其属性标识构件(groupId/artifactId)及其类型,显示在右侧窗格中。

查看依赖详情

每个依赖都以关系的形式创建,其属性标识了依赖的具体信息:配置、指定/版本及配置。下方选定的依赖显示spring-security:spring-web依赖于io.micormeter:micrometer-observation,但spring-web指定的版本1.10.7被解析为版本1.11.0。

遍历依赖关系

Neo4J允许您逐个节点地探索图,让您能够手动逐个扩展图节点,提供了一种探索依赖树特定区域的方式。

假设您想要了解io.projectreactor.netty:reactor-netty-http这个构件的依赖关系。首先,我们将针对该特定节点向Neo4J发起查询。

Cypher

 

MATCH(a:Artifact {groupId: 'io.projectreactor.netty', artifactId: 'reactor-netty-http'}) RETURN a

双击该节点将展示其邻近节点——依赖于它的构件以及它所依赖的构件。

这个扩展的图显示了一个依赖于它的构件——项目的根,其构件类型为PROJECT,以及它所依赖的其他六个构件。

接下来,双击io.netty:netty-codehttps://github.com/netty/netty/tree/4.1/codec-httpc-http以展示下一级别的依赖关系。请注意,除了所选节点的关系(依赖)外,图上已存在的节点可能还会显示额外的关系。

识别版本不匹配

Gradle的依赖输出指示了指定版本与Gradle解析的版本不一致的情况。依赖(关系)的属性可用于Neo4J查询,限制显示的关系及附加的构件(节点)。

Cypher

 

MATCH (a:Artifact)-[d:DEPENDS_ON]->(b:Artifact) WHERE d.specifiedVersion<>d.resolvedVersion RETURN a,b,d

如有需要,Neo4J可以以表格形式返回结果,便于审阅。

Cypher

 

MATCH (a:Artifact)-[d:DEPENDS_ON]->(b:Artifact) WHERE d.specifiedVersion<>d.resolvedVersion RETURNa.name AS source, b.name AS dependency, d.specifiedVersion AS specified, d.resolvedVersion AS resolved

附加信息

mappings.out

mappings.out文件允许你根据构件的groupId自定义分配给节点的构件类型,最常见的是为了专门识别你组织创建的构件。

输入目录

DependencyLoader的命令行参数可能是一个包含多个Gradle依赖树的目录,这些依赖树被加载到同一个Neo4J数据库中。这有助于理解具有独立build.gradle文件的相关项目的依赖关系。

限制与省略

Gradle将某些依赖标识为受限制的省略的。目前,这些依赖并未被加载,但很容易通过为关系创建额外的属性来包含。

Source:
https://dzone.com/articles/understanding-dependenciesvisually