项目依赖的概念?

我们任何一个人都是站在巨人的肩膀上。
我们开发的项目也是基于前人的经验。“不要重复造轮子”意思是不要重复的去做别人已经已经做过的事情。
所以我们的项目会使用别人做好的工具包或者框架。比如,Gson,Druid等。这些第三方的代码包我们称为依赖。

这里建议大家了解下一些常用的(大家都知道的)工具类库,帮助提升你的编码效率 apache commons系列 特别是commons-lang https://blog.csdn.net/f641385712/article/details/82468927 hutool https://www.hutool.cn/docs/#/ google guava http://ifeve.com/google-guava/

依赖与传递依赖

现在假设我们的项目 woniushop 依赖了 Druid,请问Druid会否依赖其他的工具或者框架呢?答案是肯定的。那么woniushop和Druid会有多少个这类的依赖呢?答案是很多个。所以一个项目的依赖关系就如同一棵树一样,具备层级关系。事实上,我们确实把依赖的整体关系称为依赖树。

  • WoniuShop
    • Druid
      • commons
      • fastjson
    • Spring
      • javasist
      • ….
    • AspectJ
    • woniumagic

解释下,woniushop依赖了druid,druid依赖了commons,那么显然woniushop在运行时是需要commons的代码才可以的,所以woniushop间接也依赖了commons。也就是说druid将commons也带进了woniushop的依赖树里。我们把这种关系叫做传递依赖,因为druid的依赖也被传递进了woniushop的依赖。所以,如果要成功运行woniushop,你需要将woniushop所使用到的依赖包都加到classpath中,也必须将这些依赖的依赖加入到classpath中。如果没有工具来管理它,这个工作就比较繁琐了。你需要从woniusho的各个依赖的官网下载jar包,并且又去找到他们的依赖的官网再去下载jar包,并且不能缺一个,也不能有版本上的错误。所以没有依赖管理工具的开发方式是弄一套出来,然后代代相传。

版本冲突

需要解释另一个概念,版本。版本是什么?
版本是某个时间点上的代码。一旦版本发布,代码就固定了。版本升级,代码就会变化。一般版本升级会对代码做哪些操作呢?

  • 增加功能或者修复bug,代码增加
  • 删除功能,比如废弃老旧的功能,代码减少
  • 更改功能,有可能更改包名

所以版本升级可能是兼容老版本,也可能不兼容老版本。假设一个项目 woniumagic是蜗牛开发团队的开发神器工具包。有可能 woniumagic 2.0 和woniumagic1.0是不兼容的。如果不兼容,会出现什么问题呢?轻则编译报错,重则运行时代码逻辑出错。
显然我们不希望这种情况出现,所以一般不会轻易升级依赖的版本。
但是结合上面所说的依赖树的情况,现实中版本冲突的问题仍然存在。比如

  • Woniushop v0.1
    • Druid v1.0
      • commons v1.0
      • fastjson v2.0
    • Spring v1.0
      • javasist v0.5
    • woniumagic v1.0
      • commons-v3.0

这里有commons的两个版本,是不是就出现了版本冲突的问题呢?如果两个版本不兼容,那么druid和woniumagic中间必然有一个会出现编译问题。
这是依赖管理的另一个问题就是版本冲突的问题。

所以,到这里,大家应该留意到我们需要一个管理依赖的工具。这款工具就是Maven
Maven精讲-可能是最详细的maven教程 - 图1

Maven定义

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 Maven是一个软件(特别是Java软件)项目管理自动构建工具,由Apache软件基金会所提供。基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。

所以这里提到,maven是一个项目管理及自动构建工具。项目管理最重要的就是依赖的管理,而自动构建指的是另一个话题。稍后再讲解。

刚才的依赖问题,maven是怎么解决的呢?
首先maven对项目结构做了些规范化的处理,并且定义了一个叫做POM的文件来管理所有的依赖。
我们先来安装和简单使用下maven,体验一下再继续讲解

安装及配置

  1. 官网下载安装(地址),选择bin.zip的版本
  2. 解压到安装目录
  3. path中加入maven安装目录下的bin目录路径
  4. 测试安装成功:cmd中执行mvn -v
  5. 修改配置文件,apache-maven-xxx\conf目录下的settings文件

settings.xml文件配置前,建议先备份,配置主要修改两个地方

①(可选,默认在C盘的用户目录下)修改localRepository 为 d:/mvn/repo ②(极速之选,不选龟速)修改mirrors,在标签下加入以下配置,启动阿里云的镜像 alimaven

aliyun maven

http://maven.aliyun.com/nexus/content/groups/public/

central

下面来建一个简单地项目测试下maven的基本使用

创建项目 mvn archetype:generate -DarchetypeCatalog=internal-DgroupId=com.woniuxy.build -DartifactId=demo 执行项目 mvnclean compile exec:java -Dexec.mainClass=”com.woniuxy.build.App”

思考:
1)项目创建后由哪些文件及文件夹组成,分别是什么意思?
2)我们现在可以脱离IDE(eclipse或者idea)创建并执行项目,这对于我们而言有什么好处?

基本结构

Maven的设计理念里有一个重要的理念叫做:约定优于配置 Conversion over configuration。意思是我们约定了,就不要去专门配置它。这样就可以显著的减少配置内容。maven的文件夹结构就是一个约定

─src 源码 ├─main 主要内容 │ ├─java java代码 │ │ └─com 以下是java的包路径 │ │ └─example │ │ └─demo │ └─resources 资源包括配置文件等 └─test 测试内容 └─java └─com └─example └─demo ─pom.xml 最核心的配置文件

这个结构是必须记住的。

pom.xml的结构

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <!--父项目,用于继承,可以没有-->
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.3.4.RELEASE</version>
  10. <relativePath/> <!-- lookup parent from repository -->
  11. </parent>
  12. <!--本项目的坐标,GAV-->
  13. <groupId>me.maiz.framework</groupId>
  14. <artifactId>springsecuritydemo</artifactId>
  15. <version>0.0.1-SNAPSHOT</version>
  16. <!--项目信息-->
  17. <name>springsecuritydemo</name>
  18. <description>Demo project for Spring Boot</description>
  19. <!--项目的属性配置-->
  20. <properties>
  21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  22. <maven.compiler.source>1.7</maven.compiler.source>
  23. <maven.compiler.target>1.7</maven.compiler.target>
  24. </properties>
  25. <!--本项目的依赖-->
  26. <dependencies>
  27. <dependency>
  28. <groupId>mysql</groupId>
  29. <artifactId>mysql-connector-java</artifactId>
  30. <version>5.1.48</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.projectlombok</groupId>
  34. <artifactId>lombok</artifactId>
  35. <optional>true</optional>
  36. </dependency>
  37. ...
  38. </dependencies>
  39. <!--构建项目的配置-->
  40. <build>
  41. <!--构建用到的插件-->
  42. <plugins>
  43. <plugin>
  44. <groupId>org.springframework.boot</groupId>
  45. <artifactId>spring-boot-maven-plugin</artifactId>
  46. </plugin>
  47. </plugins>
  48. </build>
  49. </project>

通过这种方式就把maven项目标准化了。

依赖管理

maven解决上述依赖管理问题的方式首先是标准化,就是刚才说的GAV坐标和maven的项目结构。

坐标

G:GroupId 分组ID,一般一家公司共用一个,与java包命名方式类似
A:ArtifactId 工件ID,项目ID
V: Version 版本号
可以唯一定位到某个jar包。

依赖范围

compile:编译时依赖在所有阶段都可获得,这是默认值。
provided: 提供的依赖范围用来编译应用程序,但无需部署。若你用到jdk或者应用服务器提供的JAR,则使用此范围,servlet APIs就属于这个依赖范围。测试时,此依赖范围也加入到classpath。
runtime:运行依赖范围在编译阶段是不需要的,只有在运行时需要,比如JDBC驱动程序。
test:测试范围依赖,仅在编译和运行单元测试时需要(比如Junit)。
system 本地依赖,不建议使用。(nexus)

依赖范围(scope) 编译时依赖 测试时依赖 运行时依赖 是否打入包 例子
compile Y Y Y Y SLF4J
provided Y Y Y N SERVLET-API
test N Y N N JUNIT
runtime N Y Y Y MYSQL JDBC DRIVER
system Y Y Y Y 有nexus后,不用system

传递依赖

请注意 只有编译和运行时依赖是传递的
A->B->C
简单传递依赖:A->C
A->B-C(0.1)
A->C(0.2)
最短路径优先原则: A->C(0.2)
A->B->C(0.1)
A->E->C(0.2)
第一声明优先原则:最终依赖C(0.1)

依赖归类

  1. <properties>
  2. <spring.version>2.5</spring.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-context</artifactId>
  8. <version>${spring.version}</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework</groupId>
  12. <artifactId>spring-jdbc</artifactId>
  13. <version>${spring.version}</version>
  14. </dependency>
  15. </dependencies>

依赖排除

  1. <dependency>
  2. <groupId>org.hibernate</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>4.3.1.Final</version>
  5. <exclusions>
  6. <exclusion>
  7. <artifactId>slf4j-api</artifactId>
  8. <groupId>org.slf4j</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

Maven仓库

很自然,我们能够想到,所有的maven项目最后需要发布到一个仓库中,以便于通过dependency进行获取。这个仓库称为中央仓库,地址是:https://mvnrepository.com/
但中央仓库是在公网上(而且在墙外),访问速度很慢,因此通常我们会有镜像仓库,或者称为私服。我们配置的是 阿里云的地址 https://maven.aliyun.com/mvn/guide 通过maven配置文件settings.xml中的mirror配置指定
从中央仓库或者maven私服拉取的jar包,存储在本地的一个文件夹中,我们称为maven本地仓库。这个仓库在settings.xml中的localRepository中指定。默认在用户目录的.m2目录下
反之,当需要一个依赖是,maven的查找顺序是

本地仓库 -> 私服 -> 中央仓库

了解这点,可以帮助我们解决很多实际的问题。

自动构建

构建,是指从源码到成品的过程。先举个简单地例子,在java项目中,从java代码到jar包的过程,大致有 编译,打包两步,这个就可以看做是个构建过程了。当然真实的要复杂的多。
maven定义了一个项目构建的完整过程,并且提供了命令给我们来执行这个构建过程。maven将整个构建过程称为生命周期。
image.png
每个环节称为一个阶段,每个阶段又有若干个可以执行的目标
image.png
基本上来说,有 初始化 -> 编译 -> 单元测试 -> 打包 -> 集成测试 -> 安装 -> 部署
这个过程是有顺序的。也就是说执行打包必须先执行单元测试
我们也可以在命令上加-Dmaven.test.skip=true跳过单元测试,因为一般都没写。
完整的描述如下
image.png
以上生命周期称为默认生命周期,maven定义了另一个生命周期clean,只有一个阶段就是clean,意思是清理maven所生成的代码。

对我们的项目执行以下命令并观察结果,请注意要在有pom.xml的目录下执行,所有命令都基于pom.xml来执行的:

  1. mvn compile
  2. mvn clean
  3. mvn clean install

骨架Archetype

maven通过archetype来定义项目的原型骨架,选择一个骨架就可以对应生成某个结构的项目。比如JavaSE的项目,JavaWeb的项目,甚至一本书。我们也可以自定义maven骨架,来生成自己的项目。

Maven命令总汇

  1. # 查看依赖树
  2. mvn dependency:tree
  3. # 查看生效的pom配置
  4. mvn help:effective-pom
  5. # 清理并安装,跳过测试
  6. mvn clean install -Dmaven.test.skip=true
  7. # 清理并打包,跳过测试
  8. mvn clean package -Dmaven.test.skip=true
  9. # 编译源代码
  10. mvn compile
  11. # 执行java项目
  12. mvn exec:java -Dexec.mainClass="***.Main"
  13. # 下载依赖源码
  14. mvn dependency:sources
  15. # -e 选项 开启异常打印
  16. # -T 使用多进程下载

其他主题

  • 父POM
  • 多模块项目
  • 下一代管理工具 gradle