Maven多模块项目经验

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alexanderzjs/article/details/53813403

最近被一个多模块项目搞的头晕眼花,最后经过多次摸索终于发现了如何使用maven来创建多模块的项目。现在把一些经验和教训总结一下供后来人借鉴和指教。

maven的作用

毋庸置疑,maven是用来创建一个项目的基本构架以使得这个工程可以方便的重复构建。

maven的生命周期

谈到项目,不得不提到一个基本项目的生命周期,也就是说在项目被maven打包成jar, war等等不同的包装的时候都需要经过哪些步骤呢?一般来讲,需要经过一下常用步骤:

  1. 编译(compile): 不做解释了,编译源文件成为二进制文件。
  2. 测试(test): 运行编译后的单元测试。
  3. 打包(packaging): 把二进制文件打包成为指定的格式。
  4. 安装(install): 把打包好的指定格式文件安装到本地仓库中。
  5. 部署(deploy): 把指定的文件安装到远程仓库中。

举个例子,如果你想要打包你的文件成为一个jar文件,那么你需要一下的maven配置文件(俗称pom.xml):

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.app(项目姓氏)</groupId>
  <artifactId>my-app(项目名字)</artifactId>
  <version>1.0-SNAPSHOT(项目版本号)</version>
  <packaging>jar(项目打包方式)</packaging>

</project>

这就是一个最最简单的maven项目了,在maven项目里面,前面两个标签都是默认的。

maven的打包方法

好了,基本信息确定了以后,接下来就需要我们来进行实际的打包操作了。那么,如何去打包一个项目?

你需要指定一些插件来完成具体编译,打包的过程。比如说,我想要生成一个jar文件(project.jar)和源文件包(project-sources.jar),那么,我需要的maven的插件有两个,一个是maven-jar-plugin,另一个是maven-sources-plugin。

在pom.xml里面,我们需要指定这两个插件的使用方法,例子如下所示:

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins(插件姓氏)</groupId>
        <artifactId>maven-jar-plugin(插件名字)</artifactId>
        <version>2.6(版本号)</version>
        <executions>
          <execution>
            <id>compile-my-project(操作名称)</id>
            <phase>compile(执行此操作的生命周期)</phase>
            <goals>
              <goal>jar(此操作的目标)</goal>
            </goals>
            <configuration>
              自己定义一些操作的配置信息
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

关于操作的具体配置信息,仅举一例,比如说部署插件(maven-deploy-plugin)的官网http://maven.apache.org/plugins/maven-deploy-plugin/上面写到,部署插件有两个目标,一个叫做deploy:deploy(冒号前面的是插件从属的生命周期,后面的是具体的目标),另一个叫做deploy:deploy-file。其中第一个是默认的,也就是说,如果你不指定操作的方法,只给了插件姓名和版本号,那么第一个操作就会默认执行。

当你点开deploy:deploy-file的具体用法(http://maven.apache.org/plugins/maven-deploy-plugin/deploy-file-mojo.html),你会看到一些需要的参数,比如说file, repositoryId, url等等。这些参数每个都可以当作配置的具体项目来使用。

<configuration>
  <file>c:\program\project.jar</file>
  <repositoryId>server.name</respositoryId>
  <url>http://myserver.url/</url>
</configuration>

具体用法详见maven官网。不过,官网上的例子一般来说是以命令行的方式出现的。比如说http://maven.apache.org/plugins/maven-deploy-plugin/examples/disabling-generic-pom.html

mvn org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy-file 
-Durl=file:///C:/m2-repo \                                                                            -DrepositoryId=some.id \
-Dfile=path-to-your-artifact-jar \                                              -DgroupId=your.groupId \                                               -DartifactId=your-artifactId \                                                -Dversion=version \                                                    -Dpackaging=jar \                                                      -DgeneratePom=false

其实这里的-D后面的东西,你完全可以放在<configuration></configuration>里面去。

maven添加项目依赖

maven的一大功能就是方便的处理项目依赖,比如说,我的project.jar需要依赖a.jar, b.jar. c.jar来编译project.jar里面的源文件。那么,这个时候,只需要在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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>company.app.com(a的姓氏)</groupId>
      <artifactId>a(a的名字)</artifactId>
      <version>1.0-SNAPSHOT(a的版本号)</version>
      <type>jar(a的类型)</type>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.6</version>
        <executions>
          <execution>
            <id>compile-my-project</id>
            <phase>compile</phase>
            <goals>
              <goal>jar</goal>
            </goals>
            <configuration>
              <file>c:\program\project.jar</file>
              <repositoryId>server.name</respositoryId>
              <url>http://myserver.url/</url>           
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

maven插件和依赖的管理

这个时候,一切工作正常,没有任何问题。但是,当越来越多的依赖被加入,越来越多的插件被需要的时候,管理起来就很麻烦了。特别是你的工程是多层的(一个工程有多个子工程,子工程又有它们自己的子工程)。这个时候,你往往会想要统一管理这些依赖和插件。

你需要<dependencyManagement><pluginManagement>来做这件事情。举个例子,现在的project项目有两个子项目child1和child2。child1需要依赖a, b,child2只需要b, 但是b有好多版本,我希望两个子项目用同样版本的b,那么怎么办呢?这个时候,在project的pom里面加入这样一段代码:

<dependencyManagement>  
  <dependencies>
    <dependency>
      <groupId>company.app.com</groupId>
      <artifactId>b</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>jar</type>
    </dependency>
  </dependencies>
<dependencyManagement>

这样一来,在child1的pom和child2的pom里面,分别加上

  <dependencies>
    <dependency>
      <groupId>company.app.com</groupId>
      <artifactId>b</artifactId>
    </dependency>
  </dependencies>

就可以了,版本号信息会自动从父工程的pom的<dependencyManagement>里面继承。 <pluginManagement>同理。

maven依赖的传递性

什么是依赖的传递性呢?通俗点讲,就是一个项目project需要依赖a,但是依赖a又依赖aa。问题就是,在project的pom里面需要把aa也加上么?

其实不用,因为在maven里面,依赖是有传递性的,你只需要指定依赖a,a a会在构建项目的时候自动被添加称为project的依赖。

maven多模块项目

至此为止,相信你已经对maven单模块项目的构建足够了解了。那么,下面我们聊聊maven的多模块项目。假设现在我们要构建一个项目叫做parent,下面有三个字模块,child1, child2, child3。我们希望三个模块各自分别能够编译成jar,但是,我们不想要部署这个jar,我们想要生成一个大的jar,叫做parent.jar。

那么,我们首先要让parent意识到子模块的存在。假设parent所在目录有三个文件夹(dir1, dir2, dir3)存放这三个模块。我们首先要在parent的pom里面加入<modules>:

<modules>
  <module>dir1</module>
  <module>dir2</module>
  <module>dir3</module>
</modules>

然后,我们需要一个新的模块来做组装整合子模块的工作,同时把这个模块加入到<modules>:

<modules>
  <module>dir1</module>
  <module>dir2</module>
  <module>dir3</module>
  <module>aggregate</module>
</modules>

对于每一个子模块,因为我们不想要部署它们,所以,要把部署插件的默认行为改为什么都不做。为此,只需要把maven-deploy-plugin的操作改成如下操作就可以了

<executions>
  <execution>
    <id>default-deploy</id>
    <phase>none</phase>
  </execution>
</executions>

对于整合其他子模块的那个子模块(aggregate),有两个问题需要解决:1.如何生成整合打包后的源文件和二进制文件;2. 如何处理依赖的可传递性。

maven多模块如何生成源文件和二进制文件

对于整合后的打包文件,一般来讲,这个文件就是子模块的文件合集。所以,只要把子模块的jar解压缩就可以了。

解压缩jar有好多种方法,比如依靠maven-assembly-plugin。但是,这个插件非常不给力,提供的可操作参数太少。所以,以我的经验,用maven-antrun-plugin可能是一个更好的选择。顾名思义, antrun是一个可以在maven里面运行ant脚本的插件。具体的使用实例如下:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <id>buildFiles</id>
      <phase>package</phase>
        <configuration>
          <target>
            <ant antfile="src/antbuild/buildfile.xml">
              <target name="main"/>
            </ant>
          </target>
        </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

上述实例表明了我要在package的阶段执行一个ant脚本,ant脚本名字是buildfile.xml。对于具体ant脚本的应用可以参考apache的ant官网https://ant.apache.org/

在多模块项目中,ant的作用有三方面:
1. 解压缩jar文件到指定目录 (ant copy task,mkdir task,unzip task)
2. 重新构建一个新的pom文件 (ant javac task,java task)
3. 打包解压缩文件到新的jar文件(ant jar task)

其中第二部的作用是为了正确处理依赖的可传递性,用到的方法是通过ant的java task,详情请见https://ant.apache.org/manual/Tasks/java.html

maven多模块如何处理依赖的可传递性

在这里,详细举一个例子来说明如何生成一个新的pom来处理依赖的可传递性。

<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="BuildFiles" basedir="." default="main">
    <!-- 首先声明一些路径相关的属性信息 -->
    <property name="project.dir" value="${basedir}"/>
    <property name="parent.dir" value="${project.dir}/../"/>
    <property name="target.dir" value="${project.dir}/target"/>
    <!--这里是创建目录和解压缩文件到创建目录的过程-->
    <target name="unzip">
        <!--解压缩二进制文件-->
        <mkdir dir="${target.dir}/binary"/>
        <unzip dest="${target.dir}/binary">
            <patternset>
                <exclude name="**/META-INF/**"/>
                <exclude name="**/*.properties"/>
                <exclude name="**/context.xml"/>
            </patternset>
            <fileset dir="${parent.dir}/">
                <include name="**/target/*.jar" />
                <exclude name="**/target/signinext.jar"/>
                <exclude name="**/target/*-sources.jar"/>
            </fileset>
        </unzip>
        <unzip dest="${target.dir}/binary">
            <patternset>
                <exclude name="**/com/**"/>
            </patternset>
            <fileset dir="${project.dir}/">
                <include name="**/target/*.jar" />
                <exclude name="**/target/*-sources.jar"/>
            </fileset>
        </unzip>        
        <!--解压缩源文件-->
        <mkdir dir="${target.dir}/sources/"/>
        <unzip dest="${target.dir}/sources">
            <patternset>
                <exclude name="**/META-INF/**"/>
                <exclude name="**/*.properties"/>
                <exclude name="**/context.xml"/>
            </patternset>
            <fileset dir="${parent.dir}/">
                <include name="**/target/*-sources.jar" />
                <exclude name="**/target/parent-sources.jar"/>
            </fileset>
        </unzip>
        <unzip dest="${target.dir}/sources">
            <patternset>
                <exclude name="**/com/**"/>
            </patternset>
            <fileset dir="${project.dir}/">
                <include name="**/target/*-sources.jar" />
            </fileset>
        </unzip>
        <mkdir dir="${target.dir}/pomsources/"/>   
    </target>
    <!--编译一个java的文件来生成新的pom-->
    <target name="compile" depends="unzip">
        <javac executable="javac" 
                target="1.7"
                fork="true"
                includeantruntime="false" 
                srcdir="${project.dir}/src/main/java" 
                destdir="${target.dir}">
        </javac>
    </target>
    <!--执行这个java文件-->
    <target name="run" depends="compile">
        <java fork="true" classname="com.mycompany.app.BuildFiles">
            <arg value="${project.dir}"/>
            <classpath>
                <pathelement path="${target.dir}"/>
            </classpath>
        </java>
    </target>
    <!--打包解压缩的文件到两个jar包-->
    <target name="main" depends="run">
        <delete dir="${target.dir}/*.jar"/>
        <jar destfile="${target.dir}/${project.artifactId}-${project.version}.jar" basedir="${target.dir}/binary"/>
        <jar destfile="${target.dir}/${project.artifactId}-${project.version}-sources.jar" basedir="${target.dir}/sources"/>
    </target>
</project>

对于这个BuildFile.java所做的事情,其实很简单。因为我们在parent pom文件里面用了<dependencyManagement>这个标签,我们只需要在新的pom里面复制<dependencyManagement>标签下的所有依赖到新的pom里面就可以了。

展开阅读全文

没有更多推荐了,返回首页