<div id="table-of-contents">

Table of Contents

</div>

最近尝试重整了一个Web项目的Maven pom文件,有一些简单积累,记录在此,供您参考。需要说明的是,本文并非一篇step by step类型的教程,重点在于展示我项目使用Maven的实例,更注重特定场景。

基本概念

Maven是一个强大的构建工具,帮助我们自动化清理、编译、测试和生成报告,还能进行打包和部署。

坐标

Maven希望世界上任何一个构件都可以通过坐标来唯一标识。

坐标(Coordinate)包括: groupId, artifactId, version, packaging(jar or war?), classifier。

也不用解释,举两个例子一看就明白。

举例,我们的交付包:

 

<groupId>com.mycompany.myproj</groupId>
<artifactId>myfeature-bar</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>

又如这个依赖,使用maven构建时会自动从官方Maven库的指定位置去寻找特定版本和jdk版本的jar包:

 

<dependency>
  <groupId>net.sf.json-lib</groupId>
  <artifactId>json-lib</artifactId>
  <version>2.4</version>
  <classifier>jdk15</classifier><!--指定json包的classifier版本-->
</dependency>

约定

Maven的一个思想是”约定优于配置“。使用默认的约定可以减少配置量。比如代码路径就固定在src/main/java,测试路径就固定在src/test/java。

项目结构: 聚合和继承

我们的项目结构如下。包含一个一级项目myproj, 若干个二级项目(myfeature为例)。二级项目myfeature又包含若干三级项目比如myfeature-main, myfeature-foo和myfeature-bar。

└─myproj
    │  pom.xml
    │
    └─myfeature
        │  pom.xml
        │
        ├─myfeature-main
        │      pom.xml
        │
        ├─myfeature-foo
        │      pom.xml
        │
        └─myfeature-bar
                pom.xml

这里就要提到Maven的聚合和继承的概念。聚合是上级模块把许多下级小模块汇聚到一起; 继承则是下级模块从上级模块沿用配置。

在聚合关系中,上级要看到下级,下级看不到上级; 在继承关系中,下级看到上级,上级不关心下级。

而我的项目中,上下级中同时存在着聚合和继承关系。myproj 聚合了myfeature等二级项目,myfeature等二级项目继承了 myproj的pom配置。同理myfeature聚合了myfeature-main, myfeature-bar和myfeature-foo三个三级项目,而这三个三级项目也从 myfeature模继承。

要注意,Maven要求聚合的包只能是pom类型。

最后我们要达到的效果,就是可以灵活地在任意目录执行maven命令,以构建一级、二级或者三级项目。比如我在 myproj/目录下执行mvn compile,将编译所有的二级项目,也就等于编译所有的三级项目。而我在 myproj/myfeature/myfeature-foo/目录下执行mvn clean 则只clean掉myfeature-foo这个三级项目所编译生成的目标文件。

聚合举例

二级项目myfeature聚合myfeature-foo和myfeature-bar:

 

<modules>
    <module>myfeature-foo</module>
    <module>myfeature-bar</module>        
</modules>

继承举例

三级项目myfeature.bar 以 二级项目myfeature为父:

 

<parent>
  <groupId>com.mycompany.myproj</groupId>
    <artifactId>myfeature</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

一些公共的属性可以供子项目继承,比如UTF-8编码。

 

<properties>  
  ...
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
  ...
</properties>

依赖和插件也可以继承。后面再介绍。

项目依赖

Maven的一个很方便的功能就是解决项目的依赖问题。简单说,大部分时候我们定义好依赖的jar包的坐标,Maven 就会自动为我们下载好依赖了。

下面是几个常用的使用技巧。

定义统一的版本号属性便于引用

比如我依赖多个spring框架的包,到处写重复的版本号显然太丑陋。那么我就加属性进行约束:

 

<properties>  
...
<spring.version>4.1.1.RELEASE</spring.version>    
...
</properties>

然后依赖指定时就可以使用定义好的属性:

 

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>${spring.version}</version>
</dependency>

依赖的继承方式

在父项目的pom中,依赖可以有两种写法:

普通的写法,会被子项目直接继承。

 

<dependencies>
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
    ....

当某个依赖不是所有子项目都需要继承,这时候需要更细的控制:

在父pom中使用dependencyManagement元素声明可以被子项目选择性继承的依赖:

 

<dependencyManagement>        
    <dependencies>
      <dependency>
      <groupId>com.aliyun.myfeature</groupId>
      <artifactId>myfeature-sdk-core</artifactId>
      <version>${myfeature.version}</version> 
      <scope>system</scope>
      <systemPath>${myfeature.libpath}/myfeature-sdk-core-${myfeature.version}.jar</systemPath>
    </dependency>

这里scope和systemPath具体作用先不用管。父pom中规定这个依赖的坐标和一些其他属性。子pom中可以不继承这个依赖,只要什么也不做就可以了;也可以选择继承,只需如此:

 

<dependency>
  <groupId>com.aliyun.myfeature</groupId>
  <artifactId>myfeature-sdk-core</artifactId>
</dependency>

版本号,scope,systemPath这样属性都可以不用指定。

添加本地依赖

有时候我们项目需要的依赖可能是一个本地jar包,并不在官方的Maven库中。我们该如何把这个本地jar包加入项目的依赖呢?

我们也有两种做法。

使用mvn install命令手动添加

在jar包位置输入命令: mvn install:install-file -DgroupId=com.aliyun.myfeature -DartifactId=myfeature-sdk-core -Dversion=0.16.4 -Dpackaging=jar -Dfile=myfeature-sdk-core-0.16.4.jar

有多少个要依赖的本地jar包就输入多少条命令。

并在pom.xml中定义对应依赖即可。

在pom中写明lib位置,并在打包时指定包含关系

就我个人而言,我觉得让每个拿到我们项目工程的人都去手动mvn install比较不友好,我还是倾向下面这种做法。

利用前面出现过的scope和systemPath,指定lib的位置:

 

<dependency>
  <groupId>com.aliyun.myfeature</groupId>
  <artifactId>myfeature-sdk-core</artifactId>
  <version>${myfeature.version}</version> 
  <scope>system</scope>
  <systemPath>${myfeature.libpath}/myfeature-sdk-core-${myfeature.version}.jar</systemPath>

需要说明一下的是,这里的myfeature.version 和myfeature.libpath是之前定义好的属性。前面提到过,当你需要多处声明同一内容时,这是一种好的风格:

 

<properties>  
  <myfeature.version>0.17.0-SNAPSHOT</myfeature.version>
  <myfeature.libpath>${project.basedir}/lib/myfeature-sdk-java/${myfeature.version}</myfeature.libpath>
</properties>

然后build过程需要增加一个插件的配置内容。这是Maven进行war打包的插件,Maven会根据它来把对应的本地jar 包打包进war包:

 

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1.1</version>
    <configuration>
        <webResources>
          <resource>
            <directory>${myfeature.libpath}</directory>
            <targetPath>WEB-INF/lib</targetPath>
            <filtering>false</filtering>
            <includes>
              <include>**/myfeature*.jar</include>
            </includes>
          </resource>
        </webResources>
    </configuration>
</plugin>

资源打包

打入文件

其实前面提到过。就是这样:

 

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1.1</version>
    <configuration>
        <webResources>
          <resource>
            <directory>${myfeature.libpath}</directory>
            <targetPath>WEB-INF/lib</targetPath>
            <filtering>false</filtering>
            <includes>
              <include>**/myfeature*.jar</include>
            </includes>
          </resource>
        </webResources>
    </configuration>
</plugin>

排除文件

比如我想在打WAR包时排除src\main\webapp\view\master目录下的内容,可以这样写:

 

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.1.1</version>
  <configuration>    
    <packagingExcludes>view/master/</packagingExcludes>    
  </configuration>
</plugin>

如果你有多种不同配置的环境(开发,测试,演示……)

这是一个非常有用的话题。比如我们需要配置一套环境,分为开发环境和演示环境。开发环境和演示环境访问的数据库位置不同。我们当然想到是可以手动修改配置文件解决这个问题。

如果需要频繁地切换环境呢?如果我有大量这样的可定制或者说可变配置呢?每次编版本或者说打包前手动修改是不是太麻烦了?

下面就介绍一下利用Maven的Profile和Filter特性来解决这个问题。我们将定义不同的profile,对应不同的环境; 然后对不同环境中需要变化的配置文件指定filter去替换其内容。

定义Profile

我们可以理解Profile为不同的环境。

当我在pom中定义:

 

<profiles>
  <profile>
    <id>dev</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
      <filters.env>dev</filters.env>
    </properties>               
  </profile>    
  <profile>
    <id>demo</id>
    <properties>
      <filters.env>demo</filters.env>
    </properties>       
  </profile>
</profiles>

就表明我定义了两种profile,dev对应开发环境,demo对应演示环境。后续当我执行mvn package -Pdemo命令时意味着打包的是演示环境。

activeByDefault标签意味着我把dev环境作为我的缺省profile。则mvn package 时默认是开发环境,效果和mvn package -Pdev一样。

注意这里我们定义了filters.env属性,是为了后面方便使用。

在需要切换或者定制的配置中声明需要替换的内容

假设现在我们有src/main/resources/persistence-mysql.properties这个文件在不同环境中是不一样的。我们把它修改成这样:

 

jdbc.url=${jdbc.url}
jdbc.user=${jdbc.user}
jdbc.pass=${jdbc.pass}

这是什么意思呢,是所有形如${foo}的内容后面会被定义好的foo属性替换。对于这个文件,是数据库的地址、用户和密码将是可替换的。后面我们看哪里会真正获得这些属性。

指定资源文件可以被filter

我在最高级pom文件中build标签下面增加如下内容:

 

<resources>
  <resource>
    <directory>${project.basedir}/src/main/resources</directory>
    <filtering>true</filtering>
  </resource>
</resources>

这一步是告诉Maven,打包过程中,src/main/resources目录下的内容(我们的配置文件就在这里了)是可以被替换的。

指定从哪些文件进行filter

我建立一个fitlers目录,放置两个properties文件。

 

filters
├─demo
│      persistence-mysql.properties
│
└─dev
        persistence-mysql.properties

其中的内容是不一样的,因为它们放的是不同环境下有变化的配置。

dev环境的文件:

 

jdbc.url=jdbc:mysql://10.1.3.23:3306/bigdata?createDatabaseIfNotExist=true
jdbc.user=root
jdbc.pass=123456

demo环境的文件 :

 

jdbc.url=jdbc:mysql://121.43.152.250:3306/bigdata?useUnicode=true&characterEncoding=utf8&createDatabaseIfNotExist=true&zeroDateTimeBehavior=convertToNull
jdbc.user=batman
jdbc.pass=88661234

我在具体项目的pom文件中的build标签下,指明filter的位置:

 

<filters>
    <filter>${basedir}/filters/${filters.env}/persistence-mysql.properties</filter>
</filters>

这就是告诉maven,我们会从这个位置获得filter的内容。

现在我们描述一下打包指定demo的整个执行过程(mvn package -Pdemo):

  • 因为指定的是demo Profile,所以得到filter是 ${basedir}/filters/demo/persistence-mysql.properties;
  • 因为src/main/resources目录下的内容可被filter,所以maven将查找其下面文件中所有形如${}的内容准备替换;
  • 用filter中的相关内容替换src/main/resources/persistence-mysql.properties的 ${jdbc.url}, ${jdbc.user},和${jdbc.pass}。

原理是不是很简单呢?现在你可以在filters目录下加入类似的配置文件了。

生命周期和插件(待补充)

注意事项

  • 显式安装Maven而不是使用IDE”赠送“的Maven
  • 一开始就使用Maven构建项目而不是强依赖某个IDE


最后更新: 2015-06-26 14:22