背景:
想在 eclipse 中创建一个拥有子模块的 maven 工程,因为在实际项目中用到的就是多模块工程,原谅我不知道这个工程是如何创建的,因为以前没有接触过,在这里记录下创建工程。
创建父 maven 工程:
打开 eclipse,右键 ---- New ---- Maven Project ,如下图所示:
这个 Add project(s) to working set 的意思是把一些相关的项目归类到一起,任何项目都可以归类。这个选不选没啥意义,点击 next 。
需要注意的是,这个 Packaging 要选择 pom 类型,然后点击 Finish 。
因为这是个父工程,不需要什么源码,所以进入到工程的目录下,把不用的目录都删除掉,仅留下 pom.xml 即可,如下图所示:
最后这个父工程的样子,就长成这样:
创建子 maven 工程:
我们在父工程项目上 右键 ---- New ---- Other ---- Maven Module ,如下所示:
点击 next ,输入子工程的项目名称,如下所示:
继续点击 next 。点击 Finish 即可 。
最终形成的工程目录结构如下所示:
为了方便后面说明如何添加子工程之间的依赖,我们再创建一个子工程,具体的操作步骤和上面的一样,我这里只把最终的工程目录的结构图贴出来,如下所示:
此时,若我们想在 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 依赖 B,B 依赖 C,C 依赖 A,也是所谓的循环依赖 。总的来说,在画出工程依赖图之后,如果发现工程间的依赖连线形成了一个有向循环图,则说明有循环依赖的现象 。
如果循环依赖发生在工程之间,则会影响构建,因为 maven 不知道应该先编译哪个工程。如果循环依赖发生在同一个工程的模块之间,虽然不影响编译,但是也是一种不好的实践,说明模块的设计有问题,应该避免 。
如果这种情况发生在模块内部,有几个类互相调用的话,我觉得可能是正常的。比如观察者模式里面,Observer 和 Observable 就是互相依赖的。这个没啥问题。
解决循环依赖问题:
方式一:使用 build-helper-maven-plugin 插件来规避
若发生 A 依赖 B,B 依赖 A 的情况。这个插件提供了一种规避措施,即临时地将工程 A 和 B 合并成一个中间工程,编译出临时的模块 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 用于启动所有的模块,现在整个工程的目录结构如下所示:
在 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=world 和 http://localhost:8080/hello2?name=world 进行测试,可以正常的启动和访问,当然了,构建版本也是没有问题的。
注意:
发现了一个小 bug ,就是当我修改了 springboot_dubbo_provider 和 springboot_dubbo_customer 项目中的 Controller 代码时,重启工程,发现展示的效果还是未修改之前的效果。当我把整个工程 maven-clean,maven-install 之后,再重启工程,就是修改后的效果了。可能时由于我们中间依赖了一个中间模块 springboot_dubbo_app 造成的。这点在使用的时候注意点。
方式二:重构
第一个办法是平移,比如 A 和 B 互相依赖,那么可以将 B 依赖 A 的那部分代码,移动到工程 B 中,这样一来,B 就不需要继续依赖 A,只要 A 依赖 B 就可以了,从而消除循环依赖 。
第二个办法是下移,比如 A 和 B 互相依赖,同时它们都依赖 C,那么可以将 B 和 A 相互依赖的那部分代码,移动到工程 C 里,这样一来,A 和 B 相互之间都不依赖,只继续依赖 C ,也可以消除循环依赖。
这两种重构方式都是可行的,具体采用哪种方式要根据实际情况来判断。不管采取哪种方式,都需要对代码进行修改,有时候并不是那么容易的 。