之前做的一个巡检机器人项目,硬件分3大块,如图
树莓派通过USB网卡直连到底盘,树莓派和工业相机通过8口交换机实现互联。
机器人干的事就是在变电站自主导航,到预定点位后拍照,最后将照片上传到FTP服务器,供后台图像算法识别分析。
软件运行在树莓派上,采用Java开发,划分成3个模块,分别是
3个模块之间通过线程消息队列
通信,这样既有高性能又有低耦合。
项目一开始我的规划是我只完成前2个,web交互系统交由专门做后台的同事,我将前2个打成jar包后交给他,他当作lib库调用即可。
好几年没写Java了,在简单学习了下maven
和IDEA
后,先开发底盘控制系统。主体开发完后,想加相机控制系统时,发现IDEA
同一个窗口只能打开一个工程,蛋疼。
不想新开工程,因为这样代码就分散在2个git仓库了,于是将底盘和相机都降级为maven模块,原来的工程变成了POM工程
(用在父级工程或聚合工程中,用来做jar工程
的版本控制),而降级的maven模块仍是jar工程
。
父工程的pom配置
<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.0modelVersion>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanovaartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
plugins>
build>
<modules>
<module>lanova-camera_controlmodule>
<module>lanova-chassis_controlmodule>
modules>
<dependencies>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.7version>
dependency>
dependencies>
project>
可以看到,父工程的打包格式字段packaging
为pom
,而不是单模块maven工程默认的jar
子模块底盘控制系统
的pom配置
<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.lanovagroupId>
<artifactId>lanovaartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>lanova-chassis_controlartifactId>
<dependencies>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
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模块就行。另外子模块间能共享相同版本的依赖,比较省事。
相机控制系统
的主体开发完后,通知做后台的同事着手开发web交互系统
,没想到公司在紧急做一个疫情相关的项目,他脱不开身,我想着反正手头也没其他活儿,就自己搞吧。
询问了下后台同事 公司Java Web的技术选型,得知是SpringBoot
,那我也保持一致吧。因为2008年后再也没用Java搞过Web开发,所以简单学习了下SpringBoot
,初始代码基本照抄网上的,包括pom配置:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<relativePath/>
parent>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanova-webartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>lanova-webname>
<description>lanova-webdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
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
文件,截取一部分给大家看看
<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.0modelVersion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.5.RELEASEversion>
<packaging>pompackaging>
<name>Spring Boot Dependenciesname>
<description>Spring Boot Dependenciesdescription>
<url>https://projects.spring.io/spring-boot/#url>
<licenses>
<license>
<name>Apache License, Version 2.0name>
<url>https://www.apache.org/licenses/LICENSE-2.0url>
license>
licenses>
<developers>
<developer>
<name>Pivotalname>
<email>[email protected]email>
<organization>Pivotal Software, Inc.organization>
<organizationUrl>https://www.spring.ioorganizationUrl>
developer>
developers>
<scm>
<url>https://github.com/spring-projects/spring-booturl>
scm>
<properties>
<lombok.version>1.18.12lombok.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-bootartifactId>
<version>2.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<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配置
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<relativePath/>
parent>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanovaartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
plugins>
build>
<modules>
<module>lanova-camera_controlmodule>
<module>lanova-chassis_controlmodule>
<module>lanova-webmodule>
modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.7version>
<scope>compilescope>
dependency>
dependencies>
dependencyManagement>
project>
注意看,修改前的lanova工程没有父工程,修改后父工程是springboot;另外lanova工程的依赖里并没有springMVC等spring特有的依赖,这些都放到web模块的新版pom配置里:
<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.0modelVersion>
<parent>
<artifactId>lanovaartifactId>
<groupId>com.lanplus.lanovagroupId>
<version>1.0-SNAPSHOTversion>
parent>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanova-webartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>lanova-webname>
<description>lanova-webdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanova-chassis_controlartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanova-chassis_controlartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.lanplus.lanovagroupId>
<artifactId>lanova-camera_controlartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
可以看到,在新版pom中,父工程从springboot变成了lanova,同时添加了对底盘、相机模块的依赖,这样web模块才能正常扮演全局调度的角色。
这样改还有个好处,底盘和相机的pom文件不用改动,他们还是认lanova做父亲,只是他们不知道自己已经是springboot的孙子了
本方法非常适合包含各种子系统,并通过springboot向外界暴露出web接口的大型java项目。