maven整理


1. 概念

1.1 官网:

https://maven.apache.org.

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.
  • apache开源项目;
  • Java语言开发(使用时依赖JDK);
  • 基于工程对象模型(pom)概念的项目构建工具。

1.2 两大核心:

  • 依赖管理:对Jar包的统一管理(项目源码非常小);
  • 项目构建:通过命令实现项目测试、编译、打包、安装、部署等(开发中可能使用Eclipse或Idea完成该系列功能);

1.3 maven下载安装

  • 下载:新版本maven依赖1.7及以上JDK; image-20191008145355930

  • 选择文件:选择zip即可; image-20191008145909459

  • 解压:

    sudo unzip apache-maven-3.6.2-bin.zip -d /Library/

  • 安装:https://maven.apache.org/install.html

    • vim ~/.bash_profile
    • 添加:export PATH=${PATH}:/Library/apache-maven-3.6.2/bin,保存;
    • source ~/.bash_profile
    • 验证:mvn -v

    注意:如果使用压缩包安装jdk,确保JAVA_HOME也已配置。

1.4 仓库:

  • 仓库:就是存放Jar包的地方:
    • 中央仓库(https://mvnrepository.com):公网上存放所有jar包(存在版权的例外),联网可下载Jar包;
    • 私服:一般只公司配置的局域网私服;
      • 避免多人重复从中央仓库下载资源;
      • 存放第三方Jar包;
      • 本公司开发代码(Jar)存放;
    • 本地仓库:无论从私服还是中央仓库,打包时都要先将使用的Jar包下载到本地仓库。
  • 配置本地仓库(默认:${user.home}/.m2/repository):
    • 在settings.xml添加:<localRepository>/Users/gp/.m2/repo</localRepository>

1.5 maven项目结构

  • 使用Idea创建maven项目:

  • File —> new —> Project —> maven —> next —> 填写GroupId、ArtifactIdd和Version —> next —> 填写Project Name和Project Location —> Finish

  • maven项目结构: image-20191008180504077 ```
    • src:源码
      • main:源码文件夹
        • java:java类
        • resources:配置文件
      • test:测试文件夹
        • java:测试类
    • pom.xml:核心配置文件 ``` 说明:只要保持该结构,手动创建文件(夹)也可创建maven项目。
  • 编码:在src/main/java文件夹下创建包及编码

    image-20191008181350201

  • 编写测试代码:

    • 在pom.xml中添加Junit依赖:
    	<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
    </dependencies>
    
  • 测试代码:

    image-20191008183802843

2. 项目构建

maven支持通过命令来构建项目。打开终端,在项目的pom.xml所在路径下执行。

  • mvn compile:编译。首次执行时会从中央仓库下载依赖的相关Jar包到配置的本地仓库,最后提示BUILD SUCCESS。查看目录结构,编译后增加了target文件夹:

    image-20191008182426757

    说明:通过查看终端打印信息可以看出,maven命令其实是通过调用相应Jar包完成编译等相应任务的。编译命令不会编译单元测试文件。

  • mvn clean:将target文件夹清除。执行命令后,target文件夹已被删除;

  • mvn test: 单元测试

    image-20191008184110348

    说明:可以看到,执行test命令会先执行compilemvn test会把单元测试方法全部执行(测试类也编译);

    注意:要想通过mvn命令执行单元测试,类名必须以’Test’开头或结尾。

  • mvn package:打包。通过打印信息可以看出,打包命令会先编译,还会执行单元测试,然后在target目录下会生成Jar包。

    image-20191009090344124

  • mvn install:安装,将项目打包并安装到本地仓库。

    image-20191009091301876

    说明:安装会执行编译、测试、打包。如果有修改或更新,编写完代码后直接再次执行mvn install即可覆盖更新。

注意:

  1. 使用mvn命令打包或安装生成的Jar包不会包含pom中的依赖包。可以使用Idea的Build Artifacts打包。也可以使用其它插件完成,如assembly:mvn assembly:assembly

    ...
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
  2. 如果要打包为war包,则在pom.xml中添加<packaging>war</packaging>,并添加相应的web.xml。web项目使用mvn package打包时会包含pom中的依赖包。

  3. 一般通过在pom文件中设置插件的编译版本,否则如Eclipse或Idea默认构建时使用JAVA1.5版本,Eclipse甚至直接报错:

    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

3. 依赖管理

3.1 依赖范围

  • 引入:

    通过Idea新建一个maven web项目,创建包并新建一个Servlet,会报错:

    image-20191009170749443

    原因为依赖的servlet-api包不存在。现在我们通过maven添加一个servlet-api依赖(既然是maven项目就别添加lib了):

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    

    点击提示的Import Changes,报错消失(或者选择Enable Auto-Import)。

    编译没有任何问题,但当我们通过mvn tomcat:run运行项目时,访问servlet会报错:

    image-20191010090727922

    原因为tomcat本身的lib包中是包含servlet-api.jar的(手动将war放入tomcat/apps下运行tomcat未报错,但问题肯定还是存在的):

    image-20191009171030108

    因此为使编译时正常,打包时又不会把不需要的Jar包打入,在pom文件中引入时,需要添加Jar包的依赖范围<scope></scope>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    
  • 依赖范围:

    依赖范围 编译 测试 运行 例子
    compile Y Y Y 大部分包
    test - Y - Junit
    provided Y Y - servlet-api
    runtime - Y Y JDBC驱动(反编译)
    system Y Y - 仓库外的包

    说明:pom文件默认依赖范围为compile。如果一个依赖有scope,那么该依赖包:

    • compile:编译时有效(可以import),测试时(src/test下类)有效,打包时会打入依赖包;
    • test:编译时无法使用(找不到,相当于没有引入),测试时有效,运行时不会打包;
    • provided:编译时有效,测试时有效,不会打包;

3.2 依赖传递

  • 引入

    如在maven中添加struts2依赖包,则只需要在pom文件中添加struts2-core,struts2-core本身依赖的包会自动添加到项目:

    ...
    <dependency>
        <groupId>org.apache.struts</groupId>
        <artifactId>struts2-core</artifactId>
        <version>2.3.24</version>
    </dependency>
    ....
    

    image-20191010131614469

  • 概念:

    • 直接依赖:A(项目)的pom文件中直接依赖B,则B为A的直接依赖;
    • 传递依赖:如果B本身右依赖相关Jar包C,则C称为A的传递依赖;
  • 问题:

    • 传递依赖冲突:如果A直接依赖B和D,而B和D都依赖C,但它们依赖C的版本不同,此时会引起依赖冲突问题。
  • 依赖冲突解决:

    • maven自己处理原则

      • 第一优先原则:A先依赖谁,则以谁的依赖为主。如pom文件中D在B前面,则C的版本为D依赖的版本;
      • 路径近优先:如果在A的pom文件中直接依赖C,则以该直接依赖版本为主,不再依赖传递依赖;
    • 排除依赖:手动将A的直接依赖B下面传递依赖中不想要的排除;

      ...
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>4.2.4.RELEASE</version>
        	<!-- 手动将不想要的依赖排除 -->
          <exclusions>
              <exclusion>
                  <artifactId>spring-beans</artifactId>
                  <groupId>org.springframework</groupId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-beans</artifactId>
          <version>5.1.9.RELEASE</version>
      </dependency>
      ...
      
    • 版本锁定(推荐):直接通过<dependencyManagement>节点控制版本(该节点只控制版本,本身不会导入依赖)。

      ...
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-beans</artifactId>
                  <version>5.1.9.RELEASE</version>
              </dependency>
          </dependencies>
      </dependencyManagement>
      ...
      

      说明:版本信息可以先通过<properties>节点定义,使用时通过ognl表达式引用,这也可以统一控制:

      ...
      <properties>
      	<spring.beans.version>4.2.4.RELEASE</spring.beans.version>
      </properties>
          
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-beans</artifactId>
                  <version>${spring.beans.version}</version>
              </dependency>
          </dependencies>
      </dependencyManagement>
      ...
      

      注意:实际开发中最好使依赖之间版本统一,或者确保传递依赖版本不影响使用,否则可能出现问题。因为理论上版本不一致是存在冲突的:

      image-20191010135834967

3.3 依赖传递范围

依赖传递并不会无休止传递,通过依赖范围控制传递的范围。

  compile provided runtime test
compile compile - runtime -
provided provided provided provided -
runtime runtime - runtime -
test test - test -

说明:纵坐标为直接依赖(B对A的范围),横坐标为传递依赖(C对B的范围)。如B中C的范围为test时,若A直接依赖B,则并不会引入C。