2

(一)Maven介绍

maven:是一个跨平台的项目管理工具,主要服务于Java平台的项目构建、依赖管理和项目信息管理
项目构建:通过插件帮你完成项目的清理、编译、测试、打包、部署。
依赖管理:通过坐标从maven仓库导入java类库(jar文件)
项目信息管理:项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等。

(二)Maven的安装和配置

下载页面:http://maven.apache.org/downl...
下载解压即可。在安装 Maven 之前,需确保安装JDK,可通过 java -version 命令查看
环境变量配置:
1) M2_HOME = maven的bin目录所在路径(不包含/bin)
2) Path 添加 %M2_HOME%/bin
maven升级:下载新版本Maven解压,修改M2_HOME路径即可
Linux 可通过符号链接简化 Maven升级

Maven目录分析

目录 说明
bin mvn运行脚本,配置java命令, mvn / mvnDebug
boot 只包含一个plexus-classworlds jar包,Maven类加载器框架
conf settings.xml全局maven配置文件,【推荐】复制该文件至 ~/.m2/目录下(~表示用户目录)
lib Maven运行需要的Java类库,Maven jar包以及第三方依赖jar包

mvn help:system
该命令会打印出所有的Java系统属性和环境变量

~/.m2目录分析

~(用户目录) Winodw:C:Users用户名 或者 C:Document and Settings用户名
Linux: cd 回车

在用户目录下可以发现.m2文件夹。
默认情况下,.m2文件下只放置了Maven本地仓库repository,不过大多数Maven用户需要复制 settings.xml 到此目录下作为用户配置文件。

Maven插件

IDE使用Maven需要安装Maven插件,如Eclipse的m2eclipse插件,NetBeans的Maven插件

Maven安装最佳实践

设置环境变量MAVEN_OPTS 通常设置MAVEN_OPTS的值为-Xms128m -Xmx512m,因为mvn命令实际执行java命令,项目较大时,使用Maven生成项目站点需要占用大量内存,如果没有该配置,容易得到java.lang.OutOfMemeryException
配置用户settings.xml 用户可选择配置conf中的全局配置或.m2目录下的用户配置,推荐使用用户配置,避免影响系统中其他用户,并且配置用户配置便于maven升级,因为Maven升级后,conf下的全局配置需要重新设置,而.m2下的用户配置不变。
不使用IDE内嵌Maven IDE集成的Maven,通常版本比较新,不稳定,其次要确保IDE中配置Maven与命令行Maven保持一致,避免版本不同造成构建行为的不一致。

(三)Maven入门

编写POM(Project Object Model)

clipboard.png

XML头 指定xml文档版本和编码方式
project 声明POM相关的命名空间和xsd元素
modelVersion 指定当前POM模型的版本
groupId 定义了项目属于哪个组(Maven项目隶属的实际项目),类似包名,域名反写+实际项目名,如:org.sonatype.nexus
artifactId 定义了项目在组中唯一的ID,可理解为项目名(实际项目中的一个maven项目(模块))推荐格式:实际项目名-模块名 如:nexus-indexer
version 指定了项目当前的版本
name 声明了一个对用户更为友好的项目名称,非必须,推荐声明,方便信息交流。

编写主代码

默认情况下Maven项目目录

|-src
  |-main
    |-java                   Java代码
      |-groupId.artifactId    定义包名为项目组.项目名,所有java类都在该包下创建
    |-resources              资源目录
  |-test                     测试目录
    |-java                   测试Java代码
      |-groupId.artifactId

项目编译命令 mvn clean compile
此命令主要执行了三个插件以及插件目标

clean:clean 删除target目录,默认情况下,Maven构建的所有输出都在target/目录中
resource:resources 主项目资源处理
compile:compile 将项目主代码编译至target/classes目录

编写测试代码

添加POM依赖,导入Junit jar包

<dependencies>    <!-- 可包含多个dependency声明项目的依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.7</version>
        <scope>test</scope>    <!-- 声明依赖范围,只对测试有效,在主代码中使用junit 会编译错误 -->
    </dependency>
</dependencies>

一个典型测试单元需具备三步骤:1 准备测试类及数据 2 执行要测试的方法 3 检查结果
在测试包中创建测试类(类名不要为Test),
编写测试方法(void无返回值 @Test注释),

项目测试命令:mvn clean test
此命令主要执行了五个插件以及插件目标

clean:clean 项目清理
resource:resources 项目主资源处理
compile:compile 主代码编译
resource:testResource 测试资源处理
compile:testCompile 测试代码编译

声明项目编译JDK版本

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compile-plugin</artifactId>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
</plugins>

打包和运行

mvn clean package
该命令会在编译、测试后执行 jar:jar
jar:jar任务负责打包,jar插件的jar项目在tartget目录将主代码打包成jar包,根据artifact-version.jar(项目名-版本号.jar)命名,也可通过<build>标签中的<finalName>子标签来自定义

mvn clean install 其他Maven项目依赖此项目时,需将jar包安装到Maven本地仓库中
该命令会在编译、测试、打包后执行install:install
install:install任务将jar包安装到本地仓库repository下,规则为groupIdartifactIdversionartifactId-version.jar

可执行jar包需要设置 META-INF/MANIFEST.MF 文件中编辑 Main-Class 一行。
也可通过配置maven-shade-plugin 或 maven-jar-plugin 等插件实现

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <classesDirectory>target/classes/</classesDirectory>
        <archive>
            <manifest>
                <!-- 主函数的入口 -->
                <mainClass>cn.roylion.snake.Snake</mainClass>
                <!-- 打包时 MANIFEST.MF文件不记录的时间戳版本 -->
                <useUniqueVersions>false</useUniqueVersions>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
            </manifest>
            <manifestEntries>
                <Class-Path>.</Class-Path>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Archetype项目骨架

在项目的根目录中放置pom.xml,在src/main/java中放置项目的主代码,在src/test/java中放置项目的测试代码。
我们称这些基本的目录结构和pom.xml文件内容为项目骨架,Archetype可以帮助我们快速勾勒出项目骨架。

运行插件maven-archetype-plugin生成项目骨架:
格式:mvn groupId:artifactId:version:goal
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate
简化格式:mvn archetype:generate (maven2中是不安全,会自动下载最新的版本,可能得到不稳定的SNAPSHOT版本)

执行插件之后会列出很多可用的Archetype列表,每一个Archetype前面都会对应有一个编号,同时命令行会提示一个默认的编号,其对应的Archetype为maven-archetype-quickstart,
直接回车以选择该Archetype,或输入指定编号回车
紧接着会Maven会提示输入要创建项目的groupId、artifactId、version以及包名package,确认后即可生成一个maven项目。

(四)坐标和依赖

maven坐标

Maven定义了一组规则,世界上任何一个构件都可以使用Maven坐标唯一标识,
Maven坐标的元素包括 groupId、artifactId、version、packaging、classifier
中央仓库:Maven通过坐标从中央仓库寻找相应的构件供我们使用。

groupId 定义当前Maven项目隶属的实际项目。因为Maven的模块概念,Maven项目和实际项目不一定是一对一关系,一个实际项目往往会被划分成很多模块。如果groupId只定义到组织级别,artifactId只能定义Maven项目(模块),则实际项目这一层将难以定义,最后,groupId的表现形式类似包名,域名反写+实际项目名,如:org.sonatype.nexus
artifactId 定义实际项目中的一个Maven项目(模块),推荐使用实际项目名作为artifactId的前缀,如nexus-indexer
version 定义Maven项目当前所处的版本。Maven定义了一套完整的版本规范,以及快照(SNAPSHOT)概念。
packaging 定义Maven项目的打包方式,未定义时,默认为jar
classifier 帮助定义构建输出的一些附属构件。通过使用插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-resource.jar这样一些附属构件,其中包含java文档和源代码,此时,javadoc和resource就是这两个附属构件的classifier。这样,附属构件就拥有自己唯一的坐标,

上述5个元素中,groupId、artifactId、version是必须定义的,packaging是可选的(默认为jar),而classifier是不能直接定义的。
项目构件的文件名规则:artifactId-version[-classifier].packaging
maven仓库的布局也是基于maven坐标的

maven依赖管理配置

<project>
...
    <dependencies>
        <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <type>...</type>
            <scope>...</scope>
            <optional>...<optional>
            <exclustion>
                ...
            </exclustion>
        </dependency>
        ...
    </dependencies>
...
</project>

根元素project下的dependencies可以包含多个dependency元素,以声明一个或多个项目依赖

groupId、artifactId、version 依赖的基本坐标,Maven根据坐标才能找到需要的依赖
type 依赖的类型,对应于项目坐标定义的packaing,默认为jar
scope 依赖的范围
optional 标记依赖是否可选
exclusions 排除传递性依赖

依赖范围

首先,Maven在编译项目主代码时需要使用一套classpath(编译classpath);
其次,Maven在编译和执行测试的时候会使用另外一套classpath(测试classpath);
最后,实际运行Maven项目的时候,又会使用另外一套classpath(运行classpath)。
依赖范围:maven需要使用三套classpath,控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围。

类型 有效范围 说明
compile 编译、测试、运行 编译依赖范围,如果没有指定,默认使用该范围,如 spring-core
test 测试 测试依赖范围,如 junit
provided 编译、测试 已提供依赖范围,如 servlet-api
runtime 测试、运行 运行时依赖范围,如JDBC驱动实现
system 编译、测试 系统依赖范围,依赖关系与provided一致,但是,使用system范围依赖,必须通过systemPath元素显式指定依赖文件的路径。由于此类依赖不通过Maven仓库,可能会造成构建的不可移植,需谨慎使用,systemPath可引用环境变量。
<dependency>
  <groupId>javax.sql</groupId>
   <artifactId>jdbc-stdext</artifactId>
   <version>2.0</version>
   <scope></scope>
   <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
import 导入依赖范围,该依赖范围不会对三种classpath产生实际的影响。

传递性依赖

假设:A->B->C
当项目A依赖于B包,而B包又依赖于C包,故需要引入C包。
使用Maven只需要引入B包即可,C包由Maven通过传递性依赖引入。
Maven通过传递性依赖机制,解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

传递性依赖与依赖范围

假设:A->B->C
A依赖B -> 第一直接依赖 依赖范围
B依赖C -> 第二直接依赖 依赖范围
A依赖C -> 传递性依赖 依赖范围 由第一直接依赖和第二直接依赖决定,如下图
clipboard.png
总结:
第二直接依赖范围是compile,传递性依赖范围与第一直接依赖范围一致;
第二直接依赖范围是test,依赖不得以传递;
第二直接依赖范围是provided,只第一直接依赖范围也为provided时,传递性范围同样为provided;
第二直接依赖范围是runtime,除第一直接依赖范围为compile时为runtime,传递性依赖范围与第一直接依赖范围一致。

依赖调解

假设:A->B->C-X(1.0)    A->D->X(2.0)
传递性依赖机制引入不通版本的X时,发生依赖重复,Maven遵循的第一原则:路径最近者优先。
该例中X(1.0)路径长度为3,X(2.0)路径长度为2。因此X(2.0)会被解析使用
当路径长度相同时,Maven(2.0.9之后)定义第二原则:第一声明者优先。

可选依赖(optional 标记依赖是否可选)

假设:A->B , B->X(可选) , B->Y(可选)
如果所有这个三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖,但是,由于X,Y是可选依赖,依赖将不得以传递。

可选依赖的使用场景:项目B具有两个特性,特性一依赖于X,特性二依赖于Y,并且这两个特性是互斥的,用户无法同时使用。
比如B是一个持久层隔离工具包,它支持多个数据库,包括MySql(假设驱动依赖X)、Oracle(假设驱动依赖Y),
在构建项目B时,需要引入这两种数据库的驱动程序,但在引入项目B时,只会依赖一种数据库,
此时项目B构建时需声明依赖X,Y可选(<scope>true</scope>),
当项目A依赖项目B时,X,Y项目依赖不会被传递,同时项目A根据实际需求,显式声明X或Y依赖(如项目使用MySql数据库时,项目A依赖项目B,同时显式声明X依赖)

说明:在理想状态下,是不应该使用可选依赖。可选依赖是基于一个项目实现多个特性,违背了面向对象设计中的单一职责性原则。

排除依赖

假设:A->B-X(1.0)
传递性依赖会隐式地引入很多依赖,如依赖X(1.0),这极大地简化项目依赖管理,但这种特性也会带来一些问题。
例如,我需要依赖X(2.0)时,可以使用排除依赖排除X(1.0),同时显式声明X(2.0)依赖。
代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或多个exclusion子元素,因此可以排除一个或多个传递性依赖。
注意:声明exclusion时只需要groupId和artifactId,不需要version。因为Maven解析后的依赖中,不会存在groupId和artifactId相同,而version不同的两个依赖(依赖调解特性)。

归类依赖

引入同一项目的不同模块时,这些依赖的版本都是相同的,如Spring Framework
这里简单用到了Maven属性,首先使用properties元素声明Maven属性(定义版本),并在dependency声明中引用这个版本(${})。

<project>
    ...
    <properties>
        <springframework.version>2.5.6</springframework.version>
    </properties>
    
    <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>
        ...
    </dependencies>
    ...
</project>

优化依赖

了解Maven项目的依赖,去除多余的依赖,显式地声明某些必要的依赖

Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
mvn dependency:list 查看已解析依赖列表及其依赖范围

在此基础上,还能进一步了解已解析依赖的信息,将直接在当前项目POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经解析后,就会构成一个依赖树。通过这个依赖树就能清楚得看到某个依赖是通过哪条传递路径引入的。
mvn dependency:tree 查看当前项目的依赖树

mvn dependency:analyze 帮助分析当前项目的依赖
通过分析依赖树,可以得到两个重要结果:
1 Used undeclared dependencies:意指项目中使用到的,但是没有显式声明的依赖。(存在隐患)
假设:A->B->C
如果项目A->C时,并且项目A并没有显示声明C依赖时,而使用B的传递依赖C时,当升级直接依赖B,传递依赖C也可能发送变化,并且这种变化不易发现,导致当前项目出错。如接口改变,导致编译失败。因此,显式声明任何项目中直接用到的依赖。
2 Unused declared dependencies:意指项目中未使用的,但显式声明的依赖。
对于这一类依赖,不要简单的直接删除其声明,而是应该仔细分析,因为dependency:analyze工具只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和允许时需要的依赖它就发现不了。


roylion
204 声望25 粉丝

读书破万卷