首页 > 代码库 > Maven实战读书笔记(9)
Maven实战读书笔记(9)
现代软件的现状
1、在这个技术飞速发展的时代,各类用户对软件的要求越来越高,软件本身也变得越来越复杂
2、因此,软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性
3、当把Maven应用到实际项目中的时候,也需要将项目分成不同的模块
举个例子
比如,账户注册服务就可以划分成account-email,account-persist等5个模块
Maven的聚合和继承特性
Maven的聚合特性能够把项目的各个模块聚合在一起构建
Maven的继承特性则能帮助抽象各个模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性,本章将结合实际的案例阐述Maven的这两个特性
account-persist模块的作用?
该模块负责账户数据的持久化,以XML文件的形式保存账户数据,并支持账户的创建、读取、更新、删除等操作
maven-resources-plugin插件
使用UTF-8编码处理资源文件
为什么Maven要有聚合的功能?
比如,你一个项目有两个模块,如果没有聚合的话,你需要在两个模块的目录下分别执行mvn命令,如果有十个模块,那会疯掉
有了聚合就可以只执行一次mvn命令,每个模块会自动执行
如何配置聚合?
1、首先我们有两个模块分别是account-email和account-persist,要把它们两个实现聚合
2、创建一个account-aggregator模块,通过该模块构建整个项目的所有模块
account-aggregator模块配置如下
<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/maven/-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-aggregator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Aggregator</name>
<modules>
<module>account-email</module>
<module>account-persist</module>
</modules>
</project>
对以上配置进行解释:
1、有共同的groupId——com.juvenxu.mvnbook.account
2、artifactId为独立的——account-aggregator
3、版本号与其他的两个模块一致
4、packaging其值被设置成POM,而其他两个模块都用了默认值jar
5、对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建
6、modules元素是实现模块的聚合最核心的配置,module里面配置是当前目录的相对路径
一般模块聚合的通用约定
1、为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在
2、这样当用户得到源码的时候,第一眼发现的就是聚合模块的pom不用从多个模块中去寻找聚合模块来构建整个项目
3、聚合模块只有一个pom.xml文件,没有src/main/java、src/test/java等目录,这也很容易理解,聚合模块仅仅是帮助聚合其他模块构建的工具,它本身并无实质的内容
注意:聚合模块与其他目录结构并非一定要是父子关系,平行目录结构也是可以的
Maven的继承机制
1、大量的前人经验告诉我们,重复往往就意味着更多的劳动和更多潜在的问题
2、在面向对象世界中,程序员可以使用类继承在一定程度上消除重复
3、在Maven的世界中,也有类似的机制能让我们抽出重复的配置,这就是POM的继承
如何配置Maven的继承?
1、比如在account-aggregator聚合模块下,创建一个名为account-parent的子目录,然后再该子目录下建立一个所有除account-aggregator之外的模块的父模块
2、在该子目录下创建一个pom.xml文件
account-parent的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/maven/-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Parent</name>
</project>
对上面配置进行说明:
1、groupId和version和其他模块一致
2、artifactId为——account-parent
3、packaging为pom,这一点与聚合模块一样,作为父模块的POM,其打包类型也必须为pom
4、父模块只有pom.xml,没有src/main/java之类的文件夹
有了父模块那么子模块如何配置?
修改account-email继承account-parent
<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/maven/-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
<artifactId>account-email</artifactId>
<name>Account Email</name>
<dependencies>
...
</dependencies>
<build>
<plugins>
...
</plugins>
</build>
</project>
对上述配置进行解释:
1、上述POM中使用parent元素声明父模块,parent下的子元素groupId、artifactId和version指定了父模块的坐标,这三个元素是必须的
2、元素relativePath表示父模块POM的相对路径,../account-parent/pom.xml
3、当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找,relative的默认值是../pom.xml,也就是说,Maven默认父POM在上一层目录
4、这个POM没有为account-email声明groupId和version,因为其隐式地从父模块继承了这两个元素,这也就消除了一些不必要的配置,但是对于artifactId子模块需要显式声明的
5、account-persist模块的配置类似
6、最后还需要把account-parent加入到聚合模块account-aggregator中,比如修改成下面这样的配置:
<modules>
<module>account-parent</module>
<module>account-email</module>
<module>account-persist</module>
</modules>
还有哪些POM元素可以被继承呢?
是的,除了groupId version等可以继承,还有以下的一些元素可以被继承
1、groupId:项目组ID,项目坐标的核心元素
2、version:项目版本,项目坐标的核心元素
3、description:项目的描述信息
4、organization:项目的组织信息
5、inceptionYear:项目的创始年份
6、url:项目的URL地址
7、developers:项目的开发者信息
8、contributors:项目的贡献者信息
9、distributionManagement:项目的部署配置
10、issueManagement:项目的缺陷跟踪系统信息
11、ciManagement:项目的持续集成系统信息
12、scm:项目的版本控制系统信息
13、mailingLists:项目的邮件列表信息
14、properties:自定义的Maven属性
15、dependencies:项目的依赖配置
16、dependencyManagement:项目的依赖管理配置
17、repositories:项目的仓库配置
18、build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
19、reporting:包括项目的报告输出目录配置、报告插件配置等
如何控制依赖的继承?
1、Maven提供的dependenyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性
2、在denpendenyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用
在account-parent中配置depencyManagement元素
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Parent</name>
<properties>
<springframework.version>2.5.6</springframework.version>
<junit.version>4.7</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的
继承了dependencyManagement的account-email POM
<properties>
<javax.mail.version>1.4.1</javax.mail.version>
<greenmail.version>1.3.1b</greenmail.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>${greenmail.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
对上述配置进行解释:
1、所有的springframework依赖只配置了groupId和artifactId,省去了version
2、junit依赖不仅省去了version,还省去了依赖范围scope
配置继承的好处?
1、使用这种依赖管理机制似乎不能减少太多的POM配置
2、不过还是强烈推荐采用这种方法,其主要原因在于在父POM中使用dependencyManagement声明依赖能够统一项目范围中的依赖的版本
3、当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无需声明版本,也就不会发生多个子模块使用依赖版本不一致的情况
4、如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果,也就是说在子pom不声明依赖,那么该依赖就不会被引入。这正是dependencyManagement的灵活性所在
关于import的依赖范围
1、import的依赖范围只在dependencyManagement元素下才有效果
2、使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中
3、也就是说,除了复制配置或者继承两种方式之外,还可以使用import范围依赖将这一配置导入(dependencyManagement的配置)
使用import范围依赖导入依赖管理配置
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意,上述代码中依赖的type值为pom,import范围依赖由于其特殊性,一般都是指向打包类型为pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后再各个项目中导入这些依赖管理配置
插件管理
1、Maven提供了dependencyManagement元素帮助管理依赖,类似地,Maven也提供了pluginManagement元素帮助管理插件
2、在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为
在父POM中配置pluginManagement
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
当子模块需要生成源码包时,只需要如下简单的配置
继承了pluginManagement后的插件配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
</build>
1、如果子模块不需要使用父模块的pluginManagement配置的插件,可以尽管将其忽略
2、如果子模块需要不同的插件配置,自行覆盖,妥妥的
聚合与继承的关系
1、多模块的Maven项目中的聚合与继承其实是两个概念,其目的也是完全不同的
2、前者主要是为了方便快速构建项目,后者主要是为了消除重复配置
3、对于聚合模块来说,它知道有哪些被聚合的模块,但哪些聚合的模块不知道这个聚合模块的存在
4、对于继承关系的父POM来说,它不知道哪些子模块继承了它,但哪些子模块都必须知道自己的父POM是什么
5、如果非要说这两个特性的共同点,那么,聚合POM与继承关系中的父POM的packaging都必须是pom,同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容
6、可以将聚合模块和集成模块合并
约定大于配置的优势?
1、所有程序员都基于HTTP协议开发Web应用,不然互联网会乱成什么样子
2、Java成功的重要原因之一是它能屏蔽大部分操作系统的差异
3、XML流行的原因之一是所有语言都接受它
为什么使用约定而不是自己更灵活的配置呢?
使用约定可以大量减少配置
构建简单项目使用的Maven配置文件
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
这段配置非常的简洁,但用户需要付出一定的代价,那就是遵循Maven的约定,Maven会假设用户的项目是这样的:
1、源码目录为src/main/java/
2、编译输出目录为target/classes/
3、打包方式为jar
4、包输出目录为target/
遵循约定虽然损失了一定的灵活性,用户不能随意安排目录一结构,但是却能减少配置更重要的是,遵循约定能够帮助用户遵守构建标准
如果我不想遵守约定该怎么办?
首先先问自己三遍,你真的需要这么做吗?
如果仅仅是因为喜好,就不要耍个性,个性往往意味着牺牲通用性,意味着增加无谓的复杂度
使用Maven自定义源码目录
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
<sourceDirectory>src/java</sourceDirectory>
</project>
对上面的配置进行解释:
1、这个例子中将源码目录改成了src/java而不是默认的src/main/java
2、这往往会造成交流问题,习惯Maven的人会奇怪,源代码跑那里去了
3、当这种自定义大量存在的时候,交流成本就会大大提高
4、只有在一些特殊的情况下,这种自定义配置的方式才应该被正确使用以解决实际问题
5、例如你在处理遗留代码,并且没有办法更改原来的目录结构,这个时候就之只能让Maven妥协
关于超级POM
1、任何一个Maven项目都隐式地继承自该POM
2、大量超级POM的配置都会被所有Maven项目继承,也是Maven所提倡的
3、在Maven 3中,超级POM在文件MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下
4、在Maven 2中,超级POM在文件MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/model/pom-4.0.0.xml目录下,这里的x.x.x表示Maven的具体版本
超级POM中关于仓库的定义
<repositories>
<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enable>false</enable>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
超级POM中关于项目结构的定义
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources>
</build>
对上面代码进行解释:
1、这里依次定义了,项目的主输出目录、主代码输出目录、最终构件的名称格式、测试代码输出目录、主源码目录、脚本源码目录、测试源码目录、主源码目录和测试资源目录
2、这就是Maven项目结构的约定
超级POM中关于插件版本的定义
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-4</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
</plugin>
...
</plugins>
</pluginManagement>
什么是反应堆?
1、在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构
2、对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序
反应堆的构建顺序
比如account-aggregator的聚合配置如下:
<modules>
<module>account-email</module>
<module>account-persist</module>
<module>account-parent</module>
</modules>
然后开始构建account-aggregator会看到如下的输出
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Account Aggregator
[INFO] Account Parent
[INFO] Account Email
[INFO] Account Persist
[INFO] ------------------------------------------------------------------------
上述输出依次为account-aggregator、account-parent、account-email和account-persist
我们看到输出顺序不是按modules元素中定义的顺序
那么,实际的构建顺序是按什么规则呢?
实际的构建顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖
有向非循环图 (Directed Acyclic Graph, DAG)
模块间的依赖关系会将反应堆构成一个有向非循环图 (Directed Acyclic Graph, DAG)
各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错
什么是裁剪反应堆?
一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块,换句话说,用户需要实时地裁剪反应堆。
通过命令行选项支持裁剪反应堆
输入mvn -h 可以看到这些选项:
n -am,--also-make 同时构建所列模块的依赖模块
n -amd,-also-make-dependents 同时构建依赖于所列模块的模块
n -pl,--projects <arg> 构建指定的模块,模块间用逗号分隔
n -rf -resume-from <arg> 从指定的模块回复反应堆
使用-pl选项指定构建某几个模块
mvn clean install -pl account-email, account-persist
使用-am选项可以同时构建所列模块的依赖模块
mvn clean install -pl account-email -am
构建account-parent和account-email
使用-amd选项可以同时构建依赖于所列模块的模块
mvn clean install -pl account-parent -amd
构建account-parent和其依赖account-email、account-persist
使用-rf选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建
mvn clean install -rf account-email
只会看到account-email和account-persist的构建
在-pl -am 或者-pl -amd的基础上,还能应用-rf参数,以对裁剪后的反应堆再次裁剪
mvn clean install -pl account-parent -amd -rf account-email
Maven实战读书笔记(9)