1. Maven是什么

在使用之前,我们需要了解maven是什么。在我理解,maven是一个功能非常强大的java自动化工具,包括了解决包依赖,代码生成、编译、测试、打包、部署等等系列功能。其中包依赖问题的解决是我对其非常欣赏的一点,这有点像nodejs的npm。此外maven还支持第三方编写的plugin,基本上能做到无限扩展。

maven主页:http://maven.apache.org/

guide页:http://maven.apache.org/guides/getting-started/index.html

2. Maven的安装

Maven当前版本有2和3两个稳定版本,我现在使用的是2。安装很简单 sudo port -v install maven2。

安装完成后:

  • maven2的可执行文件在 /usr/bin/mvn
  • maven2的安装目录在 /usr/share/maven
  • maven2的资源目录以及配置文件目录在当前用户文件夹下:~/.m2/

我比较有强迫症,所以我手动修改了maven2的目录地址:

  • 资源目录:/Users/jonathan/prog/Java/maven2/repository
  • 配置文件:/Users/jonathan/prog/Java/maven2/settings.xml

maven在执行过程中需要的第三方依赖包,都会下载到上面列出的资源目录下。可以手动删除里面的内容,在下次执行的时候会重新下载。

3. Maven的系列概念

在开始使用maven之前,我们需要了解一系列的maven概念,否则你会不知道该怎么使用maven。

3.1 认识pom.xml

maven的功能完全由pom.xml这个文件的内容控制,我们只需要编写这个xml文件就好了。然后在pom.xml同级的文件夹中,执行mvn compile、package、install等命令行命令就可以了。

3.2 文件夹位置的约定

如果要使用maven来维护你的项目的话,你必须要遵守maven的文件夹结构约定(参见这个链接:http://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html,和 http://maven.apache.org/guides/getting-started/index.html#How_do_I_make_my_first_Maven_project)。如果不这样构造文件夹结构的话,maven是没有办法找到你的代码文件来进行编译的,同时也没办法找到你的测试代码文件来运行测试用例。

除了app的源代码文件和测试用例的源代码文件,resources文件夹我并没有放在 src/main/resources 这个位置,config文件夹我也没有放在 src/main/config 这个位置,因为这两块我希望自己管理,而不是直接打包到jar文件内。下面我会说到的。

范例:

my-app
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- com
    |           `-- mycompany
    |               `-- app
    |                   `-- App.java
    `-- test
        `-- java
            `-- com
                `-- mycompany
                    `-- app
                        `-- AppTest.java

3.3 Maven的build lifecycle概念以及phase概念

maven对于java项目的整个生命周期已经有一个非常清晰的定义,也就是lifecycle这个概念。当中的每一个步骤,则被称为phase。这两个概念很重要,因为在下面的plugin使用中,使用者经常需要定义该plugin需要启动处理的phase。

具体的请参见:http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

4. Maven的使用

maven的使用就是pom.xml的编写,开发者除此之外不需要做其他事情。接下来我们就一个个介绍如何编写pom.xml来实现我们项目开发中需要的系列功能。

4.1 包依赖管理

刚才也说了,maven最重要的功能块就是解决项目的包依赖管理,你只需要在pom.xml中描述好你的项目所需要的依赖包,maven自然会给你下载好,并放到maven的repository里。当然,你在运行你的项目的时候,千万记得要将maven的repository加入到你项目的classpath里去。或者你也可以制作一个standalone的jar包,将所有的依赖代码都注入进去。

下面给出xml例子:

[codesyntax lang="xml"]

<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty</artifactId>
            <version>3.6.3.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    ...
</project>

[/codesyntax]

4.2 输出文件夹的清理

如果你不指定maven clean这个命令的话,maven默认是不会清理target这个输出文件夹的,新生成的文件会覆盖老的文件,但是废弃的旧文件不会被删除。当然,这不是我们所期望的,我们希望的是无论我们使用maven的什么命令,旧的target都应该被删除。这里我们可以使用maven的一个plugin,来为我们完成这个工作:

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-clean-plugin</artifactId>
            <version>2.5</version>
            <executions>
                <execution>
                    <id>auto-clean</id>
                    <phase>initialize</phase>
                    <goals>
                        <goal>clean</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.3 源代码打包

除了classes文件打包的jar文件之外,我们有的时候也需要附上source code源代码,这个时候就需要这个插件进行源代码jar的打包了。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.2.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.4 javaDoc打包

同样的,我们有的时候也需要注释的java doc的jar包。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.9</version>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.5 All In One 打包工具

有的时候我们需要将所有的依赖包与我们自己的代码打成一个jar包。比如说我自己做的java框架,我希望项目中使用到的依赖jar包只有它个,那么我就需要把所有的代码打到这个jar包里。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-jar-with-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.6 Shade Plugin

在我们使用4.5的工具将所有jar包打成一个包的时候,你会发现,这个jar包的文件名是之前你定义好的文件名,然后加上jar-with-dependencies这个后缀,这明显不是我们想要的。这里我们就可以使用shade插件,将刚才的jar文件替换成我们想要的jar文件名。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.0</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <finalName>${finalName}</finalName>
            </configuration>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.7 打包可执行的jar包

作为服务器端程序,一般我们需要一个能在命令行下直接启动的jar包。这里我们也可以使用插件进行创建。这里需要解释下,因为我项目中使用都是all in one的jar包,所以classpath可以使用customClasspathLayout这个标签,定义为单一的一个jar文件。
[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathLayoutType>custom</classpathLayoutType>
                        <customClasspathLayout>${finalJarName}</customClasspathLayout>
                        <mainClass>${mainClass}</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.8 拷贝资源文件

项目中我们肯定也会有很多资源文件,比如说配置文件,比如说日志文件,等。这里我们可以使用插件,将资源文件拷贝到我们指定的文件夹。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.6</version>
            <configuration>
                <outputDirectory>${project.build.directory}</outputDirectory>
                <includeEmptyDirs>true</includeEmptyDirs>
                <resources>
                    <resource>
                        <directory>${resourceDir}</directory>
                    </resource>
                </resources>
            </configuration>
            <executions>
                <execution>
                    <id>copy-configs-and-logs</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.9 使用ant命令打包项目

jar包制作完成,资源文件拷贝完成,之后我们还需要做的一步就是将所有的东西打包成一个tar.gz压缩包,方便拷贝、发布。使用maven原生的插件不太好做这个事情,这里我们就可以使用maven提供的功能,在maven中跑ant命令。

这里给出的例子只有一个打包的过程,其实还有很多很多命令可供选用,大家可以参考这里:https://ant.apache.org/manual/tasksoverview.html

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <configuration>
                        <target>
                            <tar destfile="${project.build.directory}/${finalName}.tar.gz" compression="gzip">
                                <tarfileset dir="${project.build.directory}">
                                    <include name="${finalJarName}"/>
                                    <include name="configs/**" />
                                    <include name="logs/**"/>
                                </tarfileset>
                            </tar>
                        </target>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]

4.10 在maven中执行bash命令

有的时候我们需要执行一些外部命令,这个时候就有需求要执行bash脚本。

[codesyntax lang="xml"]

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <id>loop-protocol-buffers</id>
                    <phase>process-resources</phase>
                    <configuration>
                        <target>
                            <exec executable="bash">
                                <arg value="-c"/>
                                <arg value="/opt/local/bin/protoc --proto_path=${protobufDir} --java_out=${project.build.sourceDirectory}"/>
                            </exec>
                        </target>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
</build>
...
</project>

[/codesyntax]