Maven
内容摘自《Introducing Maven》,包括对Maven的介绍和实际应用的讲解
Maven安装目录的主要内容
- bin:包含mvn/mvnDebug,分别是运行maven和在IDE中运行maven来远程debug
- boot:maven使用plexus classworlds框架来构建类加载器的图
- conf:配置文件,最重要的是settings.xml,其次simplelogger.properties可以修改maven的log行为
- lib:maven和插件运行所必须的库
MAVEN_OPTS环境变量可以设置maven能使用的JVM内存,建议为(-Xmx512m)
Maven从两个地址来查看settings.xml文件,一个是安装目录的conf目录中,另一个是home目录的.m2文件夹。前者是全局配置,后者是用户设置(优先级更高)
# 可以查找到本地的repository位置
mvn help:evaluate -Dexpression=settings.localRepository
settings.xml的配置项
- localRepository:改变仓库的位置
- interactiveMode:true,maven会接收用户指令,否则会使用一些默认值。默认为true
- offline:true则处于离线模式,不会下载和更新依赖。默认为false
- servers:maven可以和一些server交互,例如git/build/remote repository等。这里可以放验证信息
- mirrors:指定远程仓库下载依赖的位置
- 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的依赖管理
原始的依赖处理的问题
- 下载一个JAR,它可能还依赖其它的JAR,这都需要手动下载
- 如果要更新JAR的版本,重复1的操作
- 必须要将这些JAR也放在版本控制系统中,增加了项目大小/检查和构建时间
- 共享JAR变得困难
Maven的声明式依赖管理
在pom.xml文件中声明你项目使用的依赖,在你的项目的构建、测试和打包中使用
在第一次运行maven项目时,它会从互联网上下载所需要的artifact和元数据。然后会将这些JAR保存到本地仓库,之后运行,只有当本地仓库没有时才会继续从远程仓库下载
这种方式(使用maven central,即中央仓库)有三个现实问题
- 公司不可能把自己的jar发布到maven central
- 公司希望使用官方承认的开源软件
- 带宽和下载速度
一般的解决办法是公司自己持有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)来区分
- groupId:组织名,对这个项目负责
- artifactId:项目名,在相同组织名下这个id必须唯一
- version:版本号
- 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(域)的概念来指定在什么时候/在哪里需要哪些特定的依赖
- compile:在项目的build/test/run过程中这些jar在类路径下可见,默认的scope
- provided:build和test阶段可见,它们不会在最终的artifact中(例如容器中运行的包不包含JSP api和Servlet api)
- runtime:build阶段不可见,但是它包含在生成的artifact中
- test:test阶段可见
- system:和provided类似,这些依赖不会从仓库获取,而是硬编码路径(文件系统)
- 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项目有三个内置的生命周期
- default:compile/package/deployment
- clean:删除target目录
- site:文档和站点(site)生成
默认的default声明周期包含以下phase:
- validate:保证项目正确/所有依赖都被下载
- compile:编译源码
- test:运行单元测试,这一步骤不需要打包
- package:汇编所有的代码成为可发布的格式,例如JAR/WAR
- install:将这个包安装到本地仓库,这个包可以被当前机器运行的所有项目使用
- 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元素的值为pommvn 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的网页端看到发布的包
项目的发布
- 验证是否有未提交的改动
- 移除SNAPSHOT
- 确保项目没有使用任何SNATSHOT依赖
- 将修改的pom提交到代码控制工具
- 在源码控制中创建一个tag
- 构建一个新版本,然后发布到仓库管理器
- 在pom文件中增加版本,准备下一个发布版本
Maven提供了release插件来提供上述步骤的标准的机制。假设你是用git作为scs,并且github是存放仓库的远程服务器。maven和github的交互方式图
在开发机器上要做的事情
- 安装git客户端
- 创建新的远程仓库
- 将项目的改动添加进远程仓库
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,prepare
和perform
,并且这个插件提供了一个clean
的goal来方便清除错误操作
prepare阶段maven做的操作:
- check-pom:检查pom的版本是否有SNAPSHOT
- scm-check-modification:检查是否有未提交的改动
- check-dependency-snapshot:检查是否使用了SNAPSHOT的依赖,最佳实践就是不使用
- map-release-version:prepare如果在交互式下操作,会询问用户发布版本
- map-development-version:询问下一个开发版本
- generate-release-pom:生成release的pom
- scm-commit-release:提交生成的pom到SCM
- scm-tag:在SCM中创建一个tag
- rewrite-poms-for-development:对于新的开发周期的pom进行修改
- remove-release-pom:删除发布需要的pom
- scm-commit-development:提交开发版本的pom
- 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中拉取代码进行构建,并且发布到远程仓库
- verify-completed-prepare-phases:之前是否执行过prepare
- checkout-project-from-scm:从SCM对应tag中拉取代码
- 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的页面