• 09-Maven


    Maven介绍

    在了解Maven之前,我们先来看看一个Java项目需要的东西。首先,我们需要确定引入哪些依赖包。例如,如果我们需要用到commons logging,我们就必须把commons logging的jar包放入classpath。如果我们还需要log4j,就需要把log4j相关的jar包都放到classpath中。这些就是依赖包的管理。

    其次,我们要确定项目的目录结构。例如,src目录存放Java源码,resources目录存放配置文件,bin目录存放编译生成的.class文件。

    此外,我们还需要配置环境,例如JDK的版本,编译打包的流程,当前代码的版本号。

    最后,除了使用Eclipse这样的IDE进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。

    这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的Java项目管理和构建工具。

    Maven就是是专门为Java项目打造的管理和构建工具,它的主要功能有:

    • 提供了一套标准化的项目结构;
    • 提供了一套标准化的构建流程(编译,测试,打包,发布……);
    • 提供了一套依赖管理机制。

    Maven项目结构

    一个使用Maven管理的普通的Java项目,它的目录结构默认如下:

    项目的根目录a-maven-project是项目名,它有一个项目描述文件pom.xml,存放Java源码的目录是src/main/java,存放资源文件的目录是src/main/resources,存放测试源码的目录是src/test/java,存放测试资源的目录是src/test/resources,最后,所有编译、打包生成的文件都放在target目录里。这些就是一个Maven项目的标准目录结构。

    所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven就可以正常使用。

    我们再来看最关键的一个项目描述文件pom.xml,它的内容长得像下面:

    <project ...>
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.itranswarp.learnjava</groupId>
    	<artifactId>hello</artifactId>
    	<version>1.0</version>
    	<packaging>jar</packaging>
    	<properties>
            ...
    	</properties>
    	<dependencies>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
    	</dependencies>
    </project>
    

    其中,groupId类似于Java的包名,通常是公司或组织名称,artifactId类似于Java的类名,通常是项目名称,再加上version,一个Maven工程就是由groupIdartifactIdversion作为唯一标识。我们在引用其他第三方库的时候,也是通过这3个变量确定。例如,依赖commons-logging

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    

    使用<dependency>声明一个依赖后,Maven就会自动下载这个依赖包并把它放到classpath中。

    安装Maven

    要安装Maven,可以从Maven官网下载最新的Maven 3.6.x,然后在本地解压,设置几个环境变量:

    M2_HOME=/path/to/maven-3.6.x
    PATH=$PATH:$M2_HOME/bin
    

    Windows可以把%M2_HOME%in添加到系统Path变量中。

    然后,打开命令行窗口,输入mvn -version,应该看到Maven的版本信息:

    如果提示命令未找到,说明系统PATH路径有误,需要修复后再运行。

    依赖管理

    Maven解决了依赖管理问题。例如,我们的项目依赖abc这个jar包,而abc又依赖xyz这个jar包:

    Maven的第一个作用就是解决依赖管理。我们声明了自己的项目需要abc,Maven会自动导入abc的jar包,再判断出abc需要xyz,又会自动导入xyz的jar包,这样,最终我们的项目会依赖abcxyz两个jar包

    依赖关系

    Maven定义了几种依赖关系,分别是compiletestruntimeprovided

    scope 说明 示例
    compile 编译时需要用到该jar包(默认) commons-logging
    test 编译Test时需要用到该jar包 junit
    runtime 编译时不需要,但运行时需要用到 mysql
    provided 编译时需要用到,但运行时由JDK或某个服务器提供 servlet-api

    其中,默认的compile是最常用的,Maven会把这种类型的依赖直接放入classpath

    最后一个问题是,Maven如何知道从何处下载所需的依赖?也就是相关的jar包?答案是Maven维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的jar以及相关信息上传至中央仓库,Maven就可以从中央仓库把所需依赖下载到本地。

    Maven并不会每次都从中央仓库下载jar包。一个jar包一旦被下载过,就会被Maven自动缓存在本地目录(用户主目录的.m2目录),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的jar包。

    唯一ID

    对于某个依赖,Maven只需要3个变量即可唯一确定某个jar包:

    • groupId:属于组织的名称,类似Java的包名;
    • artifactId:该jar包自身的名称,类似Java的类名;
    • version:该jar包的版本。

    通过上述3个变量,即可唯一确定某个jar包。Maven通过对jar包进行PGP签名确保任何一个jar包一经发布就无法修改。修改已发布jar包的唯一方法是发布一个新版本。

    因此,某个jar包一旦被Maven下载过,即可永久地安全缓存在本地。

    注:只有以-SNAPSHOT结尾的版本号会被Maven视为开发版本,开发版本每次都会重复下载,这种SNAPSHOT版本只能用于内部私有的Maven repo,公开发布的版本不允许出现SNAPSHOT。

    Maven镜像

    除了可以从Maven的中央仓库下载外,还可以从Maven的镜像仓库下载。如果访问Maven的中央仓库非常慢,我们可以选择一个速度较快的Maven的镜像仓库。Maven镜像仓库定期从中央仓库同步:

    中国区用户可以使用阿里云提供的Maven镜像仓库。使用Maven镜像仓库需要一个配置,在用户主目录下进入.m2目录,创建一个settings.xml配置文件,内容如下:

    <settings>
        <mirrors>
            <mirror>
                <id>aliyun</id>
                <name>aliyun</name>
                <mirrorOf>central</mirrorOf>
                <!-- 国内推荐阿里云的Maven镜像 -->
                <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            </mirror>
        </mirrors>
    </settings>
    

    配置镜像仓库后,Maven的下载速度就会非常快。

    搜索第三方组件

    最后一个问题:如果我们要引用一个第三方组件,比如okhttp,如何确切地获得它的groupId、artifactId和version?方法是通过search.maven.org搜索关键字,找到对应的组件后,直接复制:

    命令行编译

    在命令中,进入到pom.xml所在目录,输入以下命令:

    mvn clean package
    

    如果一切顺利,即可在target目录下获得编译后自动打包的jar。

    在IDE中使用Maven

    如果导入后的Maven项目有错误,可以尝试选择项目后点击右键,选择Maven - Update Project...更新:

    构建流程

    Maven不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等

    Lifecycle和Phase

    使用Maven时,我们首先要了解什么是Maven的生命周期(lifecycle)。

    Maven的生命周期由一系列阶段(phase)构成,以内置的生命周期default为例,它包含以下phase:

    • validate:验证工程是否正确,所有需要的资源是否可用
    • initialize:
    • generate-sources
    • process-sources
    • generate-resources:产生应用需要的任何额外的源代码,如xdoclet
    • process-resources
    • compile:编译项目的源代码
    • process-classes
    • generate-test-sources
    • process-test-sources
    • generate-test-resources
    • process-test-resources
    • test-compile:编译项目测试代码
    • process-test-classes
    • test:使用已编译的测试代码,测试已编译的源代码
    • prepare-package
    • package:将已编译的源代码打包
    • pre-integration-test
    • integration-test:在集成测试可以运行的环境中处理和发布包
    • post-integration-test
    • verify:运行任何检查,验证包是否有效且达到质量标准
    • install:把包安装在本地的repository中,可以被其他工程作为依赖来使用
    • deploy:在整合或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享

    如果我们运行mvn package,Maven就会执行default生命周期,它会从开始一直运行到package这个phase为止:

    • validate
    • ...
    • package

    如果我们运行mvn compile,Maven也会执行default生命周期,但这次它只会运行到compile,即以下几个phase:

    • validate
    • ...
    • compile

    Maven另一个常用的生命周期是clean,它会执行3个phase:

    • pre-clean
    • clean (注意这个clean不是lifecycle而是phase)
    • post-clean

    所以,我们使用mvn这个命令时,后面的参数是phase,Maven自动根据生命周期运行到指定的phase。

    更复杂的例子是指定多个phase,例如,运行mvn clean package,Maven先执行clean生命周期并运行到clean这个phase,然后执行default生命周期并运行到package这个phase,实际执行的phase如下:

    • pre-clean
    • clean (注意这个clean是phase)
    • validate
    • ...
    • package

    在实际开发过程中,经常使用的命令有:

    • mvn clean:清理所有生成的class和jar;
    • mvn clean compile:先清理,再执行到compile;
    • mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile;
    • mvn clean package:先清理,再执行到package。

    大多数phase在执行过程中,因为我们通常没有在pom.xml中配置相关的设置,所以这些phase什么事情都不做。

    经常用到的phase其实只有几个:

    • clean:清理
    • compile:编译
    • test:运行测试
    • package:打包

    Goal

    执行一个phase又会触发一个或多个goal:

    执行的Phase 对应执行的Goal
    compile compiler:compile
    test compiler:testCompile surefire:test

    goal的命名总是abc:xyz这种形式。

    其实我们类比一下就明白了:

    • lifecycle相当于Java的package,它包含一个或多个phase;
    • phase相当于Java的class,它包含一个或多个goal;
    • goal相当于class的method,它其实才是真正干活的。

    大多数情况,我们只要指定phase,就默认执行这些phase默认绑定的goal,只有少数情况,我们可以直接指定运行一个goal,例如,启动Tomcat服务器:

    mvn tomcat:run
    

    phase 和 goal 的不同在于:

    • 运行某个 phase 的时,必须把生命周期中的所有的前置 phase 都会运行一遍。
    • 而运行 goal,可以脱离生命周期这个概念,通过 maven 插件,单独的运行某个 goal 或一组 goal。

    使用插件

    我们在前面介绍了Maven的lifecycle,phase和goal:使用Maven构建项目就是执行lifecycle,执行到指定的phase为止。每个phase会执行自己默认的一个或多个goal。goal是最小任务单元。

    我们以compile这个phase为例,如果执行:

    mvn compile
    

    Maven将执行compile这个phase,这个phase会调用compiler插件执行关联的compiler:compile这个goal。

    实际上,执行每个phase,都是通过某个插件(plugin)来执行的,Maven本身其实并不知道如何执行compile,它只是负责找到对应的compiler插件,然后执行默认的compiler:compile这个goal来完成编译。

    所以,使用Maven,实际上就是配置好需要使用的插件,然后通过phase调用它们。

    Maven已经内置了一些常用的标准插件:

    插件名称 对应执行的phase
    clean clean
    compiler compile
    surefire test
    jar package

    如果标准插件无法满足需求,我们还可以使用自定义插件。使用自定义插件的时候,需要声明。例如,使用maven-shade-plugin可以创建一个可执行的jar,要使用这个插件,需要在pom.xml中声明它:

    <project>
        ...
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-shade-plugin</artifactId>
    				<version>3.2.1</version>
    				<executions>
    					<execution>
    						<phase>package</phase>
    						<goals>
    							<goal>shade</goal>
    						</goals>
    						<configuration>
                                ...
    						</configuration>
    					</execution>
    				</executions>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    

    自定义插件往往需要一些配置,例如,maven-shade-plugin需要指定Java程序的入口,它的配置是:

    <configuration>
        <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>com.itranswarp.learnjava.Main</mainClass>
            </transformer>
        </transformers>
    </configuration>
    

    注意,Maven自带的标准插件例如compiler是无需声明的,只有引入其它的插件才需要声明。

    下面列举了一些常用的插件:

    • maven-shade-plugin:打包所有依赖包并生成可执行jar;
    • cobertura-maven-plugin:生成单元测试覆盖率报告;
    • findbugs-maven-plugin:对Java源码进行静态分析以找出潜在问题。

    模块管理

    在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法:

    对于Maven工程来说,原来是一个大项目:

    现在可以分拆成3个模块:

    模块A和模块B的pom.xml高度相似,因此,我们可以提取出共同部分作为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>
    
        <groupId>com.itranswarp.learnjava</groupId>
        <artifactId>parent</artifactId>
        <version>1.0</version>
        <packaging>pom</packaging>
    
        <name>parent</name>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
            <java.version>11</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.28</version>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>5.5.2</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </project>
    

    注意到parent的<packaging>pom而不是jar,因为parent本身不含任何Java代码。编写parent的pom.xml只是为了在各个模块中减少重复的配置。现在我们的整个工程结构如下:

    这样模块A就可以简化为:

    <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.itranswarp.learnjava</groupId>
            <artifactId>parent</artifactId>
            <version>1.0</version>
            <relativePath>../parent/pom.xml</relativePath>
        </parent>
    
        <artifactId>module-a</artifactId>
        <packaging>jar</packaging>
        <name>module-a</name>
    </project>
    

    模块B、模块C都可以直接从parent继承,大幅简化了pom.xml的编写。

    如果模块A依赖模块B,则模块A需要模块B的jar包才能正常编译,我们需要在模块A中引入模块B:

    <dependencies>
        <dependency>
            <groupId>com.itranswarp.learnjava</groupId>
            <artifactId>module-b</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    

    最后,在编译的时候,需要在根目录创建一个pom.xml统一编译:

    <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>com.itranswarp.learnjava</groupId>
        <artifactId>build</artifactId>
        <version>1.0</version>
        <packaging>pom</packaging>
        <name>build</name>
    
        <modules>
            <module>parent</module>
            <module>module-a</module>
            <module>module-b</module>
            <module>module-c</module>
        </modules>
    </project>
    

    这样,在根目录执行mvn clean package时,Maven根据根目录的pom.xml找到包括parent在内的共4个,一次性全部编译。

    中央仓库

    其实我们使用的大多数第三方模块都是这个用法,例如,我们使用commons logginglog4j这些第三方模块,就是第三方模块的开发者自己把编译好的jar包发布到Maven的中央仓库中。

    私有仓库

    私有仓库是指公司内部如果不希望把源码和jar包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的~/.m2/settings.xml中配置好,使用方式和中央仓位没有任何区别。

    本地仓库

    本地仓库是指把本地开发的项目“发布”在本地,这样其他项目可以通过本地仓库引用它。但是我们不推荐把自己的模块安装到Maven的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。更好的方法是使用模块化编译,在编译的时候,告诉Maven几个模块之间存在依赖关系,需要一块编译,Maven就会自动按依赖顺序编译这些模块。

    使用mvnw

    我们使用Maven时,基本上只会用到mvn这一个命令。有些童鞋可能听说过mvnw,这个是啥?

    mvnw是Maven Wrapper的缩写。因为我们安装Maven时,默认情况下,系统所有项目都会使用全局安装的这个Maven版本。但是,对于某些项目来说,它可能必须使用某个特定的Maven版本,这个时候,就可以使用Maven Wrapper,它可以负责给这个特定的项目安装指定版本的Maven,而其他项目不受影响。

    简单地说,Maven Wrapper就是给一个项目提供一个独立的,指定版本的Maven给它使用。

    安装Maven Wrapper

    安装Maven Wrapper最简单的方式是在项目的根目录(即pom.xml所在的目录)下运行安装命令:

    mvn -N io.takari:maven:0.7.6:wrapper
    

    它会自动使用最新版本的Maven。注意0.7.6是Maven Wrapper的版本。最新的Maven Wrapper版本可以去官方网站查看。

    如果要指定使用的Maven版本,使用下面的安装命令指定版本,例如3.3.3:

    mvn -N io.takari:maven:0.7.6:wrapper -Dmaven=3.3.3
    

    安装后,查看项目结构:

    发现多了mvnwmvnw.cmd.mvn目录,我们只需要把mvn命令改成mvnw就可以使用跟项目关联的Maven。例如:

    mvnw clean package
    

    在Linux或macOS下运行时需要加上./

    ./mvnw clean package
    

    Maven Wrapper的另一个作用是把项目的mvnwmvnw.cmd.mvn提交到版本库中,可以使所有开发人员使用统一的Maven版本。

    发布Arifact

    当我们使用commons-logging这些第三方开源库的时候,我们实际上是通过Maven自动下载它的jar包,并根据其pom.xml解析依赖,自动把相关依赖包都下载后加入到classpath。

    那么问题来了:当我们自己写了一个牛逼的开源库时,非常希望别人也能使用,总不能直接放个jar包的链接让别人下载吧?

    如果我们把自己的开源库放到Maven的repo中,那么,别人只需按标准引用groupId:artifactId:version,即可自动下载jar包以及相关依赖。因此,本节我们介绍如何发布一个库到Maven的repo中。

    把自己的库发布到Maven的repo中有好几种方法,我们介绍3种最常用的方法。

    以静态文件发布

    通过Nexus发布到中央仓库

    发布到私有仓库

  • 相关阅读:
    第 5 章 Nova
    第 5 章 Nova
    第 5 章 Nova
    第 5 章 Nova
    第 5 章 Nova
    第 5 章 Nova
    第 5 章 Nova
    vba:提取字符串中间字符
    vba:根据给定单元格搜索目标值
    vba:合并当前目录下所有工作簿的全部工作表
  • 原文地址:https://www.cnblogs.com/shenleg/p/14223328.html
Copyright © 2020-2023  润新知