Maven机制

Maven坐标

在平面几何中,用坐标(x, y)唯一确定一个点。在maven的世界里,用maven坐标来唯一确定一个构件。

maven坐标元素包括groupId, artifactId, version, packaging, classifier。

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
    <packaging>pom</packaging>
    ......
</project>

groupId:对应公司的一个实际项目
artifactId:实际项目中的子工程
version:版本号
packaging:打包方式,默认为jar,还有pom, maven-plugin, ejb, war, ear, rar, par等。
classifier:比如dom4j-1.6.1-sources.jar,dom4j-1.6.1-javadoc.jar这里的sources、javadoc就是一个classifier。再比如,一个包用不同的JDK版本编译,可以加上classifier,在dependency配置依赖时,可以指定具体的classifier的包(例子)。classifier是一个可选项,也不能在定义构件时指定,而是通过其它插件额外生成的附属构件。之所以被作为坐标的一部分,我想就是因为dependency时候有时需要指定。

构件的文件名是与坐标相对应的,一般规则为 artifactId-version[-classifier].packaging,[-classifier是可选的]。

最佳实践

个人认为最佳实践:groupId是artifactId的前缀,artifactId是package的前缀。比如,groupId是com.example.demo,artifactId是com.example.demo.payment,class是com.example.demo.payment.service.PaymentService,这样的话,看到class能知道在哪个包里,看到jar能知道属于哪个groupId,方便追根溯源。

Maven依赖

<project>
    ...
    <dependencies>
        <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <type>...</type>
            <classifier>...</classifier>
            <scope>...</scope>
            <systemPath>...</systemPath>
            <exclusions>
                <exclusion>
                    <groupId>...</groupId>
                    <artifactId>...</artifactId>
                </exclusion>
                ...
            </exclusions>
        </dependency>
    </dependencies>    ...
</project>

groupId, artifactId, version, type, classifier是依赖的坐标。这里的type和坐标中的packaging是对应的,可以不声明,默认为jar。

scope,表示依赖的范围。mvn在编译、测试、运行时使用不同的classpath,在不同阶段需要的依赖也不相同。scope有如下取值:

  1. compile:编译依赖范围。如果没有指定scope,默认为compile。使用这个依赖范围,对mvn编译、测试、运行时三种classpath都有效。

  2. test:测试依赖范围。使用这个依赖范围,只对测试classpath有效,在编译主代码或者运行时无法使用此依赖。

  3. provided:已提供依赖范围。使用这个依赖范围,对于编译和测试classpath有效,在运行时无效。简单来说,就是声明一下,运行时环境已经存在这个依赖包了,你打运行时包的时候不需要把这个依赖包带上。典型的例子是servlet-api,编译和测试的时候需要该依赖,但在运行时,容器已经提供。

  4. runtime:运行时依赖范围。这个依赖对于测试和运行classpath有效,在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

  5. system:系统依赖范围。这个依赖对classpath的影响与provided一致,不同的是,这里的依赖必须通过systemPath显式指定文件路径,依赖不是通过maven仓库管理的,可移植性不好,谨慎使用。
    例:

<dependency>
    <groupId>javax.sql</groupId>
    <artifactId>jdbc-stdext</artifactId>
    <version>2.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>

  1. import:导入依赖范围。这种依赖不会对classpath产生实际的影响。该范围只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将POM中的dependencyManagement的配置导入并合并到当前POM的dependencyManagement元素中。

  2. optional:标记依赖是可选的。可选依赖不会传递,实际项目中建议不要使用。通常只有一个项目实现了多个特性,但打包时只会选择部分特性发布时,才会把依赖设置为可选依赖。

  3. exclusions:用来排除传递性依赖。

传递性依赖及其范围

假设A依赖B,B依赖C,那么A也依赖C,这就是依赖的传递性。依赖有范围的控制,那么传递依赖的范围是怎样的呢?

我们把A对B的依赖叫第一直接依赖,B对C的依赖叫第二直接依赖,A对C是传递性依赖。在下面的表格中,左侧表示第一直接依赖范围,上侧表示第二直接依赖范围,中间交叉单元格得到传递性依赖范围。

第一依赖\第二依赖compiletestprovidedruntime
compilecompileruntime
testtesttest
prividedprovidedprovidedprovided
runtimeruntimeruntime

依赖调解原则

如果A有两条依赖路径,依赖了不同版本的X,那么谁会生效呢?
A->B->C->X(1.0) A->D->X(2.0)

依赖调解有两条规则:

  • 路径最短的优先。上例中X的2.0版本的依赖路径比1.0版本短,所以2.0版本生效。
  • 第一声明者优先。如果路径长度相同,则根据在POM依赖声明的顺序决定,先声明的生效。

最佳实践

排除依赖

依赖传递简化了项目管理,但隐式的依赖调解也可能带来一些问题。如果A->B->C,但是A想要自己指定依赖的C的版本号,并不想从B传递依赖,就可以使用排除依赖。排除依赖时只需要groupId和artifactId,不需要version。

<dependency>
    <groupId>org.test</groupId>
    <artifactId>projectB</artifactId>
    <version>1.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.test</groupId>
            <artifactId>projectC<artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.test</groupId>
    <artifactId>projectC</artifactId>
    <version>1.2.3</version>
</dependency>

归类依赖

假如项目中存在对多个spring包的依赖,通常都是对同一个版本的依赖,那么可以将这些依赖归类,定义版本号常量springframework.version,这样便于管理,后续升级依赖的版本时只要修改常量即可。

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

分析优化依赖

可以定期审视项目依赖,不断优化。
maven提供了dependency插件,可以分析项目依赖。

mvn dependency:list 已解析依赖。包含直接依赖和间接依赖,解析后得到的所有生效依赖。

mvn dependency:tree 依赖树。可以看到依赖引入路径,依赖层级关系。

mvn dependency:analyze 分析依赖。分析的结果主要有两种:代码中使用了但没有直接声明的依赖,和声明但未使用的依赖。使用但没有显式声明的依赖,是传递性依赖,这是一种潜在的风险,可能依赖的不是自己想要的版本,或者传递性依赖的版本升级了自己确没有感知到。所以,在项目中显示声明项目直接用到的依赖是一种好的实践。显式声明但未直接使用的依赖,可能是无用依赖,也可能是运行时的依赖,需要具体甄别。

Maven生命周期和插件

Maven生命周期:http://juvenshun.iteye.com/blog/213959
生命周期阶段和插件目标 内置插件的声明周期绑定:http://www.cnblogs.com/build-up/p/4973358.html

自定义插件绑定生命周期:

<plugin>
    <groupId>com.example.demo</groupId>
    <artifactId>com.example.demo.self-define.plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <executions>
        <execution>
        <id>pre-check</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>codestyle-check</goal>
            </goals>
    </execution>
    </executions>
</plugin>

phase可选,因为很多插件目标在编写时已经默认绑定了阶段。pom配置会覆盖默认绑定。

多个插件目标绑定在同一个生命周期阶段,将会根据插件声明的先后顺序执行。
查找可用的maven插件:http://maven.apache.org/plugins/index.html

Maven仓库

Maven仓库简单来说可以分为本地仓库和远程仓库,远程仓库包括中央仓库、其它公共仓库、私服等。

本地仓库就是$MAVEN_HOME/conf/settings.xml中配置的localRepository,如果不配置默认为当前用户目录下的.m2/repository。

不在本地的仓库都可以称为远程仓库,其中,maven默认的超级POM,配置的远程仓库地址为中央仓库:

<repositories>
    <repository>
      <id>central</id>
      <name>Central Repository</name>
      <url>http://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

还有一些其它公共的远程仓库,不一一列举。其中,私服是一个特殊的远程仓库,一般公司都会选择假设自己的私服代理,具有节省带宽、加速构建、部署自有构件等优势。

远程仓库配置

在settings.xml中配置,仓库repository配置中id是关键,是仓库的唯一标识。其中中央仓库的id是central,如果配置时id使用central会覆盖中央仓库。
如果公司自己假设私服,通常还需要配置仓库用户名密码server。还可以为多个仓库配置统一的镜像mirror入口。

远程仓库配置与认证、发布至远程仓库、配置远程仓库的镜像

http://www.cnblogs.com/AlanLee/p/6198413.html

聚合与继承

聚合

一个项目往往有多个子模块,如果挨个对子模块进行构建,就太麻烦了。maven支持聚合能力,通过modules配置实现。

<?xml version="1.0" encoding="UTF-8"?>
<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.example.demo</groupId>
    <artifactId>com.example.demo.parent</artifactId>
    <packaging>pom</packaging>
    <name>demo-parent</name>
    <version>1.0-SNAPSHOT</version>
    <description>Demo Parent</description>
    <modules>
        <module>com.example.demo.payment</module>
        <module>com.example.demo.order</module>
        <module>com.example.demo.search</module>
        <module>com.example.demo.database</module>
    </modules>
        ...
</project>

module的值是相对于当前POM的路径。

通常将聚合模块的配置放在POM最顶层,这样一目了然。聚合模块的packaging必须是pom。

对聚合模块的POM执行mvn命令,maven会分析构建顺序,然后依次为每个子模块执行相同的mvn命令。

继承

项目众多子模块有很多相似之处,比如groupId, version, dependency等等,为了实现一处声明,多处使用,maven支持继承,通过parent配置来实现。

<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>
    <parent>
        <groupId>com.example.demo</groupId>
        <artifactId>com.example.demo.parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>com.example.demo.payment</artifactId>
    <packaging>jar</packaging>
        ...
</project>

relativePath表示父模块的POM相对于当前POM的路径。

可继承的POM元素有:http://www.cnblogs.com/liuzy2014/p/5794798.html

依赖管理

在父pom中添加公共的依赖,进行统一管理,是一种好的习惯。但是,如果一个项目有10个子模块,只有4个子模块依赖了spring,如果直接在父pom中配置dependency,就会导致其它6个模块也会依赖spring,这样是不够合理的。

Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。

比如,父模块有如下spring依赖:

<?xml version="1.0" encoding="UTF-8"?>
<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.example.demo</groupId>
    <artifactId>com.example.demo.parent</artifactId>
    <packaging>pom</packaging>
    <name>demo-parent</name>
    <version>1.0-SNAPSHOT</version>
    <description>Demo Parent</description>
    <modules>
        <module>com.example.demo.payment</module>
        ...
    </modules>
    
    <properties>
        <springframework.version>2.5.6</springframework.version>
        ...
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            ...
        </dependencies>
    </dependencyManagement>
        ...
</project>

子模块如果需要该依赖,只要配置groupId和artifactId即可,version等配置从父模块继承。

<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>
    <parent>
        <groupId>com.example.demo</groupId>
        <artifactId>com.example.demo.parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>com.example.demo.payment</artifactId>
    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        ...
    </dependencies>
    ...
</project>

这种配置虽然看起来并没有简化,但却是一种好的工程规范实践,有利于项目管理。

插件管理

插件管理和依赖管理的逻辑基本相同,不过是把dependencyManagement换成了pluginManagement。

约定优于配置

Maven的理念是约定优于配置,尽量多的使用约定的默认配置,有助于新成员理解项目,也有利于你在不同项目切换时的快速上手。

应该尽量遵循超级POM的约定。超级POM位于$MAVEN_HOME/lib/maven-model-builder-*.jar中的org/apache/maven/model/pom-4.0.0.xml,可以在此查看默认设置。

使用Maven进行测试

http://c.biancheng.net/view/5281.html

灵活的构建

Maven属性

http://c.biancheng.net/view/5284.html

Maven Profile

http://c.biancheng.net/view/5286.html

资源过滤

http://c.biancheng.net/view/5288.html

生成站点信息

mvn site命令可以调用maven-site-plugin插件生成站点信息,默认生成位置在target/site目录下。

changelog

mvn changelog:changelog

javadoc

mvn javadoc:javadoc

编写Maven插件

http://c.biancheng.net/view/5293.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值