背景
之前做的一个巡检机器人项目,硬件分3大块,如图
树莓派通过USB网卡直连到底盘,树莓派和工业相机通过8口交换机实现互联。
机器人干的事就是在变电站自主导航,到预定点位后拍照,最后将照片上传到FTP服务器,供后台图像算法识别分析。
软件运行在树莓派上,采用Java开发,划分成3个模块,分别是
- 底盘控制系统
- 相机控制系统
- web交互系统
3个模块之间通过线程消息队列
通信,这样既有高性能又有低耦合。
项目一开始我的规划是我只完成前2个,web交互系统交由专门做后台的同事,我将前2个打成jar包后交给他,他当作lib库调用即可。
多模块maven工程
好几年没写Java了,在简单学习了下maven
和IDEA
后,先开发底盘控制系统。主体开发完后,想加相机控制系统时,发现IDEA
同一个窗口只能打开一个工程,蛋疼。
不想新开工程,因为这样代码就分散在2个git仓库了,于是将底盘和相机都降级为maven模块,原来的工程变成了POM工程
(用在父级工程或聚合工程中,用来做jar工程
的版本控制),而降级的maven模块仍是jar工程
。
父工程的pom配置
<?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>com.lanplus.lanova</groupId>
<artifactId>lanova</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>lanova-camera_control</module>
<module>lanova-chassis_control</module>
</modules>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
<!-- 其他公共依赖 -->
</dependency>
</dependencies>
</project>
可以看到,父工程的打包格式字段packaging
为pom
,而不是单模块maven工程默认的jar
子模块底盘控制系统
的pom配置
<?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>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lanova-chassis_control</artifactId>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- 其他模块特有依赖 -->
</dependencies>
</project>
可以看到,底盘控制系统
降级成子模块后,增加了parent
字段,里面记录了父工程lanova
的maven信息,这样它本身的依赖项gson
就不用指定版本信息了。
子模块相机控制系统
的pom配置除了依赖有差异外,其他都跟底盘控制系统
一样,就不贴了。
降级后的目录结构
lanova/
├── lanova-camera_control
│ ├── lanova-camera_control.iml
│ ├── pom.xml
│ ├── src
│ └── target
├── lanova-chassis_control
│ ├── lanova-chassis_control.iml
│ ├── pom.xml
│ ├── src
│ └── target
├── lanova.iml
├── pom.xml
└── src
这样重构后,代码组织结构清晰,将来引入其他传感器(例如热成像)也不慌,再加一个maven模块就行。另外子模块间能共享相同版本的依赖,比较省事。
SpringBoot:你管谁叫爸爸呢
相机控制系统
的主体开发完后,通知做后台的同事着手开发web交互系统
,没想到公司在紧急做一个疫情相关的项目,他脱不开身,我想着反正手头也没其他活儿,就自己搞吧。
询问了下后台同事 公司Java Web的技术选型,得知是SpringBoot
,那我也保持一致吧。因为2008年后再也没用Java搞过Web开发,所以简单学习了下SpringBoot
,初始代码基本照抄网上的,包括pom配置:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lanova-web</name>
<description>lanova-web</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以看到,SpringBoot
的版本号是2.2.5.RELEASE,它默认生成的模块pom配置指定org.springframework.boot
的spring-boot-starter-parent
为自己的父工程,这样的好处是下面那些依赖项都不用指定版本号,因为都在父工程里指定好了。
虽然web模块的父亲跟其他模块不一样,但无所谓了,只要没有共同的依赖就行。开始在web里添加业务逻辑,其中一部分是跟底盘通信,需要lombok
辅助实现对象与JSON互转
,但是发现SpringBoot
的lombok
跟lanova
的lombok
版本不一致,虽然不影响编译,但存在隐患,最好搞成一致的。
不过SpringBoot
的lombok
是不能改版本的,因为一旦SpringBoot
的版本确定,它囊括的一堆库的相应版本也就确定了,具体见repository\org\springframework\boot\spring-boot-dependencies\2.2.5.RELEASE
下的spring-boot-dependencies-2.2.5.RELEASE.pom
文件,截取一部分给大家看看
<?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.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Boot Dependencies</name>
<description>Spring Boot Dependencies</description>
<url>https://projects.spring.io/spring-boot/#</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>
<developers>
<developer>
<name>Pivotal</name>
<email>info@pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
</developer>
</developers>
<scm>
<url>https://github.com/spring-projects/spring-boot</url>
</scm>
<properties>
<lombok.version>1.18.12</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
</pluginManagement>
</build>
</project>
可以看到,这也是个pom工程!并且在properties
字段里指定了lombok
等依赖的版本号,很明显,改这个pom文件是不明智的。
现在问题来了,到底该喊谁爸爸?即,所有子模块到底选谁做自己的父工程?
如果选lanova,则web模块无法使用springboot,不可接受。如果选springboot,则其他子模块一来要被迫跟Spring扯上关系,增加了不必要的耦合;二来其他子模块间本来通过lanova父工程共享的依赖,现在也没法挪到springboot里,只能每个工程单独定义一遍,容易不一致。
两全其美的解法
那就是将两代人整成三代人,爷爷、爸爸、孙子。因为pom模块也可以指定parent,就像类的继承层级,所以给父工程lanova添加parent字段,取值为springboot,同时将web模块的父工程改为lanova——跟底盘、相机一致——这样3个子模块都成了springboot的孙子,共享spring的web依赖版本,同时是lanova的儿子,共享lanova特定的gson、lombok依赖版本。
修改后的lanova工程pom配置
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>lanova-camera_control</module>
<module>lanova-chassis_control</module>
<module>lanova-web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
<scope>compile</scope>
</dependency>
<!-- 其他公共依赖 -->
</dependencies>
</dependencyManagement>
</project>
注意看,修改前的lanova工程没有父工程,修改后父工程是springboot;另外lanova工程的依赖里并没有springMVC等spring特有的依赖,这些都放到web模块的新版pom配置里:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>lanova</artifactId>
<groupId>com.lanplus.lanova</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lanova-web</name>
<description>lanova-web</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova-chassis_control</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova-chassis_control</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lanplus.lanova</groupId>
<artifactId>lanova-camera_control</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以看到,在新版pom中,父工程从springboot变成了lanova,同时添加了对底盘、相机模块的依赖,这样web模块才能正常扮演全局调度的角色。
这样改还有个好处,底盘和相机的pom文件不用改动,他们还是认lanova做父亲,只是他们不知道自己已经是springboot的孙子了😉
总结
本方法非常适合包含各种子系统,并通过springboot向外界暴露出web接口的大型java项目。