Maven(二)核心知识点

Maven有一些核心的知识点需要了解,比如坐标、仓库、插件、生命周期等概念,这里将依次解释。

 

坐标


Maven以构件来组成基本的控制单元,而定义这个构件的标示,Maven给定义为“坐标”。坐标是Maven最基本的概念,它就像每个构件的身份证号码,有了它我们就可以在数以千万计的构件中定位任何一个我们感兴趣的构件。

“坐标”这个词听起来很摸不着头脑,其实很简单,上一博客里面已经用到了,即下面这几个xml元素即组成了一个坐标

<groupId>com.company.maven01</groupId>
<artifactId>mavenapp1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

具体的每个元素代表什么意思请参考上一博客。

在用Maven开发一个项目时,是被强制要求定义改项目的坐标的。因为只有这样你的项目才会被其他项目引用,其他Maven项目才能使用你的项目生成出来的构件。比如上一博客中例子的dependency中引用了junit,可以看到里面实际上描述了所要引用junit的坐标。

下面举例说明几个常用的坐标,这些开发包可以直接加入到你的pom.xml的依赖中:

junit4.x

 <dependency>  
    <groupId>junit</groupId>  
    <artifactId>junit</artifactId>  
    <version>4.8.2</version>  
    <scope>test</scope>  
</dependency> 

groovy

<dependency>  
    <groupId>org.codehaus.groovy</groupId>  
    <artifactId>groovy-all</artifactId>  
    <version>2.2.2</version>  
</dependency>

hibernate

 <dependency>  
    <groupId>org.hibernate</groupId>  
    <artifactId>hibernate-core</artifactId>  
    <version>3.6.10.Final</version>  
</dependency> 

 

仓库


当你的Maven在本地安装好后实际上已经生成了一个本地仓库,可以在下列地址中找到

...\Users\username\.m2\repository

打开后发现里面全是各种本地仓库中的各种引用开发包,这里也能发现上一博客中已经引用的Junit,可以发现已经下载到本地仓库里面了

 

有本地仓库,那么是不是还有个远程仓库的概念呢?

答案是肯定的,这个远程仓库是个大仓库,叫做中央仓库,地址为

https://repo.maven.apache.org/maven2

 

有时候因为各种原因,可能你访问不了默认的中央仓库的地址,那么可以通过配置一个镜像中央仓库来解决

修改maven安装目录->conf文件夹->settings.xml这个文件,找到mirror这个元素,可以看到上面有一些注释:

 <!-- mirrors
   | This is a list of mirrors to be used in downloading artifacts from remote repositories.
   |
   | It works like this: a POM may declare a repository to use in resolving certain artifacts.
   | However, this repository may have problems with heavy traffic at times, so people have mirrored
   | it to several places.
   |
   | That repository definition will have a unique id, so we can create a mirror reference for that
   | repository, to be used as an alternate download site. The mirror site will be the preferred
   | server for that repository.
   |-->

大概的意思就是通过配置这个mirror元素可以替换掉默认的中央仓库,但是一旦配置了镜像中央仓库,原默认中央仓库则无法访问,除非将mirror元素归位

下面给出一个配置mirror的例子:

<mirror>
    <id>maven.net.cn</id>
    <mirrorOf>central</mirrorOf>
    <name>central mirror in china</name>
    <url>http://maven.net.cn/content/groups/public</url>
</mirror>

 

发布到本地仓库


 

现在你建立了一个项目 ,同时你有另一个项目B需要引用这个项目A,只要将项目A通过Maven发布到本地仓库即可

进入项目根目录,首先运行下mvn clean,这个命令将之前生成的target给一并清理了

D:\Work\moudle\maven\mavenapp1> ls target


    目录: D:\Work\moudle\maven\mavenapp1\target


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          2015/8/4     15:05            classes
d----          2015/8/4     16:04            maven-archiver
d----          2015/8/4     15:05            maven-status
d----          2015/8/4     15:13            surefire-reports
d----          2015/8/4     15:13            test-classes
-a---          2015/8/4     16:04       2380 mavenapp1-1.0-SNAPSHOT.jar


D:\Work\moudle\maven\mavenapp1> mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building mavenapp1 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mavenapp1 ---
[INFO] Deleting D:\Work\moudle\maven\mavenapp1\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.230 s
[INFO] Finished at: 2015-08-05T12:25:53+08:00
[INFO] Final Memory: 8M/184M
[INFO] ------------------------------------------------------------------------
D:\Work\moudle\maven\mavenapp1> ls


    目录: D:\Work\moudle\maven\mavenapp1


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          2015/8/4     14:46            src
-a---          2015/8/4     14:46        784 pom.xml

发现target文件夹没了,随后再运行mvn install,即把改项目打包并发布到本地仓库:

D:\Work\moudle\maven\mavenapp1> mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building mavenapp1 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mavenapp1 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Work\moudle\maven\mavenapp1\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mavenapp1 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\Work\moudle\maven\mavenapp1\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mavenapp1 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Work\moudle\maven\mavenapp1\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mavenapp1 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\Work\moudle\maven\mavenapp1\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mavenapp1 ---
[INFO] Surefire report directory: D:\Work\moudle\maven\mavenapp1\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.company.maven01.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mavenapp1 ---
[INFO] Building jar: D:\Work\moudle\maven\mavenapp1\target\mavenapp1-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ mavenapp1 ---
[INFO] Installing D:\Work\moudle\maven\mavenapp1\target\mavenapp1-1.0-SNAPSHOT.jar to D:\Users\haitao\.m
\company\maven01\mavenapp1\1.0-SNAPSHOT\mavenapp1-1.0-SNAPSHOT.jar
[INFO] Installing D:\Work\moudle\maven\mavenapp1\pom.xml to D:\Users\haitao\.m2\repository\com\company\m
\1.0-SNAPSHOT\mavenapp1-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.977 s
[INFO] Finished at: 2015-08-05T12:27:50+08:00
[INFO] Final Memory: 21M/198M
[INFO] ------------------------------------------------------------------------

发现这个命令实际上是把编译、测试、打包等都给一起顺序跑了下,这个时候我们在看看本地仓库里面多了什么:

发现本地仓库以测试项目的包名建立了文件夹树,同时生成了jar文件,这个即为打包以后的jar。

 

那么下一步项目B该怎么引用项目A呢?是的,这里肯定又要用到坐标的概念了,即在项目B的描述文件pom.xml中添加项目A的依赖,并在dependency元素中写上项目A的坐标:

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.company.maven01</groupId>
      <artifactId>mavenapp1</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
    </dependency>
  </dependencies>

这样子再mvn compile项目B就没有问题了

 

仓库和坐标的关系


首先看看mavenapp1这个测试项目在本地仓库里面的目录结构

可以发现com\company\maven01恰好对应坐标中的groupId,随后的maven01正好对应artifactId,最后jar所处的1.0-SNAPSHOT正好对应了version。所以说有了坐标,就知道在什么位置存储构件的内容,中央仓库也是一个样,上述例子中引用的junit的可以把它的坐标描述为:junit:junit:3.8.1,在中央仓库地址中打开对应的目录果然看到了Junit的jar包

有了正确的坐标,Maven才能够在正确的位置找到依赖文件并使用,上述例子中值为test的scope是用来控制该依赖只在测试时可用,与坐标无关。

正因为坐标是Maven核心的核心,因此规划正确的坐标至关重要,如果你使用了模糊不清的坐标,那么你的用户就很难找到你的构件,或者即使找到了,也容易写错。错误的使用坐标,还会造成冲突,如果你也使用junit这样的groupId,那就悲剧了

关于如何规划坐标内容的最佳实践,可以参考下面的经典博客:

http://www.infoq.com/cn/news/2010/12/xxb-maven-1

 

生命周期和插件


Maven定义了三套生命周期:clean、default、site,每个生命周期都包含了一些阶段(phase)。三套生命周期相互独立,但各个生命 周期中的phase却是有顺序的,且后面的phase依赖于前面的phase。执行某个phase时,其前面的phase会依顺序执行,但不会触发另外两 套生命周期中的任何phase。

  • clean,做些清理的工作
  • default,最核心的周期,做初始化和构建的工作,里面分的阶段很多,主要是compllie,test, package, install等
  • site,生成站点的周期,包括生成文档和发布等

Maven的生命周期是抽象的,实际需要插件来完成任务,这一过程是通过将插件的目标(goal)绑定到生命周期的具体阶段(phase)来完成的。这里就像设计模式中的模板模式,父类定义好了方法模板并规定对了执行顺序,而子类定义了每个模板方法具体要做的事情。这里的父类相当于maven,而子类就像是一个个的插件。

比如compile这个阶段,对应的是mvn complie这个命令,但是实际上是maven-compiler-plugin这个插件在起作用

每个插件在执行的时候会有多个任务,每个任务被称作这个插件的一个目标(goal),这个目标都是可以对应上maven生命周期中某个阶段(phase)的,这里重新看下上述mvn install命令(其中有个插件就是叫做maven-install-plugin)的执行结果

D:\Work\moudle\maven\mavenapp1> mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building mavenapp1 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mavenapp1 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Work\moudle\maven\mavenapp1\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mavenapp1 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\Work\moudle\maven\mavenapp1\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mavenapp1 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Work\moudle\maven\mavenapp1\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mavenapp1 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\Work\moudle\maven\mavenapp1\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mavenapp1 ---
[INFO] Surefire report directory: D:\Work\moudle\maven\mavenapp1\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.company.maven01.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mavenapp1 ---
[INFO] Building jar: D:\Work\moudle\maven\mavenapp1\target\mavenapp1-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ mavenapp1 ---
[INFO] Installing D:\Work\moudle\maven\mavenapp1\target\mavenapp1-1.0-SNAPSHOT.jar to D:\Users\haitao\.m
\company\maven01\mavenapp1\1.0-SNAPSHOT\mavenapp1-1.0-SNAPSHOT.jar
[INFO] Installing D:\Work\moudle\maven\mavenapp1\pom.xml to D:\Users\haitao\.m2\repository\com\company\m
\1.0-SNAPSHOT\mavenapp1-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.977 s
[INFO] Finished at: 2015-08-05T12:27:50+08:00
[INFO] Final Memory: 21M/198M
[INFO] ------------------------------------------------------------------------

通过红色的部分可以看到mvn install这个命令是由很多个插件依次按照一定的顺序执行的结果,同时可以发现,红色的部分都是按照一定的格式:

{插件名}:{版本}:{任务}

可以看到 maven-resources-plugin这个插件在这里实际上执行了两种任务,即resources testResources

而这里的resource和testResources都对应了maven的default生命周期中两个phase。

更多的关于插件的信息,可以查看maven的官方网站,包括每个插件都可以执行什么样的任务都有详细的介绍。

 

显然的,默认情况下maven的每个阶段(phase)都已经绑定了一个默认的插件的任务目标,比如:

左边栏是生命周期的phase,右边是所对应的默认的插件的任务目标(goal),但实际上phase和goal并不是一对一的关系,而是一对多的关系,比如package这个阶段,项目可能并不想打包成jar格式而是别的格式,那么这里的package对应的goal就要变了,这里都是可以通过pom.xml描述文件来修改和定制

 

自定义插件绑定到maven生命周期


那么如果你想自己开发个插件,并且想让maven默认生命周期的某个阶段使用你自己的写的插件的某个任务目标,改如何做呢?

 

这一过程是通过将插件的目标(goal)绑定到生命周期的具体阶段(phase)来完成的。如:将maven-compiler-plugin插件的compile目标绑定到default生命周期的compile阶段,完成项目的源代码编译:

通过在pom.xml的build元素即可完成自定义插件(或者别的已有插件)对生命周期某个阶段的绑定,用户可以根据需要将任何插件目标绑定到任何生命周期的阶段,如:将maven-source-plugin的jar-no-fork目标绑定到default生命周期的package阶段,这样,以后在执行mvn package命令打包项目时,在package阶段之后会执行源代码打包,生成如:ehcache-core-2.5.0-sources.jar形式的源码包。

 

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.2.1</version>
            <executions>
                <execution>
                    <id>attach-source</id>
                    <phase>package</phase><!-- 要绑定到的生命周期的阶段 -->
                    <goals>
                        <goal>jar-no-fork</goal><!-- 要绑定的插件的目标 -->
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
    ……
</build>

 

posted @ 2015-08-05 17:37  soaringEveryday  阅读(1187)  评论(1编辑  收藏  举报