Maven必知必会

了解Maven

Maven(Apache Maven)是一个用于项目管理和构建的开源工具。

Maven 的作用:

  • 项目构建:Maven 可以自动执行项目的构建过程,包括编译源代码、运行单元测试、打包生成可部署的产物等。
  • 依赖管理: Maven 负责管理项目的依赖关系,通过在项目的 POM 文件中声明依赖,Maven 可以自动下载并配置所需的库或框架。这简化了项目的配置和部署过程,并确保了项目的依赖关系是一致的。
  • 多模块项目支持 : Maven 支持多模块项目,允许将一个大型项目划分为多个模块,每个模块可以独立构建和测试。这种模块化的结构有助于提高项目的可维护性和可扩展性。
  • 插件体系结构: Maven 的插件体系结构允许开发人员编写自定义的插件,以满足特定项目的需求。
  • 仓库管理: Maven 使用本地仓库和远程仓库来管理项目的构建产物和依赖库。本地仓库用于存储本机构建的产物,而远程仓库用于获取和发布构建产物。

其他的比如版本管理项目报告等一般用的不多,版本管理一般都会选择Git

苹果(macOS)安装Maven 比较简单,使用brew 安装管理就好。

brew install maven

一、依赖

1.1 依赖管理

Maven 使用 pom.xml 文件进行依赖配置和管理

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0</version>    
    
    <!-- 项目依赖 -->
    <dependencies>
        <!-- 例:JUnit 依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

1.2.1 Maven 坐标

Maven 坐标(Coordinates)是用于唯一标识 Maven 项目的元数据,它包含三个关键信息:groupId、artifactId 和 version。

  • groupId(组织或包名):代表项目所属的组织或包名,用于组织 Maven 仓库中的项目。
    命名规则通常是反转的域名(或者组织的标识符),例如 com.example。
    建议使用全球唯一的标识符,以避免命名冲突。
  • artifactId(项目名称):代表项目的名称,用于唯一标识 Maven 仓库中的构建产物(例如 JAR 文件)。
    命名规则通常是项目的简短名称,例如 my-project。
  • version(版本号):代表项目的版本号,用于标识项目构建的不同版本。
    命名规则通常是 X.Y.Z 形式,例如 1.0.0。其中,X 是主版本号,Y 是次版本号,Z 是修订版本号。

备注: 个人建议 groupId 的命名规则是 反转的域名+项目名称, artifactId 的命名规则是 公司名称 + 项目名称
举个例子:

<dependency>
    <groupId>com.olym.jdbc</groupId>
    <artifactId>olym-jdbc</artifactId>
    <version>1.8.0</version>
</dependency>

打包后的依赖名称是 olym-jdbc-1.8.0.jar,更加清晰。

1.2.2 Maven 依赖的作用域 (Scope)

Maven 依赖的作用域(Scope)定义了依赖在不同阶段的生命周期中的可见性和有效性。不同的作用域用于不同的情景,以满足项目的需求。以下是 Maven 中常见的依赖作用域:

作用域描述用途
compile默认作用域,适用于所有阶段。依赖在编译、测试、运行时都可见。
provided适用于编译和测试阶段,但不会包含在最终打包的产物中。依赖在运行时由容器或环境提供。
runtime适用于运行时和测试阶段,但不参与编译。依赖在运行时可见,但在编译时不需要。
test仅适用于测试阶段,不参与编译和运行时。依赖仅在测试代码中可见。
systemprovided 类似,需要显式提供路径指定本地 JAR 文件的位置。不推荐使用,容易导致构建不可移植性。
import仅适用于 <dependencyManagement> 部分,用于管理依赖的版本。不参与实际的构建,仅用于管理版本。

重点提示
system 作用域的要格外注意,system 作用域的依赖在打包(mvn package)时不会包含在项目的构建产物中。这是因为 system 作用域通常用于引入本地系统中的 JAR 文件,而不是从 Maven 仓库中解析。

举个例子:

<dependency>
     <groupId>com.olym.jdbc</groupId>
     <artifactId>olym-jdbc</artifactId>
     <version>1.8.0</version>
     <scope>system</scope>
     <systemPath>${project.basedir}/libs/olym-jdbc-1.8.0-GA-pg.jar</systemPath>
 </dependency>

这个依赖是 system ,在IDEA运行时没有任何异常,但是当打包后,运行时就会抛出 ClassNotFoundException,查看程序包时,里面没有包含该依赖。

如何解决 system 打包问题?

  1. 安装依赖到本地库 (不太推荐)
mvn install:install-file \
  -Dfile=path/to/your/artifact.jar \
  -DgroupId=your.groupId \
  -DartifactId=your-artifactId \
  -Dversion=1.0 \
  -Dpackaging=jar
  • -Dfile: 指定要安装的 JAR 文件的路径。
  • -DgroupId: 指定项目的 Group ID。
  • -DartifactId: 指定项目的 Artifact ID。
  • -Dversion: 指定项目的版本号。
  • -Dpackaging: 指定 JAR 文件的打包类型,这里是 jar。
  1. 将依赖放到私库进行管理 (推荐) 使用Nexus搭建个人仓库

1.2.3 传递性依赖 (概念性很强)

传递性依赖指的是项目依赖关系中的间接依赖。当项目 A 依赖于项目 B,而项目 B 依赖于项目 C,那么项目 A 与项目 C 之间就存在一种传递性依赖关系。

Maven 在处理传递性依赖时会自动解析和管理这些依赖关系,确保项目构建时所需的所有依赖都被正确地引入。当你在项目 A 中运行 Maven 构建时,Maven 会自动下载项目 B 和项目 C 的相关 JAR 文件,并将它们添加到项目 A 的类路径中。

1.2.4 依赖调解

因为 传递性依赖 的存在,大多数情况下,开发者不需要关心依赖,但可能存在问题,传递性依赖可能存在冲突,也就是说Maven没有选对传递性依赖,造成依赖冲突,某些功能无法使用。

Maven 依赖调解的规则:

  • 第一原则: 路径最近者优先。 比如 A -> B -> C -> X(1.0)、 A -> D -> X(2.0),那么会使用X(2.0)。
  • 第二原则: 如果路径相同,那么在 pom.xml 文件中依赖顺序决定了谁被使用。

可以通过排除依赖,解决依赖冲突,例如:

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>projectB</artifactId>
        <version>1.0</version>
        <!-- 排除传递性依赖项 org.unwanted:unwanted-artifact -->
        <exclusions>
            <exclusion>
                <groupId>org.unwanted</groupId>
                <artifactId>unwanted-artifact</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

备注: 不需要声明版本信息。

二、项目构建和插件

Maven 的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的构建形式存在。
Maven插件官网链接

2.1 生命周期

Maven 生命周期定义了一个项目的构建过程,并将它划分为不同的阶段(phase)。 简单的一个例子,命令mvn clean 就是一个生命周期。

  1. Clean Lifecycle(清理生命周期):

    • clean: 清理项目,删除 target 目录及其内容。
  2. Default Lifecycle(默认生命周期):

    • validate: 验证项目是否正确且所有必要的信息都可用。
    • compile: 编译项目的源代码。
    • test: 使用适当的测试框架运行测试。这些测试不要求代码被打包或部署。
    • package: 接受编译的代码,并将其打包成可分发的格式,如 JAR。
    • verify: 运行所有检查,验证包是否有效且达到质量标准。
    • install: 将包安装到本地存储库,以供其他项目本地使用。
    • deploy: 将最终的包复制到远程存储库,以便共享和部署。
  3. Site Lifecycle(站点生命周期):

    • pre-site: 在生成站点文档之前执行的工作。
    • site: 生成项目的站点文档。
    • post-site: 在生成站点文档之后执行的工作,并且为部署准备。
    • site-deploy: 将生成的站点文档部署到服务器上。

2.2 构建和插件

Maven 中大概使用了类似 Facade 模式,举个例子 mvn clean 运行可以理解为一个接口,实际是使用插件实现的。

生命周期和插件的关系,如下:

阶段插件目标执行任务
validate-验证项目是否正确且所有必要的信息可用
initialize-初始化构建状态
generate-sources-生成源代码
process-sources-处理源代码,如过滤资源文件
generate-resources-生成资源文件
process-resourcesmaven-resources-plugin:resources处理资源文件,如拷贝资源到输出目录
compilemaven-compiler-plugin:compile编译项目的源代码
process-classes-处理编译后的类文件
generate-test-sources-生成测试源代码
process-test-sources-处理测试源代码
generate-test-resources-生成测试资源文件
process-test-resourcesmaven-resources-plugin:testResources处理测试资源文件
test-compilemaven-compiler-plugin:testCompile编译测试源代码
process-test-classes-处理编译后的测试类文件
testmaven-surefire-plugin:test运行单元测试
prepare-package-准备打包阶段
packagemaven-jar-plugin:jar将编译后的代码打包成可分发的格式
pre-integration-test-在集成测试之前执行
integration-testmaven-surefire-plugin:integration-test运行集成测试
post-integration-test-在集成测试之后执行
verify-对集成测试的结果进行验证
installmaven-install-plugin:install将构建的项目副本安装到本地仓库
deploymaven-deploy-plugin:deploy将构建的项目副本部署到远程仓库

2.2.1 实战-构建时依赖单独打包

如果默认<build>,执行 mvn clean package -DskipTests 会将程序代码、依赖、配置文件同时打包进入到一个服务jar包内,依赖jar包基本上占用的空间会非常大,对于部署运行影响还是很大的,所以将依赖和代码分离,是很好的一个主意。

主要用到了 maven-dependency-plugin 插件和 maven-jar-plugin 插件,配置如下:

<build>
<!--           打包时将依赖 jar 放到 lib目录下-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.2.0</version> <!-- 替换为你需要的版本号 -->
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- 指定依赖项的输出目录 -->
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <!-- 排除不需要的依赖项 -->
<!--                            <excludeArtifactIds>unwanted-artifact</excludeArtifactIds>-->
                        </configuration>
                    </execution>
                </executions>
            </plugin>
<!--        打包时,应用jar包内不包括依赖jar-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <!-- 类路径信息 -->
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.example.fancoding.web.FancodingWebApplication</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

上面的配置会有两个效果:

  1. 构建时,依赖jar包会统一放到 /lib目录下
  2. 构建时,服务jar包中会有MANIFEST.MF 文件,文件中设置类路径信息以及主类。

文件目录参考如下:
在这里插入图片描述
MANIFEST.MF 文件参考:

在这里插入图片描述
备注: 除了首次部署或依赖变更时,需要将/lib 上传到服务器,其他时候就可以单独部署服务jar包。如果使用上面的例子,那么lib目录需要和服务jar包在同一目录,启动也不会有任何影响 java -jar fancoding.jar

扩展一下
之前有个安全需求,由第三方提供JDBC替换换原来的驱动,在不需要修改代码的前提下,那么我直接上传依赖jar包到lib目录,修改MANIFEST.MF 增加依赖就可以了,不需要重新打包,是不是很方便。

2.2.1 实战-构建时根据不同的环境配置文件进行打包

一般的服务都会有开发、测试、灰度和生产等环境,打包时需要不同的配置文件,如果使用spring.profiles.active去控制,那么每次打包前都需要修改配置;另一种方式将配置文件在服务器上部署 /config 也是不错的主意。但是使用Maven 可以更好构建。

配置文件参考如下:

<!--    多配置文件管理-->
<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <environment>dev</environment>
        </properties>
        <activation>
            <activeByDefault>true</activeByDefault> <!-- 默认激活的Profile -->
        </activation>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <environment>prod</environment>
        </properties>
    </profile>
</profiles>
<build>
<!--        定义打包的名称-->
        <finalName>fancoding</finalName>

<!--        定义打包的配置文件引入-->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>

            <resource>
                <directory>src/main/resources-env/${environment}</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
</build>                

构建时,执行 mvn clean install -P prod

  • -P: 你配置的环境id

三、仓库 (了解即可)

如果需要搭建自己搭建私有仓库和使用,可以参考 使用Nexus搭建个人仓库

Maven 仓库是存储 Maven 构建项目所需依赖项(JAR 文件、插件、父项目等)的地方。它是一个目录结构,按照 Maven 的约定,有两种主要类型的仓库:本地仓库和远程仓库。

  1. 本地仓库(Local Repository):

    本地仓库是位于开发者本地机器上的一个目录,用于存储下载的依赖项。Maven 在首次构建项目时,会从远程仓库下载所需的依赖项,并将它们缓存到本地仓库中。之后的构建过程将重复使用本地仓库中的缓存,避免重复下载相同的依赖项。

    默认情况下,本地仓库位于用户的家目录下的 .m2/repository 目录(Windows 下为 C:\Users\YourUsername\.m2\repository,Linux 下为 /home/YourUsername/.m2/repository)。

  2. 远程仓库(Remote Repository):

    远程仓库是位于网络上的一个仓库,用于存储 Maven 中央仓库或其他公共或私有仓库的依赖项。当 Maven 构建项目时,它会从远程仓库下载缺失的依赖项。Maven 中央仓库是 Maven 默认的远程仓库,包含了大量的开源项目的依赖项。

    除了 Maven 中央仓库,还可以配置其他远程仓库,例如企业内部的私有仓库或其他公共的 Maven 仓库。这样,项目可以从这些远程仓库获取依赖项。

仓库的分类可以按照其可访问性和管理者的不同来进行划分:

  • 本地仓库(Local Repository): 存储在本地开发者机器上的仓库。

  • 远程仓库(Remote Repository):

    • 中央仓库(Central Repository): Maven 默认的远程仓库,包含了大量的开源项目的依赖项。
    • 其他公共仓库: 包括其他开源仓库,可以通过 Maven 的配置文件指定。
    • 私有仓库(Private Repository): 企业内部搭建的私有仓库,用于存储自己的项目和依赖项。

四、多模块项目支持

虽然现在好像微服务十分流行,但是一般的中小公司或者项目规模级别并不大的前提下,项目模块化更常见,毕竟成本更低。
在 Maven 中,模块化通常指的是将一个大型项目拆分成多个独立的、可重用的子模块,每个子模块有自己的 pom.xml 文件和项目结构。

    project-root/
    ├── pom.xml (父项目的 pom 文件)
    ├── module1/
    │   └── pom.xml (子模块1的 pom 文件)
    ├── module2/
    │   └── pom.xml (子模块2的 pom 文件)
    └── ...

4.1 模块化管理

在 Maven 中,模块化是指将一个大型项目拆分成多个独立的、可重用的子模块,每个子模块有自己的 pom.xml 文件和项目结构。Maven 提供了一种机制来管理多模块项目,允许你将这些子模块组织在一起,以便更好地管理和构建项目。

4.1.1 父项目

一般的父项目没有 /src目录,并且一般只有 /doc、和 pom.xml ,前者用来保存项目相关的文档,比如需求文档、表设计文档、网络工单等和项目相关的文档,后者用来统一管理模块的依赖和插件。

<?xml version="1.0" encoding="UTF-8"?>
<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>

<!--  表示它是一个聚合项目  -->
    <packaging>pom</packaging>
    
 <!-- 子模块 -->
    <modules>
        <module>fancoding-web</module>
    </modules>

    <groupId>com.example</groupId>
    <artifactId>fancodig</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <spring-boot.version>2.6.13</spring-boot.version>
        <knife4j.version>4.0.0</knife4j.version>
    </properties>

<!--    集中管理项目中所有模块的依赖版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
                <version>${knife4j.version}</version>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>     
    </dependencyManagement>
</project>
  • dependencyManagement :用于集中管理项目的依赖版本号。当你在 Maven 项目中有多个模块时,dependencyManagement 元素可以帮助你在一个父 POM 文件中集中定义和管理依赖的版本,而子模块只需引用这些依赖而无需指定版本号。

4.1.1 子模块

在父项目的目录下创建每个子模块,并在每个子模块的 pom.xml 文件中定义子模块的配置。

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>


 <!-- 指定父项目的坐标 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>fancodig</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

<!-- 子项目的坐标 -->
    <groupId>com.example.fancoding</groupId>
    <artifactId>web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>fancoding-web</name>
    <description>fancoding-web</description>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

Maven的生命周期如下:
在这里插入图片描述

4.1.3 实战

网上做的比较好的开源项目,模块化一把都会拆分成 webservicecoreapi,可能还会有 ui等。我个人是比较喜欢这种拆分方式的。

 fancoding/
    ├── pom.xml (父项目的 pom 文件)
    ├── fancoding-web/
    │   └── pom.xml (子模块1的 pom 文件)
    ├── fancoding-service/
    │   └── pom.xml (子模块2的 pom 文件)
    └── fancoding-core/
    │   └── pom.xml (子模块3的 pom 文件)
    └── fancoding-api/
	 │   └── pom.xml (子模块4的 pom 文件) 
  • web/:Controller层代码,过滤器(Filter)、拦截器(Interceptor)、监听器(Listener)等相关的代码我也会放到这里,比如 接口请求日志过滤。
  • service/ :后端业务逻辑的核心代码,一般是开放服务接口给 web 使用,避免 web 层直接使用 dao层。
  • core/:一些常用的配置信息、常量、工具类等代码。
  • api/ : 一般放些VO、DTO、DO等代码。

可能刚入门时候,比较熟悉的名词是 MVC

  • MVC(Model-View-Controller)是一种经典的软件设计模式,用于组织和分离应用程序的各个组件。

后面可能有 分布式微服务中台等各种高大上的名词。
其实本质还是一样的,使得代码更易于维护、扩展和测试

比如我工作过的项目,一个系统拆分成了 门户系统、沙盒系统、数据资产等多个系统和模块(很乱),前任选择了将一个系统拆分成了所谓的微服务,独立运行,但是启动门户系统时,其他服务没有启动就会报错,这。。。就很尴尬了。

五、常用的Maven指令

  1. mvn clean package -DskipTests : 跳过测试,清理打包
  2. mvn spring-boot:run 在 Spring Boot 项目中运行应用程序。
  3. mvn dependency:tree 打印项目的依赖关系树,显示所有直接和传递性依赖。
  4. mvn dependency:list 用于列出项目的所有直接依赖项及其版本信息。这个命令可以帮助开发者快速查看项目中使用的依赖项,以及它们的版本。
  5. mvn dependency:analyze 用于分析项目的依赖关系,检测潜在的问题,如过期的依赖项、未使用的依赖项等。
  6. mvn clean source:jar -Dmaven.compiler.skip=true:打包源码(不进行编译)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值