虽然在Java平台下,各类构建工具如Maven、Gradle、SBT已经获得了较为普遍地运用,同时Maven约定的模块目录结构也获得了业界的承认,成为了Java平台下项目结构的事实标准。但咱们总没法避免与各类遗留系统或老系统打交道。在没有Maven的时代,是Ant统治的时代,它由于提供了较为灵活的编写Task的功能,而忽略了制定一套看似呆板,实则有效的标准模板。因而,在不一样的企业,不一样的Java项目,出现了各类各样奇怪的目录结构与打包要求。拖着这些沉重的历史包袱,咱们天然但愿完全革命,把那些看不顺眼的东西所有改造,让整个项目面目一新。这是好事儿,所谓长痛不如短痛,一会儿把问题肿瘤给割了,痛快!但是现实总有那么一些阻力会让咱们缩手缩脚,咱们不能挥起革命的利刃一阵乱砍,这弄很差会砍伤本身的手。因而乎,咱们须要作战略性的撤退。退一步海阔天空嘛。java
我面对的就是这样一个软件系统。这个Java开发的软件系统一直没有依赖管理,仅仅编写了Ant任务用于发布打包。咱们的任务是渐进地引入Maven,并在从Build到deploy的整个生命周期中,逐步替换Ant,与持续集成搭配起来。这个系统的多数模块都划分了服务端与客户端。然而不巧的是,各个模块的服务端和客户端都集中在一个模块中。同时,这个项目的目录结构并不是标准的Maven结构,以下图所示。所以,还须要自定义Source与TestSource的目录结构。在原来的Ant任务中,是将它们打包成了两个Jar包。如今,咱们须要在Maven中一样作到这一点。apache
分析这个目录结构,无非是在打包时,对文件进行include或exclude。我查阅到Maven的一位开发者Tim O’Brien写的一篇博客Sonatype的博客,详细介绍了具体的作法。固然,在博客中,他一再强调了这种作法的不可取,建议在项目模块上作出好的分解,保证一个Module对应一个Jar包。这篇博客介绍了两种作法,一个是在Profile中定义,一个则是在build中定义,使用的插件皆为maven-jar-plugin。对于我要解决的问题,能够考虑选择使用第二种作法,由于它只须要执行一条mvn package命令就能同时获得Server和Client的Jar包。具体的作法就是在插件的配置中,include各自的文件夹便可。配置以下:maven
<groupId>com.test.maven</groupId> <artifactId>testmaven</artifactId> <version>1.0-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <testSourceDirectory>testSrc</testSourceDirectory> <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> <executions> <execution> <id>server</id> <goals><goal>jar</goal></goals> <phase>package</phase> <configuration> <classifier>server</classifier> <includes> <include>**/server/**</include> </includes> </configuration> </execution> <execution> <id>client</id> <goals><goal>jar</goal></goals> <phase>package</phase> <configuration> <classifier>client</classifier> <includes> <include>**/client/**</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> |
经过这样的配置,运行mvn package能够生成三个包,其中testmaven-1.0-SNAPSHOT.jar同时包含了服务端和客户端的类;而服务端和客户端对应的Jar则为testmaven-1.0-SNAPSHOT-server.jar和testmaven-1.0-SNAPSHOT-client.jar。工具
在前面的配置中,咱们并无为server和client包定义本身的坐标,而是沿用了统一的一个。这就意味着依赖这个包的其余Module,可能没法经过Dependency来精肯定位Server或Client。这对于部署来讲,是没有问题的,但却没法进行依赖管理;除非在依赖的时候,去依赖整个大的模块。oop
要保证依赖管理,就意味着须要为server和client分别指定各自的坐标。看来须要另辟蹊径。其实,Maven是支持在一个项目中创建多个子模块的。咱们能够考虑在项目中引入两个子模块,分别对应server和client,并在这两个子模块中创建本身的pom.xml文件。这在本质上是与Maven多模块支持是相同的,惟一不一样的是代码结构。并且这种新建模块并无影响原有的目录结构,对于遗留系统而言,仍是能够接受的。所以,咱们创建了以下图所示的模块结构:单元测试
在新的结构中,除了原有模块外,我还引入了另外两个新的模块server和client,它们除了拥有本身的pom.xml文件,没有其余任何内容。而在原有模块下,一样定义了一个pom.xml文件,它将做为整个项目的parent。定义以下:测试
<groupId>com.test.maven</groupId> <artifactId>testmaven</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>server</module> <module>client</module> </modules> |
这里定义的坐标是整个项目的坐标,同时指定了packaging的类型为pom。在这个pom.xml文件中还包括了两个子模块,其中的值应该与模块的名称对应。接下来配置server模块的pom.xml:ui
<parent> <groupId>com.test.maven</groupId> <artifactId>testmaven</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>testmaven-server</artifactId> <build> <sourceDirectory>../src</sourceDirectory> <testSourceDirectory>../testSrc</testSourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <includes> <include>**/server/**</include> </includes> </configuration> </plugin> </plugins> </build> |
首先声明了parent指向了主模块的坐标。接下来,声明当前模块的artifact id为testmaven-server。这就为server指定了独立的坐标。一旦部署后,在maven的Repository中会获得这样的文件:com/test/maven/testmaven-server/1.0-SNAPSHOT/testmaven-server-1.0-SNAPSHOT.jar。咱们就能够在依赖中这样声明:插件
<dependencies> <dependency> <groupId>com.test.maven</groupId> <artifactId>testmaven-server</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> |
运行mvn package,oops……居然出现问题了。什么问题呢?单元测试没法经过。报告的错误为:3d
Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile (default-testCompile) on project testmaven-server: Compilation failure: Compilation failure: [ERROR] /Users/twer/learn/testmaven/testSrc/com/test/maven/client/HelloMavenTest.java:[11,9] cannot find symbol [ERROR] symbol : class HelloMaven [ERROR] location: class com.test.maven.client.HelloMavenTest |
仔细分析,原来是在执行编译server包时,报告没法编译client包对应的测试类。怎么会在编译server包时,去编译client包对应的测试呢?仔细观察咱们的pom.xml文件,在maven-compiler-plugin插件中,咱们配置了对server文件的引入,这就意味着在编译server包时,不会引入client文件夹下的全部文件(固然在这里就是Java类文件)。可是,咱们并无在test-compile阶段排除client对应的测试文件。这就致使client的测试没法找到对应的实现类。找到根源,问题就好解决了,显然咱们须要在test-compile阶段排除client文件夹。因此,server模块下正确的pom.xml配置为:
<parent> <groupId>com.test.maven</groupId> <artifactId>testmaven</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>testmaven-server</artifactId> <build> <sourceDirectory>../src</sourceDirectory> <testSourceDirectory>../testSrc</testSourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <includes> <include>**/server/**</include> </includes> </configuration> <executions> <execution> <id>default-testCompile</id> <phase>test-compile</phase> <configuration> <testExcludes> <exclude>**/client/**</exclude> </testExcludes> </configuration> </execution> </executions> </plugin> </plugins> </build> |
注意:我在IntelliJ配置execution下的configuration节时,碰到一个问题,那就是针对testExcludes配置节没有智能提示。因为其余maven配置节在正确状况下都有智能提示,于是让我产生错误,认为这个配置项不支持testExcludes,这让我纠结了好半天。
对于client模块而言,如法炮制,只是包含以及过滤的文件夹反转了一个个儿而已。当咱们进行install甚至deploy时,在repository下的test/maven文件夹中,看到了三个文件夹,如图所示:
其中的testmaven/1.0-SNAPSHOT文件夹下并无jar包,由于它对应的配置为主模块的配置,也就是parent配置。在这个配置中,咱们将packaging的类型设置为pom了。