Maven

内容摘自《Introducing Maven》,包括对Maven的介绍和实际应用的讲解

Maven安装目录的主要内容

  1. bin:包含mvn/mvnDebug,分别是运行maven和在IDE中运行maven来远程debug
  2. boot:maven使用plexus classworlds框架来构建类加载器的图
  3. conf:配置文件,最重要的是settings.xml,其次simplelogger.properties可以修改maven的log行为
  4. lib:maven和插件运行所必须的库

MAVEN_OPTS环境变量可以设置maven能使用的JVM内存,建议为(-Xmx512m)

Maven从两个地址来查看settings.xml文件,一个是安装目录的conf目录中,另一个是home目录的.m2文件夹。前者是全局配置,后者是用户设置(优先级更高)

# 可以查找到本地的repository位置
mvn help:evaluate -Dexpression=settings.localRepository

settings.xml的配置项

  1. localRepository:改变仓库的位置
  2. interactiveMode:true,maven会接收用户指令,否则会使用一些默认值。默认为true
  3. offline:true则处于离线模式,不会下载和更新依赖。默认为false
  4. servers:maven可以和一些server交互,例如git/build/remote repository等。这里可以放验证信息
  5. mirrors:指定远程仓库下载依赖的位置
  6. proxies:连接互联网的HTTP协议

如果公司环境限制访问互联网,可以在proxies标签中创建新的协议(连接仓库)

<proxy>
     <id>companyProxy</id>
     <active>true</active>
     <protocol>http</protocol>
     <host>proxy.company.com</host>
     <port>8080</port>
     <username>proxyusername</username>
     <password>proxypassword</password>
     <nonProxyHosts />
</proxy>

这种将密码写在配置文件的情况有泄漏风险,可以使用mvn -emp password生成密码,然后将这个密码要保存到settings-security.xml文件中(.m2)。mvn -ep proxypassword(刚才的加密密码),再替换到上面的password属性中

Maven的依赖管理

原始的依赖处理的问题

  1. 下载一个JAR,它可能还依赖其它的JAR,这都需要手动下载
  2. 如果要更新JAR的版本,重复1的操作
  3. 必须要将这些JAR也放在版本控制系统中,增加了项目大小/检查和构建时间
  4. 共享JAR变得困难

Maven的声明式依赖管理

在pom.xml文件中声明你项目使用的依赖,在你的项目的构建、测试和打包中使用

在第一次运行maven项目时,它会从互联网上下载所需要的artifact和元数据。然后会将这些JAR保存到本地仓库,之后运行,只有当本地仓库没有时才会继续从远程仓库下载

这种方式(使用maven central,即中央仓库)有三个现实问题

  1. 公司不可能把自己的jar发布到maven central
  2. 公司希望使用官方承认的开源软件
  3. 带宽和下载速度

一般的解决办法是公司自己持有repository manger(仓库管理器),类似于远程仓库的代理,它可以缓存远程仓库的artifact,并且在这里控制公司内部可以使用哪些包。并且你可以将自己的artifact发布到仓库管理器上

在settings.xml和pom.xml上都可以指定仓库。后者可以让构建变得容易,因为它不需要修改本地的settings.xml文件。问题是当发布artifact时,这个文件中的仓库信息是硬编码的,如果仓库的url发生变更,那么使用这个artifact的项目会报错,而前者不会有这些问题

<profiles>
  <profile>
    <id>your_company</id>
    <repositories>
      <repository>
        <id>spring_repo</id>
        <url>http://repo.spring.io/release/</url>
      </repository>
      <repository>
        <id>jboss_repo</id>
        <url>https://repository.jboss.org/</url>
      </repository>
    </repositories>
  </profile>
</profiles> 
<activeProfiles>
  <activeProfile>your_company</activeProfile>
</activeProfiles> 

Maven的依赖通常是JAR、WAR、EAR和ZIP的形式归档,每个依赖都由group/artifact/version(GAV)来区分

  1. groupId:组织名,对这个项目负责
  2. artifactId:项目名,在相同组织名下这个id必须唯一
  3. version:版本号
  4. type:生成的artifact的打包方式

SNAPSHOT表示这个项目还在开发中。它告诉maven每次build这个项目时要去远程仓库查看这个项目的更新版本

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-tools</artifactId>
  <version>5.4.2.Final</version>
</dependency>

pom.xml中声明的依赖(包)也有它们自己的依赖,叫做间接依赖(transitive dependencies),maven会自动处理这些间接依赖。maven使用dependency mediation技术来处理版本冲突,简单来说,maven会选择离依赖树更近的依赖版本,如果出现同一层的不同版本依赖,需要在pom.xml文件中显式指出

Maven提供了依赖插件来查看项目的依赖树,mvn dependency:tree

当你不想将一些jar包打入最终的包时,可以使用 excludes 标签来排除间接依赖

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>${junit.version}</version>
  <scope>test</scope>
  <exclusions>
    <exclusion> <!— 这里也需要groupId和artifactId —>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest</artifactId>
    </exclusion>
  </exclusions>
</dependency>

依赖的Scope

例如JUnit的JAR不需要包含在最终的生产环境包中,而mysql的驱动包在代码编译或者测试的时候并不需要。maven使用scope(域)的概念来指定在什么时候/在哪里需要哪些特定的依赖

  1. compile:在项目的build/test/run过程中这些jar在类路径下可见,默认的scope
  2. provided:build和test阶段可见,它们不会在最终的artifact中(例如容器中运行的包不包含JSP api和Servlet api)
  3. runtime:build阶段不可见,但是它包含在生成的artifact中
  4. test:test阶段可见
  5. system:和provided类似,这些依赖不会从仓库获取,而是硬编码路径(文件系统)
  6. import:只对.pom文件依赖有效,允许包含从远程(remote)pom文件中的依赖管理信息。Maven2.0.9之后可用

手工安装依赖,mvn install:install-file,这会安装到本地仓库中

基本的maven项目结构:

  • 通常,项目的根目录和artifact名字相同
  • src包含源码和配置文件,这里的代码在版本控制系统(SCM)中
  • src/main/java包含Java源码
  • src/test/java包含Java的单元测试代码
  • target目录包含生成的artifact,例如class文件,通常这些文件都不在SCM(source code manage)中
  • LICENSE 证书
  • README 项目简介
  • NOTICE 这个项目使用的第三方库的注意事项
  • pom.xml 每个maven项目都有(在项目跟路径下),包含项目和配置信息--依赖和插件

Maven其它的一些目录

  • src/main/resources 资源文件,例如Spring配置文件和模版,它们会在生成的artifacts中
  • src/main/config 配置文件,例如tomcat的context file等,它们不会在artifact中
  • src/main/scripts 系统管理员和开发者可能会使用的脚本
  • src/test/resources 测试需要的配置文件
  • src/main/webapp web asset
  • src/it 应用的集成测试
  • src/main/db 数据库文件,例如sql脚本
  • src/site 生成项目站点(文档)时所需的文件

开发过程中最好将项目的版本带上SNAPSHOT

mvn package是maven的一个阶段(phase),将java代码编译并打包,JAR包最终会在target目录下

编写了单元测试之后,mvn package会执行测试代码,并且target目录中会有test-classes文件夹和相关的surefire-reports文件夹

Maven提供了占位符可以在pom.xml中使用。形式是${property_name},有两种形式:隐式的和用户定义的。前者是所有maven项目都有的属性,maven将当前项目的一些属性以“project.“的前缀来表明,例如${project.artifactId}

<build>
  <finalName>${project.artifactId}</finalName>
</build>

同理,使用”settings.“前缀可以引用settigns.xml文件中的属性,使用”env.“可以引用环境变量(PATH)。后者通过properties标签来声明自定义属性,通常用来指定依赖版本号

Maven生命周期

从build到发布项目,maven通过生命周期提供了同一的接口来实现所有功能

goal和plug-in

编译源码 -> 运行单元测试 -> 打包artifact。maven使用goal来描述这些任务

例如compile的goal。mvn compiler:compile,compile这个goal就是编译源码,先找到java文件,然后将编译好的class文件放到target/classes文件夹下

maven中的goal放在plug-in中,它包含一组goal。target文件夹拥有maven生成的临时文件和artifact,当这些文件需要被清理时,clean这个goal来完成这个任务,它会尝试删除target目录。mvn clean:clean冒号前面是plug-in名字,后面是goal

Maven提供了help插件用来列出给定插件的goal。例如 mvn help:describe -Dplugin=compiler

插件和它们的行为可以在pom.xml中的plug-in部分配置

<build>
   <plugins>
    <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>3.8.1</version>
       <configuration>
         <source>1.8</source>
         <target>1.8</target>
       </configuration>
     </plugin> 
   </plugins>
</build>

build标签中的finalName标签可以在不改变artifactId的前提下修改打成的包的名字

Maven的goal是粗粒度的,通常只完成一个任务。maven的build声明周期由一系列按固定顺序执行的阶段完成,这些阶段称作phases。每个maven项目有三个内置的生命周期

  1. default:compile/package/deployment
  2. clean:删除target目录
  3. site:文档和站点(site)生成

默认的default声明周期包含以下phase:

  1. validate:保证项目正确/所有依赖都被下载
  2. compile:编译源码
  3. test:运行单元测试,这一步骤不需要打包
  4. package:汇编所有的代码成为可发布的格式,例如JAR/WAR
  5. install:将这个包安装到本地仓库,这个包可以被当前机器运行的所有项目使用
  6. deploy:将这个包推送到远程仓库

Maven会自动执行某个phase之前所有的phase。每个phase都关联0个或多个goal。当某个phase没有goal,那么maven会跳过这个phase的执行,这种phase留作第三方供应者和用户的自定义build goal

pom.xml中的packaging标签会自动给每个phase以正确的goal(CoC)

如果想跳过单元测试,可以使用mvn package -Dmaven.test.skip=true

开发自定义的maven插件

一个插件就是一组goal,这些goal使用MOJO来实现。运行goal的命令总是pluginId:goal-name

控制插件的执行phase

<executions> 
    <execution>
     <phase>validate</phase>
      <goals>
       <goal>systeminfo
         </goal>
        </goals>
     </execution>
</executions>

Maven的archetypes

为了节省每次新建项目中的各种冗余操作,maven提供了archetypes。公司可以通过archetype来发布包括自己拥有的内部组件的项目结构

archetype插件的generate goal可以查看并且选择archetype来使用

<plugins>
      <plugin>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-maven-plugin</artifactId>
          <version>9.4.12.RC2</version>
       </plugin>
</plugins>

以内嵌的服务器来启动根据webapp原型生成的项目,可以通过mvn jetty:run来运行嵌入式的服务器

多模块项目

每个模块都会生成artifact,例如EJB/Web Service/web项目/客户端jar等。maven支持这种大型的JEE项目,即将多个maven项目嵌套进单个maven项目中

  • mvn archetype:generate -DgroupId=com.apress.gswmbook -DartifactId=gswm-parent -Dversion=1.0.0-SNAPSHOT -DarchetypeGroupId=org.codehaus.mojo.archetypes -DarchetypeArtifactId=pom-root
    项目中只有一个pom文件,packaging元素的值为pom
  • mvn archetype:generate -DgroupId=com.apress.gswmbook -DartifactId=gswm-web -Dversion=1.0.0-SNAPSHOT -Dpackage=war -DarchetypeArtifactId=maven-archetype-webapp
    创建一个webapp项目
  • mvn archetype:generate -DgroupId=com.apress.gswmbook -DartifactId=gswm-service -Dversion=1.0.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
    interactiveMode参数表示直接执行命令而不询问用户,quickstart默认打包方式为jar
    创建一个service项目
  • mvn archetype:generate -DgroupId=com.apress.gswmbook -DartifactId=gswm-repository -Dversion=1.0.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
    创建一个数据库操作项目

在parent项目中的pom文件会自动追加module标签,在子模块的pom文件中会自动加上parent pom。之后简单在父项目下使用mvn package

创建新的archetype

可以从头创建,也可以从已有的项目中创建,在项目的根目录运行mvn archetype:create-from-project。通过mvn archetype:generate -DarchetypeCatalog=local来创建基于安装好的archetype生成的项目

Documentation和Reporting

Maven的site生命周期可以用来生成项目文档mvn site,它会生成一个site文件夹。通过在pom文件中添加信息可以定制文档的内容

<description>
  This project acts as a starter project for the Introducing
  Maven book (http://www.apress.com/9781484208427) published
  by Apress.
</description>
<mailingLists>
  <mailingList>
    <name>GSWM Developer List</name>
    <subscribe>gswm-dev-subscribe@apress.com</subscribe>
    <unsubscribe>gswm-dev-unsubscribe@apress.com</unsubscribe>
    <post>developer@apress.com</post> 
  </mailingList>
</mailingLists>
<licenses>
  <license>
    <name>Apache License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
  </license>
</licenses> 

使用mvn clean site可以重新生成文档。description对应about页面,licenses对应license页面

高级的定制文档页面方法:在src/site文件夹下,存在一个site.xml文件(site descriptor)。apt文件夹包含使用APT(almost plain text)格式写的网站内容。除此之外maven也支持markdown、xdoc、fml等格式

Maven提供了一些archetype来自动生成站点结构。mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-site-simple -DarchetypeVersion=1.4

生成javadoc

maven提供了javadoc的插件,在pom.xml文件中声明在reporting标签下即可

<reporting> 
    <plugins>
      <plugin>
          <artifactId>maven-javadoc-plugin</artifactId>
      </plugin>
    </plugins>
</reporting>

配置好之后,运行mvn clean site可以生成javadoc,在target/site目录下的index.html导航到javadoc

生成单元测试报告

Maven每次build都会执行所有的单元测试,任何测试失败都会导致build失败。maven提供了surefire插件来提供统一的运行测试的接口,例如JUnit和TestNG

<reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
        <version>2.17</version>
      </plugin>
    </plugins>
</reporting>

运行mvn clean site,在target目录下有surefire reports文件夹,它包括xml和txt格式的测试执行结果报告

生成代码覆盖报告(code coverage)

代码覆盖是一种检查有多少代码被测试覆盖到的技术。JaCoCo(开源)和Clover(Atlassian)是两个代码覆盖工具(java)。在以下的配置中,prepare-agent goal设置了一个属性,指向JaCoCo运行环境,当单元测试运行时可以传递给vm。report goal生成代码覆盖报告

<build>
  <plugins>
  <!--Content removed for brevity-->
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>0.8.4</version>
      <executions>
        <execution>
          <id>jacoco-init</id>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
        </execution>
        <execution>
           <id>jacoco-report</id>
           <phase>test</phase>
           <goals>
              <goal>report</goal>
            </goals>
         </execution>
      </executions>
    </plugin>
   </plugins>
<build>

运行mvn clean site,在target/site文件夹下可以看到JaCoCo文件夹

生成SpotBug报告

Spotbugs是检查java代码缺陷的工具,使用静态分析来检测bug,例如无限循环和空指针

<reporting>
    <plugins>
      <plugin>
         <groupId>com.github.spotbugs</groupId>
         <artifactId>spotbugs-maven-plugin</artifactId>
         <version>3.1.12</version>
      </plugin>
   </plugins>
</reporting>

Maven的发布(release)

Maven提供了发布插件

和Nexus的集成

Nexus仓库管理器是一个开源软件,可以维护内部的仓库并且能访问外部仓库。允许仓库分组并通过一个URL来访问,此外,它提供了定位maven的site阶段生成的文档的能力和搜索artifact的能力

pom文件中的distributionManagement标签提供项目的artifact发布到哪个仓库的信息

<distributionManagement>
   <repository>
        <id>nexusReleases</id>
        <name>Releases</name>
	<url>http://localhost:8081/repository/maven-releases</url>
   </repository>
   <snapshotRepository>
       <id>nexusSnapshots</id>
       <name>Snapshots</name>
        <url>http://localhost:8081/repository/maven-snapshots</url>
   </snapshotRepository>
</distributionManagement>

Maven要和仓库管理器交互,需要在maven的settings.xml中指定正确的用户访问信息

<servers>
   <server>
      <id>nexusReleases</id>
      <username>admin</username>
      <password>admin123</password>
   </server>
   <server>
      <id>nexusSnapshots</id>
      <username>admin</username>
      <password>admin123</password>
   </server>
</servers>

运行mvn deploy,可以在Nexus的网页端看到发布的包

项目的发布

  1. 验证是否有未提交的改动
  2. 移除SNAPSHOT
  3. 确保项目没有使用任何SNATSHOT依赖
  4. 将修改的pom提交到代码控制工具
  5. 在源码控制中创建一个tag
  6. 构建一个新版本,然后发布到仓库管理器
  7. 在pom文件中增加版本,准备下一个发布版本

Maven提供了release插件来提供上述步骤的标准的机制。假设你是用git作为scs,并且github是存放仓库的远程服务器。maven和github的交互方式图

在开发机器上要做的事情

  1. 安装git客户端
  2. 创建新的远程仓库
  3. 将项目的改动添加进远程仓库
git init
git add .
git commit -m “initial commit”
git remote add origin https://github.com/xxx/xxx.git
git push -u origin master

Maven会和远程仓库的release分支交互,所以需要在本地创建这个分支,并且推送到github

git checkout -b release
git push origin release

Maven release需要两个goal,prepareperform,并且这个插件提供了一个clean的goal来方便清除错误操作

prepare阶段maven做的操作:

  1. check-pom:检查pom的版本是否有SNAPSHOT
  2. scm-check-modification:检查是否有未提交的改动
  3. check-dependency-snapshot:检查是否使用了SNAPSHOT的依赖,最佳实践就是不使用
  4. map-release-version:prepare如果在交互式下操作,会询问用户发布版本
  5. map-development-version:询问下一个开发版本
  6. generate-release-pom:生成release的pom
  7. scm-commit-release:提交生成的pom到SCM
  8. scm-tag:在SCM中创建一个tag
  9. rewrite-poms-for-development:对于新的开发周期的pom进行修改
  10. remove-release-pom:删除发布需要的pom
  11. scm-commit-development:提交开发版本的pom
  12. end-release;完成prepare阶段

你需要在pom中提供SCM的信息

<scm>
      <connection>scm:git:https://github.com/bava/intro-maven.git</connection>
      <developerConnection>scm:git:https://github.com/bava/intro-maven.git</developerConnection>
      <url>https://github.com/bava/intro-maven</url>
</scm>

要保证maven可以访问github,需要github的认证信息。在settings.xml文件中需要提供,id必须符合域名(hostname)

<server>
     <id>github</id>
     <username>[your_github_account_name]</username>
     <password>[your_github_account_password]</password>
</server>

mvn release:prepare,当prepare goal执行失败,它生成的临时文件可以通过mvn release:clean来清理

perform goal负责从新创建的tag中拉取代码进行构建,并且发布到远程仓库

  1. verify-completed-prepare-phases:之前是否执行过prepare
  2. checkout-project-from-scm:从SCM对应tag中拉取代码
  3. run-perform-goal:执行和perform相关的goal,默认是deploy

持续集成(CI) continuous integration

CI是一种软件开发实践(best),开发者将它们代码变更集成到一个通用的仓库(每天数次)。每个提交的改变都会引发自动的build(编译/测试/生成新版本artifact),中间发生任何的错误都会报告给开发者团队

CI工具:jenkins/bamboo/teamcity/gitlab

(举例)下载jenkins之后,通过java -jar jenkins.war,在8080的页面中可以看到jenkins的页面

posted on 2021-03-27 19:13  老鼠不上树  阅读(188)  评论(0)    收藏  举报