现代软件的现状java
1、在这个技术飞速发展的时代,各种用户对软件的要求愈来愈高,软件自己也变得愈来愈复杂程序员
2、所以,软件设计人员每每会采用各类方式对软件划分模块,以获得更清晰的设计及更高的重用性spring
3、当把Maven应用到实际项目中的时候,也须要将项目分红不一样的模块apache
举个例子maven
好比,帐户注册服务就能够划分红account-email,account-persist等5个模块工具
Maven的聚合和继承特性性能
Maven的聚合特性可以把项目的各个模块聚合在一块儿构建测试
Maven的继承特性则能帮助抽象各个模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性,本章将结合实际的案例阐述Maven的这两个特性ui
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