背景:

       想在 eclipse 中创建一个拥有子模块的 maven 工程,因为在实际项目中用到的就是多模块工程,原谅我不知道这个工程是如何创建的,因为以前没有接触过,在这里记录下创建工程。

创建父 maven 工程:

       打开 eclipse右键 ---- New ---- Maven Project ,如下图所示:

java循环依赖导致启动失败 eclipse循环依赖_java循环依赖导致启动失败

       这个 Add project(s) to working set 的意思是把一些相关的项目归类到一起,任何项目都可以归类。这个选不选没啥意义,点击 next 。

java循环依赖导致启动失败 eclipse循环依赖_xml_02

        需要注意的是,这个 Packaging 要选择 pom 类型,然后点击 Finish 。

java循环依赖导致启动失败 eclipse循环依赖_spring_03

        因为这是个父工程,不需要什么源码,所以进入到工程的目录下,把不用的目录都删除掉,仅留下 pom.xml 即可,如下图所示:

java循环依赖导致启动失败 eclipse循环依赖_java循环依赖导致启动失败_04

       最后这个父工程的样子,就长成这样:

java循环依赖导致启动失败 eclipse循环依赖_maven_05

创建子 maven 工程: 

       我们在父工程项目上 右键 ---- New ---- Other ---- Maven Module ,如下所示:

java循环依赖导致启动失败 eclipse循环依赖_xml_06

       点击 next ,输入子工程的项目名称,如下所示:

java循环依赖导致启动失败 eclipse循环依赖_xml_07

       继续点击 next 。点击 Finish 即可 。 

java循环依赖导致启动失败 eclipse循环依赖_spring_08

        最终形成的工程目录结构如下所示:

java循环依赖导致启动失败 eclipse循环依赖_spring_09

        为了方便后面说明如何添加子工程之间的依赖,我们再创建一个子工程,具体的操作步骤和上面的一样,我这里只把最终的工程目录的结构图贴出来,如下所示:

java循环依赖导致启动失败 eclipse循环依赖_spring_10

       此时,若我们想在 springboot_dubbo_customer 项目中,引入 springboot_dubbo_provider 项目的依赖,相应的 pom.xml 内容如下所示:

<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>
  <parent>
    <groupId>com</groupId>
    <artifactId>springboot_dubbo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>springboot_dubbo_customer</artifactId>
  
  <dependencies>
  	<dependency>
  		<groupId>com</groupId>
  		<artifactId>springboot_dubbo_provider</artifactId>
	 	<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  </dependencies>
</project>

循环依赖:

       此时发生这样一种情况,即 springboot_dubbo_customer 项目依赖于 springboot_dubbo_provider 项目,而 springboot_dubbo_provider 又依赖于 springboot_dubbo_customer 项目,就会发生循环依赖,程序就无法进行进行编译,导致项目报错。

       如果工程 A 依赖工程 B,工程 B 又依赖工程 A,就会形成循环依赖。或者 A 依赖 BB 依赖 CC 依赖 A,也是所谓的循环依赖 。总的来说,在画出工程依赖图之后,如果发现工程间的依赖连线形成了一个有向循环图,则说明有循环依赖的现象 。

       如果循环依赖发生在工程之间,则会影响构建,因为 maven 不知道应该先编译哪个工程。如果循环依赖发生在同一个工程的模块之间,虽然不影响编译,但是也是一种不好的实践,说明模块的设计有问题,应该避免 。

       如果这种情况发生在模块内部,有几个类互相调用的话,我觉得可能是正常的。比如观察者模式里面,Observer Observable 就是互相依赖的。这个没啥问题。 

解决循环依赖问题:

方式一:使用 build-helper-maven-plugin 插件来规避

       若发生 A 依赖 BB 依赖 A 的情况。这个插件提供了一种规避措施,即临时地将工程 AB 合并成一个中间工程,编译出临时的模块 D。然后 A B 再分别依赖临时模块 D 进行编译。这种方法可以解决无法构建的问题,但是只是一个规避措施,工程的依赖关系依然是混乱的

       针对于这种解决方案,在我们上面的项目中再创建一个子模块工程 springboot_dubbo_app 作为中间工程,创建过程省略,只贴出这个中间模块的 pom.xml,因为这个工程除了这个配置文件其他什么内容都没有,配置文件内容如下所示:

<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>
  <parent>
    <groupId>com</groupId>
    <artifactId>springboot_dubbo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>  
  </parent>
  
   <artifactId>springboot_dubbo_app</artifactId>
    <packaging>jar</packaging>  
    <name>springboot_dubbo_app</name>  
    <url>http://maven.apache.org</url>  
    
  <properties>  
        <project.build.sourceEncoding>  
            UTF-8  
        </project.build.sourceEncoding>  
        <module.a.src>../springboot_dubbo_customer/src/main/java</module.a.src>  
        <module.b.src>../springboot_dubbo_provider/src/main/java</module.b.src>  
    </properties>
     <build>  
        <plugins><!-- 解决模块相互依赖,综合所有相互依赖代码统一编译 -->  
            <plugin>  
                <groupId>org.codehaus.mojo</groupId>  
                <artifactId>build-helper-maven-plugin</artifactId>  
                <executions>  
                    <execution>  
                         <configuration>
                         	<id>add-source</id>
                         	 <phase>generate-sources</phase>
                         	<goals>  
                            	<goal>add-source</goal>  
                            </goals>  
                            <sources>
                                <source>${module.a.src}</source>  
                                <source>${module.b.src}</source>  
                            </sources>  
                        </configuration>  
                    </execution>  
                </executions>  
            </plugin>  
        </plugins>  
    </build> 
</project>

        我们再创建一个子工程 springboot_dubbo_start 用于启动所有的模块,现在整个工程的目录结构如下所示:

java循环依赖导致启动失败 eclipse循环依赖_maven_11

       在 springboot_dubbo 中配置 springboot 的父 maven 依赖,不能在子模块中添加这个依赖,因为子模块中已经有了 <parent>  标签,这个标签在一个 pom.xml 中只能有一个。pom.xml 的内容如下所示:

<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>com</groupId>
	<artifactId>springboot_dubbo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<modules>
		<module>springboot_dubbo_provider</module>
		<module>springboot_dubbo_customer</module>
		<module>springboot_dubbo_app</module>
		<module>springboot_dubbo_start</module>
	</modules>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.8.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
</project>

       在 springboot_dubbo_customer 子项目中配置其依赖于中间模块 springboot_dubbo_app ,完整的 pom.xml 内容如下所示:

<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>
	<parent>
		<groupId>com</groupId>
		<artifactId>springboot_dubbo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<name>springboot_dubbo_customer</name>
	<artifactId>springboot_dubbo_customer</artifactId>

	<dependencies>
		<dependency>
			<groupId>com</groupId>
			<artifactId>springboot_dubbo_app</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>
</project>

       添加一个 Controller 类用于测试项目能否正常编译和访问,代码如下所示:

package com;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class HelloWorld2 {
 
	@RequestMapping("/hello2")
	public String Hello2(String name) {
		
		return "Hellow"+name;
	}
}

        在 springboot_dubbo_provider 子项目中配置其依赖于中间模块 springboot_dubbo_app ,完整的 pom.xml 内容如下所示:

<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>
	<parent>
		<groupId>com</groupId>
		<artifactId>springboot_dubbo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<name>springboot_dubbo_provider</name>
	<artifactId>springboot_dubbo_provider</artifactId>

	<dependencies>
		<dependency>
			<groupId>com</groupId>
			<artifactId>springboot_dubbo_app</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>
</project>

       添加一个 Controller 类用于测试项目能否正常编译和访问,代码如下所示:

package com;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class HelloWorld {
 
	@RequestMapping("/hello")
	public String Hello(String name) {
		
		return "Hello"+name;
	}
}

       在 springboot_dubbo_start 子项目中配置其依赖于所有的子模块方便启动所有的子模块工程,完整的 pom.xml 内容如下所示:

<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>
	<parent>
		<groupId>com</groupId>
		<artifactId>springboot_dubbo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>springboot_dubbo_start</artifactId>

	<dependencies>
		<dependency>
			<groupId>com</groupId>
			<artifactId>springBoot_dubbo_customer</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>com</groupId>
			<artifactId>springBoot_dubbo_provider</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>
</project>

       添加一个启动类 App,代码如下所示:

package com;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}

        启动工程,在浏览器中输入 http://localhost:8080/hello?name=worldhttp://localhost:8080/hello2?name=world 进行测试,可以正常的启动和访问,当然了,构建版本也是没有问题的。

java循环依赖导致启动失败 eclipse循环依赖_maven_12

java循环依赖导致启动失败 eclipse循环依赖_xml_13

注意:

        发现了一个小 bug ,就是当我修改了 springboot_dubbo_provider 和 springboot_dubbo_customer 项目中的 Controller 代码时,重启工程,发现展示的效果还是未修改之前的效果。当我把整个工程 maven-cleanmaven-install 之后,再重启工程,就是修改后的效果了。可能时由于我们中间依赖了一个中间模块 springboot_dubbo_app 造成的。这点在使用的时候注意点。

方式二:重构

       第一个办法是平移,比如 AB 互相依赖,那么可以将 B 依赖 A 的那部分代码,移动到工程 B 中,这样一来,B 就不需要继续依赖 A,只要 A 依赖 B 就可以了,从而消除循环依赖 。

       第二个办法是下移,比如 AB 互相依赖,同时它们都依赖 C,那么可以将 BA 相互依赖的那部分代码,移动到工程 C 里,这样一来,A B 相互之间都不依赖,只继续依赖 C ,也可以消除循环依赖。

       这两种重构方式都是可行的,具体采用哪种方式要根据实际情况来判断。不管采取哪种方式,都需要对代码进行修改,有时候并不是那么容易的 。