添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

可以看出有很多功能, 个人的理解就是从 javac HelloWorld.java 到开发复杂结构的项目, 中间就差一个Maven.

Maven里的一切都是围绕project发生的, 使用pom.xml描述关于project的各种信息.

由pom结构拆解maven功能

Project Object Model

官方reference xsd文件 都是了解pom很好的途径. 可以将pom整体分为四个部分:

presentation notes:此时看官方文档, intro-pom.xml, 和xsd.

  • 基本信息 . 各种ID和描述.
  • 项目组织结构/依赖 . parent和modules, dependencies和dependencyManagement等等.
  • 构建配置 . build配置, 生命周期和插件.
  • 外部环境 . scm, distributionManagement等, 还有比较重要的repositories.
  • 1. 基本信息

      <!-- 基本信息 -->
      <groupId>...</groupId>
      <artifactId>...</artifactId>
      <version>...</version>
      <packaging>...</packaging>
      <classifier>...</classifier>
      <!-- 描述信息 -->
      <name>...</name>
      <description>...</description>
      <url>...</url>
      <inceptionYear>...</inceptionYear>
      <licenses>...</licenses>
      <organization>...</organization>
      <developers>...</developers>
      <contributors>...</contributors>
    

    groupId, artifactId, version等等, 作为项目的唯一ID, 完整的是 groupId:artifactId:packaging:classifier:version.

    这里两个需要特别说明的是:

  • packaging, 项目的打包类型, jar/war/ear/pom/maven-plugin.
  • classifier, 额外的标识符, 比如jdk14, jdk15这种不同环境的区分, 或者javadoc, sources这种特殊的包(commons-io-2.6-javadoc.jar), 发布的时候可以指定发布的classifier, 下载依赖的时候可以指定所需的classifier.
  • name, developers, license, organization等等纯描述性信息.

    2. 项目组织结构/依赖

    Maven中能够处理的项目间关系包括三种:

  • 依赖(dependencies)
  • 继承(inheritance)
  • 组合(aggregation, multi-module)
  • 依赖(dependencies)

      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.0</version>
          <type>jar</type>
          <scope>test</scope>
          <optional>true</optional>
          <exclusions>...<exclusions>
        </dependency>
      </dependencies>
    

    在pom的dependencies中配置的dependency, 即表示这个项目要依赖的项目, maven帮助我们管理这些依赖.

    关于version的一个误解是maven只能指定固定的版本号, 实际:

    1.0: "Soft" requirement on 1.0 (just a recommendation, if it matches all other ranges for the dependency) [1.0]: "Hard" requirement on 1.0 (,1.0]: x <= 1.0 [1.2,1.3]: 1.2 <= x <= 1.3 [1.0,2.0): 1.0 <= x < 2.0 [1.5,): x >= 1.5 (,1.0],[1.2,): x <= 1.0 or x >= 1.2; multiple sets are comma-separated (,1.1),(1.1,): this excludes 1.1 (for example if it is known not to work in combination with this library)

    scope表示, 这个依赖在各种构建步骤时是否要放到classpath里

    网友制图:

  • 不同scope的依赖, 处理其传递依赖不同. 见下面的表.
  • exclusion 手动配置排除传递依赖. A exclude掉 B 的依赖 C. (会排除整个树下所有的C);
  • optional dependency. 可选依赖这个命名不合适, 其实是B的owner可以将C标记为optional, 然后A依赖B的时候就不会引入C, 相当于default excluded.
  • dependencyManagement. 依赖管理xml下面的依赖不是真的依赖, 而是当出现传递依赖或没指定版本号的依赖是, 使用依赖管理里面指定的. (依赖管理有个scope为import的特殊用法)
  • 经过以上还是传递依赖冲突时, 以离主项目最近的为准, 最近(closest)就是说树的深度最小. 相同时按顺序. A->B->C1.0 A->D->E->C2.0 会选C1.0.
  • 我的compile依赖的compile依赖还是我的compile依赖
  • 我的compile依赖的provided依赖被忽略
  • 我的compile依赖的runtime依赖还是我的runtime依赖
  • 我的compile依赖的test依赖被忽略
  • 继承(inheritance, parent)

      <parent>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>my-parent</artifactId>
        <version>2.0</version>
        <relativePath>../my-parent</relativePath>
      </parent>
    

    继承就和java中对象继承一样, 项目会继承parent项目pom中的绝大多数内容(除了artifactId这种), 冲突时以自己的为准. 这也是parent的作用, 抽出公共的配置放在parent里.

    parent项目的packageType必须为pom, 也就是不能是jar这种. (类似于只能是接口不能是包含代码的具体类).

    所有的项目的默认会有根的parent叫Super POM, (类似于java中的Object类). 这个就是神奇发生的地方.

    组合(aggregation,multi-module)

      <groupId>org.codehaus.mojo</groupId>
      <artifactId>my-parent</artifactId>
      <version>2.0</version>
      <!-- 也是必须为pom类型 -->
      <packaging>pom</packaging>
      <modules>
        <module>my-project</module>
        <module>another-project</module>
        <module>third-project/pom-example.xml</module>
      </modules>
    

    build带有modules的project, maven会收集所有子模块, 按一定顺序build每一个子模块.

    组合和继承没有什么必然关系. parent项目可以包含或不包含modules.

    配置变量properties

    maven中可以引入五种配置变量

  • ${env.PATH}, 取shell环境变量
  • ${project.version}, 取pom.xml里的项
  • ${settings.offline}, 取settings.xml里的项
  • ${java.home}, 取java.lang.System.getProperties()能取到的项
  • ${someVar}, 在pom的properties里自定义的项
  • 这些配置变量, 可以在pom文件中用${}来取用, 也可以用在后面会提到的filter中.

    3. 构建

    上面说了一堆项目的各种信息和依赖, 接下来介绍的就是真正执行各种功能. 我们常用的命令mvn clean package是什么意思呢? mvn -h可以看出, 命令执行时, 后面给出的是 [<goal(s)>] [<phase(s)>].

    presentation notes:此时看idea右侧都是什么呢?

    lifecycle & phase

    maven将项目的构建组织成lifecycle, 每个lifecycle有多个阶段phase组成. 默认三个lifecycle: default, clean, site, 其中default由如下阶段组成:

  • validate - validate the project is correct and all necessary information is available
  • compile - compile the source code of the project
  • test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package - take the compiled code and package it in its distributable format, such as a JAR.
  • verify - run any checks on results of integration tests to ensure quality criteria are met
  • install - install the package into the local repository, for use as a dependency in other projects locally
  • deploy - done in the build environment, copies the final package to the remote repository for sharing with other developers and projects.
  • 这些阶段是有序的, 比如我们执行package, 那么package前面的几个阶段都会先执行.

    (除了这些可以看到和执行的phase还有一些隐含的phase, 完整的phase参看)

    plugin & goal

     <plugin>
       <groupId>org.codehaus.modello</groupId>
       <artifactId>modello-maven-plugin</artifactId>
       <version>1.8.1</version>
       <executions>
         <execution>
           <configuration>
             <models>
               <model>src/main/mdo/maven.mdo</model>
             </models>
             <version>4.0.0</version>
           </configuration>
           <goals>
             <goal>java</goal>
           </goals>
         </execution>
       </executions>
     </plugin>
    

    每个阶段具体做什么取决于插件, 每个插件有多个goals, 插件的功能通过goals暴露. 比如clean插件有clean:cleanclean:help两个goals.

    presentation notes:此时看idea右侧插件的goals

    每个goal可以灵活绑定到一个或多个phase上:

  • 插件开发者开发每个goal时指定默认绑定的phase.
  • 项目拥有者通过<execution>指定自己想要的绑定.
  • 所以执行构建的mvn [<goal(s)>] [<phase(s)>]:

  • 直接执行某个插件的某个goal.
  • 指定某个phase, 按顺序从上到下执行每个phase, 就是执行每个phase上绑定的所有goals.
  • presentation notes:此时看mvn执行输出中的 — xx

    所有maven构建步骤都由各个插件完成, 即使pom.xml中没有配置任何插件, maven也根据打包类型不同, 默认绑定了一些插件的goals到不同的phase.

    其实这个goals灵活绑定到phase上的设计还是不错的, 不过没啥太大用处的lifecycle设计可能被idea嫌弃了, 所以idea右侧lifecycle是一个简化形式.

    resource & filter

      <build>
    	<filters>
    	  <filter>src/main/profiles/${package.env}/config.properties</filter>
          <filter>src/main/profiles/${package.env}/jdbc.properties</filter>
        </filters>
        <resources>
          <resource>
            <targetPath>META-INF/plexus</targetPath>
            <filtering>false</filtering>
            <directory>${basedir}/src/main/plexus</directory>
            <includes>
              <include>configuration.xml</include>
            </includes>
            <excludes>
              <exclude>**/*.properties</exclude>
            </excludes>
          </resource>
        </resources>
        <testResources>
        </testResources>
      </build>
    

    build中另外比较重要的配置就是资源. 一些配置文件, 静态文件等不需要编译的东西我们一般放在src/main/resources目录(为什么用这个目录呢)下, 编译的时候需要拷贝到该去的地方.

    还有一个filter功能, 就是将资源文件中的${someVar}占位符替换为实际的变量值. 这里可用的someVar就是上面提到过的所有五种properties, 外加使用filters配置的.properties文件.

    同一个目录的资源也可以分开处理, 可以指定<inclues><excludes>(冲突时exclude wins.), 可以指定某些资源是否filter.

    ${}的变量占位符写法是默认的, 可以通过<resource.delimiter>@</resource.delimiter>这项properties配置改掉, springboot就这么坑人@someVar@

    其他构建配置

      <defaultGoal>install</defaultGoal>
      <directory>${basedir}/target</directory>
      <finalName>${artifactId}-${version}</finalName>
      <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
      <scriptSourceDirectory>${basedir}/src/main/scripts</scriptSourceDirectory>
      <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>
      <outputDirectory>${basedir}/target/classes</outputDirectory>
      <testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory>
    

    reporting

    site阶段, 将项目的基本信息, 依赖信息, 文档, 测试报告等, 生成一个site. 像测试报告等各种功能可以使用插件增强, 不详细介绍.

    4. 外部环境

    repository

      <repositories>
       <repository>
          <releases>
            <enabled>false</enabled>
            <updatePolicy>always</updatePolicy>
            <checksumPolicy>warn</checksumPolicy>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
            <checksumPolicy>fail</checksumPolicy>
          </snapshots>
          <id>codehausSnapshots</id>
          <name>Codehaus Snapshots</name>
          <url>http://snapshots.maven.codehaus.org/maven2</url>
          <layout>default</layout>
        </repository>
      </repositories>
        

    仓库: 就是下载依赖的地方, Super POM中配置了中央仓库, 全世界的人都从这里下载jar包.

    镜像仓库: 为了速度快之类的原因, 可以在settings.xml里配置镜像仓库 <mirror>, 比如一个阿里云的源的central仓库的镜像. (一个仓库至多指定一个镜像)

    私有仓库: 搭建自己的私有仓库, 里面放一些私有的包, 在项目中添加和使用. 也可以在settings.xml里的profile里配置全局的私有仓库. 多个仓库按照在pom中的顺序进行使用.

  • 本地仓库: 其实maven的策略是在本地user/.m2目录下存放本地缓存仓库, 本地有的时候就不用从远程下载了. 使用updatePolicy和checksumPolicy策略校验本地缓存是否有效.

    presentation notes:此时看.m2, 可以看到里面都是按groupId, artifactId组织的依赖

  • releases or snapshots: maven将项目版本分为这两类. 有的仓库比如中央仓库里是不放snapshot版本东西的.
  • 插件仓库同理 <pluginRepositories>
  • profile

      <profiles>
        <profile>
          <id>test</id>
          <activation>...</activation>
          <build>...</build>
          <modules>...</modules>
          <repositories>...</repositories>
          <pluginRepositories>...</pluginRepositories>
          <dependencies>...</dependencies>
          <reports>...</reports>
          <reporting>...</reporting>
          <dependencyManagement>...</dependencyManagement>
          <distributionManagement>...</distributionManagement>
          <properties>...</properties>
        </profile>
      </profiles>
    

    不同环境不同配置, 比如dev, test, online, jdk8等等, 当这项profile激活时, 用profile里的东西覆盖项目的配置.

    我们profile的常用用法, 设个变量, 拷贝resource或添加filter. 其实<profile>下面可以放的配置有很多, 都可以控制不同profile进行变化.

    presentation notes:此时看idea代码, 解释上述两种用法.

    profile的激活, 除了运行mvn -Ptest这种方式, 还可以在<activation>控制. 多个profile可以同时生效.

    <activation>
      <activeByDefault>false</activeByDefault>
      <jdk>1.5</jdk>
        <name>Windows XP</name>
        <family>Windows</family>
        <arch>x86</arch>
        <version>5.1.2600</version>
      <property>
        <name>sparrow-type</name>
        <value>African</value>
      </property>
        <exists>${basedir}/file2.properties</exists>
        <missing>${basedir}/file1.properties</missing>
      </file>
    </activation>
    

    scm issue ci dist

      <issueManagement>...</issueManagement>
      <ciManagement>...</ciManagement>
      <mailingLists>...</mailingLists>
      <scm>...</scm>
      <prerequisites>...</prerequisites>
      <distributionManagement>...</distributionManagement>
    

    看起来大部分没啥用的, 其实是可以被一些插件拿来做功能的, 比如<distributionManagement>配置可以在deploy阶段将打好的jar包发布到maven仓库中.

    其他todo

    settings.xml

    对比Gradle

    flexibility, performance, user experience, and dependency management

  • 配置简单, 可能有些人比较烦xml吧, gradle使用dsl. 而且虽然都是约定大于配置, gradle内置的更方便一些.
  • 各种提示信息, report, 界面, 比较友好. build scan更是不错, 不过必须发到他的网站上. 不过Idea支持非常蠢.
  • 通过加cache之类的操作, 速度快很多.
  • Android,Groovy官方使用, C++/Scala/Kotlin/Java/Js都可以用.
  • 大部分概念都很相像:

  • 包, 命名规范, 沿用Maven的
  • 仓库沿用Maven的, 另外可以用JCenter库.
  • 功能也靠各种插件实现.
  • Maven是plugin+goal, Gradle是plugin+task
  • Maven通过将goal绑定phase来实现动态控制执行步骤. Gradle通过task直接的depend关系形成DAG动态控制执行步骤.
  • <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.19</version> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.1</version> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>default-report</id> <goals> <goal>report</goal> </goals> </execution> <execution> <id>default-check</id> <goals> <goal>check</goal> </goals> <configuration> <rules> <rule implementation="org.jacoco.maven.RuleConfiguration"> <element>BUNDLE</element> <limits> <limit implementation="org.jacoco.report.check.Limit"> <counter>COMPLEXITY</counter> <value>COVEREDRATIO</value> <minimum>0.06</minimum> </limit> </limits> </rule> </rules> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>3.0.2</version> </plugin> </plugins> </build> <!--所有<reporting>里面的配置不是必须的, 当想要mvn site生成项目site时可以加上--> <reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-report-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <reportSets> <reportSet> <reports> <report>report</report> </reports> </reportSet> </reportSets> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>3.0.2</version> </plugin> </plugins> </reporting>

    UPDATE1: Assembly

    使用Assembly插件打成自定义格式的包, 参考官方文档 1 2

    UPDATE2: transitive repository

    原本以为mvn只会使用setting和项目pom里明确指定的repo下载依赖, 然而有个项目报错从一个amazon的repo下东西超时.

    使用mvn dependency:list-repositories 命令看到, 列出的repo远超我配置的. 猜测是mvn还会使用我的依赖的pom里配置的repo, 我称之为鬼畜的传递repo.

    参考这两个SO问题:

  • https://stackoverflow.com/q/1754495/5142886
  • https://stackoverflow.com/q/14502440/5142886
  • 使用里面的方法干掉repo就好了