之前有个项目使用的 Ant 构建出来的,当时为了节省时间,就在里面填填补补,导致现在感觉项目比较凌乱。于是花了大约2周的时间将 Ant 重构成了 Maven,由于第一次使用,好多地方也不是很明白,没有达到100%还原。

这里只简单记录一下耗时比较长的坑,一些简单的内容可以通过谷歌直接解决了。

Maven 划分模块

一个典型的 Maven 项目包含 父模块子模块 ,父模块主要用于整合项目,声明一些基本的变量数据集,统筹各个子模块之间的关系。每个子模块最好只包含一个单独的功能。

关于模块的打包方式有 pomjarwar 等,父模块只能为 pom ,子模块根据需要可以声明为不同的打包格式。

在这个项目分成的结构示意如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---- app-parent
|-- pom.xml (pom)
|
|-- app-conf
| |-- pom.xml (pom)
|
|-- app-a
| |-- pom.xml (jar)
|
|-- app-b
| |-- pom.xml (jar)
|
|-- app-dist
|-- pom.xml (directory)

app-parent 是父模块, app-conf 放置一些单独的配置文件, app-aapp-b 是项目的一个jar包, app-dist 用于将项目中的内容组合成符合要求的目录格式。

Maven assembly

Maven 是一种 约定 型构建语言,即使很少的配置,只要位置放置正确,也能生成符合要求的jar包。但是,现实情况下往往需要生成复杂的目录结构,Maven 中含有很多的打包插件可以解决这个问题,这里使用的是 maven-assembly-plugin 打包插件。它会根据xml声明的配置来组织打包结构。

主要的结构如下:

1
2
3
4
5
6
7
8
9
10
<assembly>
[...]
<dependencySets>
</dependencySets>
<moduleSets>
</moduleSets>
<fileSets>
</fileSets>
[...]
</assembly>

dependencySets 处理模块依赖的jar如何存放, moduleSets 处理单独的模块如何存放, fileSets 处理自定义文件如何存放。每个标签下面都可以指定模块名称和输出目录。

举个例子:

  1. 将模块 app-a 依赖 放入 /lib 目录下
  2. 将模块 app-b 放在根目录下
  3. app-conf 放在 /conf 目录下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<assembly>
[...]
<dependencySets>
<!-- 将app-a的依赖打包到 /lib 目录 -->
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<includes>
<include>xx.xx.xx:app-a</include>
</includes>
</dependencySet>
</dependencySets>
<moduleSets>
<!-- 将app-b的jar复制到根目录 -->
<moduleSet>
<includes>
<include>xx.xx.xx:app-b</include>
</includes>
<binaries>
<outputDirectory>/</outputDirectory>
<unpack>false</unpack>
<includes>
<include>*</include>
</includes>
</binaries>
</moduleSet>
</moduleSets>
<fileSets>
<!-- 将app-conf目录下的内容复制到 /conf 目录 -->
<fileSet>
<directory>../app-conf/</directory>
<outputDirectory>./conf</outputDirectory>
</fileSet>
</fileSets>
[...]
</assembly>

assembly 还有很多配置,暂时用到的只有这么多。

可运行 jar 打包

可运行的 jar 包在文件内会包含 MANIFEST.MF 文件,在 ant 构建的时候,这个文件是自己书写并提供的。通过 maven-jar-plugin 插件可以指定 mainClass 并自动将依赖写入 MANIFEST.MF 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[...]
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>xx.xx.xx.Xxxx</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
[...]

但是,这里存在一个小问题,如果我们使用的jar包在maven库中找不到,你可能会通过本地引入的方式应用它(ScopeSystem)。

1
2
3
4
5
6
7
<dependency>
<groupId>lib</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.4</version>
<scope>system</scope>
<systemPath>${basedir}/lib/commons-logging.jar</systemPath>
</dependency>

这样虽然可以编译通过,但Maven却不将其作为标准 依赖 ,通过assembly和MANIFEST.MF都无法对其进行处理。

一个比较好的解决方案是创建一个项目专有的 私有仓库 。在父模块下创建一个文件夹 repo 作为项目的私有仓库。

1
2
3
4
---- app-parent
|-- pom.xml (pom)
|
|-- repo

复制jar包到私有仓库,使用maven命令

1
2
3
mvn install:install-file -Dfile=<path-to-file> -DgroupId=<myGroup> \
-DartifactId=<myArtifactId> -Dversion=<myVersion> \
-Dpackaging=<myPackaging> -DlocalRepositoryPath=<path>

例如:

1
2
3
mvn install:install-file -Dfile=/opt/a.jar -DgroupId=a.b.c \
-DartifactId=logging -Dversion=1.0.0 \
-Dpackaging=jar -DlocalRepositoryPath=./repo

执行完成后就可以在 repo 文件夹下看到新加入的jar包。

在项目中引用私有仓库的jar包,首先要添加私有仓库的地址。如果包含子模块,不建议将仓库地址放在父模块的 pom.xml 文件内。

1
2
3
4
5
6
7
8
<repositories>
<repository>
<id>my-local-repo</id>
<url>file://${basedir}/repo</url>
<!-- 子模块中请使用 -->
<url>file://${basedir}/../repo</url>
</repository>
</repositories>

之后像其它 Maven 依赖一样引用就可以了。

1
2
3
4
5
<dependency>
<groupId>a.b.c</groupId>
<artifactId>logging</artifactId>
<version>1.0.0</version>
</dependency>

参考文献