Maven 的爱恨情仇 | Java Debug 笔记

1,063 阅读12分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看<活动链接>

前言

在如今的互联网项目开发当中,特别是 Java 开发中,可以说 Maven是随处可见。Maven 的仓库管理、依赖管理、继承和聚合等特性为项目的构建提供了一整套完善的解决方案,可以说如果你搞不懂 Maven,那么一个多模块的项目足以让你头疼,依赖冲突就会让你不知所措,甚至搞不清楚项目是如何运行起来的。相信使用过Maven 的人,一定曾经被 Maven 伤害过,但又不得不去让它来伤害,谁让它能给项目的构建提供便利呢。

最近在微信群中,不断有人在使用 Maven 构建项目时,遇到了各种问题。一些问题也是大家经常遇见的,在此,博主就 Maven 的爱恨情仇,来说道说道,使用时能够得心应手。  

为什么要使用Maven

通常在一个项目中,我们会使用一些第三方类库,来提高开发速度,而不是闭门造车,毕竟在当今软件飞速发展的潮流下,不断涌现、开源出一些优秀的类库,供咱们灵活使用。在未使用Maven时,通常需要在项目中建立一个lib目录,在其中放着项目所依赖的各种类库,这样提交到SVN或GIT之后, 每个开发人员检出项目到本地,这样所有开发人员就会拥有统一的依赖类库。 但这样务必面临着下面这些问题:

1、依赖冗余,浪费空间

随着公司项目的变大、变多,模块的增多,这种方式就会面临一些问题。不同项目,不同模块都可能会引用相同的依赖。当每个模块都把自己的依赖提交到SVN或GIT,那么相同的依赖就会占用服务器 SVNGITRepository 很大的空间,造成空间浪费。 

2、版本问题

如果一个项目中依赖的版本和另一个项目依赖的版本不一致。比如这个项目依赖spring-boot-starter-parent-1.5.9.RELEASE,而另一个可能依赖 spring-boot-starter-parent-2.0.0.RELEASE, 当合并两个项目发布的时候,可能因为这种依赖类库详细版本信息的缺失,造成版本混乱冲突等问题。

3、管理问题

随着项目的延续,项目依赖的类库可能需要更新,这时就需要不断从网上或通过其他途径,来替换 lib 目录下依赖的类库jar包文件,给依赖类库的管理带来了不便。

为了解决上面依赖类库管理过程中出现的问题,我们需要寻求另一种依赖管理方式,即:一种集中式的依赖管理方式。各个项目只要通过统一的依赖描述文件(pom.xml)来指定自己需要的依赖就可以,而不用自己来管理真正的依赖库,因为所有的项目都使用了同一个中央依赖库(中央仓库), 所以即使各个项目中有相同的依赖, 也不会出现依赖冗余的问题。 在依赖类库需要升级时,只需修改 pom.xml 即可方便升级更新。这种新的依赖管理方式,则是 Maven,是基于POM 的一款进行项目依赖管理,构建管理和项目信息管理的工具。

回想一下,当你新到一家公司或新进入一个项目,安装完JDK后就会安装配置 Maven,或许需要修改 settings.xml 文件,比如你可能会从其他人那里 copy 一段配置到你的settings.xml中(私服的一些配置)。接下来,你会到 IDEA 或者Eclipse 中进行 Maven 插件配置,然后你就可以在工程中的 pom.xml 里面开始添加、修改 <dependency> 标签来管理 jar 包,在 Maven 规范的目录结构下进行编写代码,最后你会通过插件的方式来进行测试、打包、部署、运行。

常见问题

上面讲述了我们为什么使用 Maven,什么时候使用它,下面就开始看看它的一些使用方法、常见问题:

1、本地仓库?Maven到底有哪些仓库?它们什么关系?

  本地仓库路径配置:

<localRepository>E:/.m2/repository</localRepository>

你要的依赖jar包,不可能每次都要从互联网去下载,特别是有的公司或项目根本就没有外网的情况,多费劲,所以本地仓库就是相当于加了一层jar包缓存,先到这里来查。如果这里查不到,那么就去私服上找,如果私服也找不到,那么去中央仓库去找,找到jar后,会把jar的信息同步到私服和本地仓库中。

私服: 就是公司内局域网的一台服务器而已,当你的工程 Project-A 依赖别人的 Project-B 的接口,怎么做呢?没有 Maven 的时候,当然是copy Project-B jar到你的本地 lib 中引入,那么Maven 的方式,很显然需要其他人把 Project-B deploy 到私服仓库中供你使用。因此私服中存储了本公司的内部专用的 jar,不仅如此,私服还充当了中央仓库的镜像,说白了就是一个仓库代理!

中央仓库: 该仓库位于互联网上,由Maven团队来维护,地址是http://repo1.maven.org/maven2/。此外,像阿里也对外提供了中央仓库,地址是http://maven.aliyun.com/nexus/content/groups/public,下载速度比 Maven 的还快,推荐使用。

2、关于 <dependency> 的使用

pom.xml依赖

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.0.0.RELEASE</version>
</dependency>

从上图可以看出,通过 groupIdartifactIdversion,就可以在仓库中查找到依赖jar的位置。

一般而言,我们可以到私服上输入 artifactId 进行搜索,或者到http://search.maven.org/http://mvnrepository.com/ 上进行查找确定依赖 jar

version 分为开发版本(Snapshot)和发布版本(Release),那么为什么要分呢?

在实际开发中,我们经常遇到这样的场景,比如A服务依赖于B服务,A和B同时开发,B在开发中发现了 BUG,修改后,将版本由 1.0 升级为 2.0,那么A必须也跟着在 POM.XML 中进行版本升级。过了几天后,B又发现了问题,进行修改后升级版本发布,然后通知A进行升级...可以说这是开发过程中的版本不稳定导致了这样的问题。

Maven,已经替我们想好了解决方案,就是使用Snapshot 版本,在开发过程中B发布的版本标志为 Snapshot 版本,A进行依赖的时候选择 Snapshot 版本,那么每次B发布的话,会在私服仓库中,形成带有时间戳的Snapshot版本,而A构建的时候会自动下载B最新时间戳的 Snapshot 版本!

3、pom.xml进行了依赖配置,本地仓库已经下载下来了,为什么还会出现依赖冲突?

明明已经在 pom.xml 中进行了依赖配置,检查本地仓库发现依赖包已经存在,却突然提示依赖冲突或某个类找不到。此时可以采取把本地仓库中对应的依赖清空,重新下载即可。

4、引入依赖的最佳实践,提前发现问题!

在项目工程中,我们避免不了不断增加一些依赖,也许加了依赖之后运行时才发现存在依赖冲突再去解决,似乎有点晚!那么能不能提前发现问题呢?

如果我们新加入一个依赖的话,那么先通过 mvn dependency:tree 命令形成依赖树,看看我们新加入的依赖,是否存在传递依赖,传递依赖中是否和依赖树中的版本存在冲突,如果存在多个版本冲突,再逐步解决!

5、Maven 规范化目录结构

上图为 Maven 项目的规范目录结构。

需要有下面两点注意:

  • src/main 下内容最终会打包到 Jar/War 中,而 src/test 下是测试内容,并不会打包进去。

  • src/main/resources 中的资源文件会COPY至目标目录,这是 Maven 的默认生命周期中的一个规定动作。(想一想,hibernate/mybatis 的映射 XML 需要放入 resources 下,而不能在放在其他地方了)

6、Maven 的生命周期

如上图所示Maven的生命周期包括:cleanvalidatecompiletestpackageverifyinstallsitedeploy,其中需要注意的是:执行后面的命令时,前面的命令自动得到执行,(其中,也可以跳过其中的步骤,如:test)。

  • clean:清理。有问题,多清理!
  • validate:验证。验证项目是否正确。
  • compile:编译。执行编译,源代码编译在此阶段完成。
  • test:测试。使用适当的单元测试框架(例如 JUnit)运行测试。
  • package:打包。打成 jar 或 war 包,其中会自动进行 clean+compile
  • verify:检查。对集成测试的结果进行检查,以保证质量达标。
  • install:安装。将本地工程 jar 包上传安装到本地仓库,以供其他项目使用。
  • site:站点。进行站点部署。
  • deploy:部署。拷贝最终的工程包到远程仓库或私服中,以共享给其他开发人员和工程。

7、关于scope依赖范围

Maven 的生命周期存在编译、测试、打包这些过程,其中有些依赖只是用于测试,如 junit,有些依赖编译时是用不到的,只有运行的时候才能用到,比如 mysql 的驱动包在编译期就用不到(编译期用的是 JDBC 接口),而是在运行时用到的。还有些依赖,编译期要用到,而运行期不需要提供,因为有些容器已经提供了,比如servlet-apitomcat 中已经提供了,我们只需要的是编译期提供而已。

其中 scope 就可以解决上面的问题,即:scope 参数用来控制打包的时机,scope 有如下几个值,分别代表如下:

  • compile:默认的scope,运行期有效,需要打入包中。
  • provided:编译期有效,运行期不需要提供,不会打入包中。
  • runtime:编译不需要,在运行期有效,需要导入包中。(接口与实现分离)
  • test:测试需要,不会打入包中。
  • system:非本地仓库引入、存在系统的某个路径下的 jar。(一般不使用)

8、编译时,出现类似“源值1.5已过时,将在未来所有发行版中删除”的错误

在编译项目时,如出现如下类似的错误:

Warning:java: 源值1.5已过时, 将在未来所有发行版中删除
Warning:java: 目标值1.5已过时, 将在未来所有发行版中删除
Warning:java: 要隐藏有关已过时选项的警告, 请使用 -Xlint:-options

这是由于在 IDEA 中使用 Maven 编译的时候,项目源和目标都使用了 JDK 1.5的来编译,但是目前我们又没装 JDK1.5(实际上我们安装的是JDK1.7以上的版本),最后还是用了我们自己装的版本来编译,因此编译还是不能通过,就出现了刚刚这种错误!

完整解决方法下面三种:

第一种:将 IDEA中对应的项目的:【Modules->Language Level】为 ”8”

  • IDEA 中打开项目设置(或者按下【Ctrl + Alt + Shift + S】)
  • 找到 Modules,找到对应的项目
  • 将【Language Level】下拉菜单的值改为 "8"

第二种:配置 Maven的配置文件,将编译插件用的 JDK 改为 1.8

  • 打开settings.xml
  • 找到 <profiles>...</profiles> 标签对,并在标签对中间加上如下代码:
<profile>
      <id>jdk-1.8</id>
      <activation>
          <activeByDefault>true</activeByDefault>
          <jdk>1.8</jdk>
      </activation>
      <properties>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
      </properties>
  </profile>
  • 启用该 profile 设置,找到 <activeProfiles>...</activeProfiles> 标签对,在中间加入:
  <activeProfile>jdk-1.8</activeProfile> 

表示启用该 profile 的配置。

第三种:在项目的 pom.xml 中加入

<properties>
   <maven.compiler.source>1.8</maven.compiler.source>
   <maven.compiler.target>1.8</maven.compiler.target>
 </properties>

9、报错 “Usage of API documented as @since 1.8+”

代码中如果出现下面的错误提示:

Usage of API documented as @since 1.8+ 
This inspection finds all usages of methods that have @since tag in their documentation. 
This may be useful when development is performed under newer SDK version as the target platform for production.

出现该问题的原因是由于我们的代码中使用了 JAVA8 的新特性,但是 Language Level(最低可支持的版本)比较低,无法支持这些特性。比如设置的 Language Level6.0,可是却使用了 8.0/9.0 的新特性,6.0 无法解析这些特性,因此IDE会报错来提醒我们。

pom.xml 中添加如下配置,就可以解决啦。

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

10、Spring BootSpring Cloud 项目中如何解决各依赖包间相互冲突的问题?

在微服务项目中,我们会在引入很多依赖包,而且不同依赖包存在很多的版本,经常发现引入的版本不合适,很容易造成一些包中的类相互冲突,如:数据库方面循环调用某个类等,这类问题通常是很难解决的。费好大劲,查询官方网站资料会发现,是由于依赖的某个包的版本不对造成的,你不得不按照要求修改版本号。

在此, 我告诉大家一个方法可以避免这种问题。可通过 Spring Initializr 在线(https://start.spring.io/)创建你的项目,从上面可以选择你需要的功能模块,会自动给你匹配对应各个版本,非常方便。你可以不使用它自动生成的 demo 项目,但可以参考使用它自动生成的 pom.xml 文件,从中获取对应依赖包的版本。这样就完全可以避免了因选择的版本不对,而造成的一些冲突问题。

Maven 的爱恨情仇,今天就分享到这里,如果你还遇到过其他问题,可以留言一起谈论分享。

参考:

1.maven.apache.org/guides/intr…

2.www.runoob.com/maven/maven…

3.www.cnblogs.com/wangyonghao…