Maven Build 循环依赖的危害及发现解决

提到maven循环依赖是maven 解析的痛点。循环依赖的存在会使maven build阶段出现build fail严重的会出现死循环,进而导致maven栈溢出。这篇文章我们将从最简单的maven循环依赖讲起,结合笔者的项目经历对循环依赖进行分析

最简单的循环依赖

Sample 项目

为了更好的理解maven循环依赖。我们创建一个最简单的sample项目:maven-test。这是一个maven多模块项目,包含a-component和b-component这两个模块。分析一下这个项目的pom.xml和java类,两个模块的依赖关系是a->b->a,两个模块中的类的依赖关系是A->B->A。 这就是最简单的循环依赖的case。

maven-test(parent) pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
    <artifactId>maven-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>a-component</module>
        <module>b-component</module>
    </modules>
    <packaging>pom</packaging>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>a-component</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>b-component</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.2.0</version>
        </plugin>
    </plugins>
</build>
</project>

a-component pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>maven-test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>a-component</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>b-component</artifactId>
        </dependency>
    </dependencies>
</project>

a-component A.java

public class A {
    private B b;
}

b-component pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>maven-test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>b-component</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>a-component</artifactId>
        </dependency>
    </dependencies>
</project>

b-component B.java

public class B {
    private A a;
}

使用maven定位最简单循环依赖

此时我们run一下maven clean install命令。通过观察log,发现对于多模块项目的各个模块,maven不仅能够帮我们判断项目中是否存在循环依赖,更会帮我们找到具体的循环依赖。

LM-SHC-16507782:maven-test xianghan$ mvn clean install
[INFO] Scanning for projects...
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='org.example:b-component:1.0-SNAPSHOT'}' and 'Vertex{label='org.example:a-component:1.0-SNAPSHOT'}' introduces to cycle in the graph org.example:a-component:1.0-SNAPSHOT --> org.example:b-component:1.0-SNAPSHOT --> org.example:a-component:1.0-SNAPSHOT @ 
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='org.example:b-component:1.0-SNAPSHOT'}' and 'Vertex{label='org.example:a-component:1.0-SNAPSHOT'}' introduces to cycle in the graph org.example:a-component:1.0-SNAPSHOT --> org.example:b-component:1.0-SNAPSHOT --> org.example:a-component:1.0-SNAPSHOT -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectCycleException

最简单循环依赖的解决思路

解决循环依赖的方式就是打断循环依赖链。无论是使用build-helper-maven-plugin的方式或者项目重构,思路都是抽出耦合部分代码创建为新的component,之后将a-component和b-component的依赖关系转向这个新的component即可。下面我们介绍一下使用build-helper-maven-plugin进行解决的方法。

使用build-helper-maven-plugin

  1. 我们在a-component和b-component之外,创建一个新的模块c-component。
  2. 修改a-component和b-component的pom.xml,将他们的依赖关系从彼此转向c-component。此时项目中各模块的依赖关系从 a->b->a改为了a->c,b->c,已经不存在循环依赖的问题,但是此时会发现a-component和b-component会由于找不到类文件而报错。
  3. 在c-component中添加build-helper-maven-plugin插件,通过build-helper-maven-plugin整合a-component和b-component中耦合部分的代码。
<build>
   <plugins>
           <plugin>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>build-helper-maven-plugin</artifactId>
               <version>3.2.0</version>
               <executions>
                   <execution>
                       <id>add-source</id>
                       <phase>generate-sources</phase>
                       <goals>
                           <goal>add-source</goal>
                       </goals>
                       <configuration>
                           <sources>
                               <source>../a-component/src/main/java</source>
                               <source>../b-component/src/main/java</source>
                           </sources>
                       </configuration>
                   </execution>
               </executions>
           </plugin>
   </plugins>
</build>

如何发现循环依赖

在一个复杂系统中,我们需要同时release几千个libraries。这些libraries由于数量众多,分别属于不同的部门, 因此很难管理,没有人清楚这些libraries中是不是存在循环依赖。
为了release工作的顺利开展,我们需要提前确认这些libraries中是否存在循环依赖,如果存在循环依赖的话,我们需要定位出这个循环依赖。

解决思路

libraries之间的依赖关系可以通过有向图这种数据结构来描述。所以,我们现在的任务就可以通过三个步骤来解决。

得到有向图

有向图的基本元素是节点和弧。我们来规定一下在我们的case中节点和弧分别指代的内容。

节点:每一个需要被release的library都是图中的一个节点。
:项目间的依赖关系,从依赖方指向被依赖方。

接下来就是通过计算来得到有向图了。经过各种尝试后,笔者最后通过下面这种方法得到以每一个节点为起点的弧的信息。

  1. git clone下载这些library的源码
  2. 运行mvn dependency:list获取library的依赖关系

至此,我们已经得到了由libraries和它们之间的依赖关系组成的有向图了。

判断有向图是否有环

我们假设上一步得到的有向图如图1所示。检查有向图是否有环的算法已经比较多了,我们简单介绍其中的一个办法,供大家参考。

  1. 求出图中所有节点的出度。
  2. 将所有出度==0的节点放入队列
  3. 当队列不空时进入循环,弹出队首节点h,把节点h所依赖的节点的出度减1,如果这些节点的出度变为0,则将这些节点入队。队列为空则退出循环。


    图1:有向图

    图2:环的计算过程
  4. 循环结束时判断已经访问过(进入过队列)的节点数是否等于 n。等于 n 说明全部节点都被访问过,无环;反之,则有环。


    image.png

从上图看,还剩下Project1,Project2, Project 3 此时依赖有环。

有环的话,我们需要找出其中的一个环

根据上一步的计算结果,已经确定在这些libraries中存在循环依赖,下一步的任务就是找到循环依赖中的一个环,抽丝剥茧的解决项目中的循环依赖。

通过在上一个小节中的知识,我们从有向图的正向(出度为0)出发,可以排除掉一批进入过队列的节点以及与之对应的弧。同样的,我们也可以从有向图的反向(入度为0)出发,排除掉另一批进入过队列的节点和对应的弧。

此时,我们沿着任意一个节点的弧走下去,无论这个节点本身是否处于一个环中,沿着它出发必然能够找到一个环。算法如下:

  1. 创建空的队列Q。
  2. 任选一个节点。将节点标志成已读之后放入到队列Q中。
  3. 以上次进入队列的节点H为起点,任选一条弧,找到弧的终点的节点T。检查T的节点标志是否为已读,如果是,则进入步骤4;否则,将节点标志成已读之后放入到队列Q中,重复步骤3。


    enqueue sequence
  1. 将队中元素依次出队并与T进行比较,直到出队元素为T。此时,T以及队列中剩余的元素构成循环依赖。


    result
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容