前言
  
  在如今的互联网项目开发当中,特别是Java领域,可以说Maven随处可见。Maven的仓库管理、依赖管理、继承和聚合等特性为项目的构建提供了一整套完善的解决方案,可以说如果你搞不懂Maven,那么一个多模块的项目足以让你头疼,依赖冲突就会让你不知所措,甚至搞不清楚项目是如何运行起来的.....OK,博主就曾经被Maven“伤害”过,那么该专题的目的就是:彻底搞定Maven!
  
  Thinking in Maven
  
  回想一下,当你新到一家公司,安装完JDK后就会安装配置Maven(MAVEN_HOME、path),很大可能性你需要修改settings.xml文件,比如你会修改本地仓库地址路径,比如你很可能会copy一段配置到你的settings.xml中(很可能就是私服的一些配置)。
  
  接下来,你会到IDEA或者Eclipse中进行Maven插件配置,然后你就可以在工程中的pom.xml里面开始添加<dependency>标签来管理jar包,在Maven规范的目录结构下进行编写代码,最后你会通过插件的方式来进行测试、打包(jar or war)、部署、运行。
  
  上面描述了我们对Maven的一些使用方式,下面我们进行一些思考:
  
  本地仓库?Maven到底有哪些仓库?它们什么关系?
  
  你要jar包,不可能每次都要联网去下载吧,多费劲,所以本地仓库就是相当于加了一层jar包缓存,先到这里来查。如果这里查不到,那么就去私服上找,如果私服也找不到,那么去中央仓库去找,找到jar后,会把jar的信息同步到私服和本地仓库中。
  
  私服,就是公司内部局域网的一台服务器而已,你想一下,当你的工程Project-A依赖别人的Project-B的接口,怎么做呢?没有Maven的时候,当然是copy Project-B jar到你的本地lib中引入,那么Maven的方式,很显然需要其他人把Project-B deploy到私服仓库中供你使用。因此私服中存储了本公司的内部专用的jar!不仅如此,私服还充当了中央仓库的镜像,说白了就是一个代理!
  
  中央仓库:该仓库存储了互联网上的jar,由Maven团队来维护,地址是:http://repo1.maven.org/maven2/。
  
  关于<dependency>的使用
  
  其实这个标签揭示了jar的查找坐标:groupId、artifactId、version。
  
  一般而言,我们可以到私服上输入artifactId进行搜索,或者到http://search.maven.org/、http://mvnrepository.com/上进行查找确定坐标。
  
  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版本!
  
  既然Maven进行了依赖管理,为什么还会出现依赖冲突?处理依赖冲突的手段是?
  
  首先来说,对于Maven而言,同一个groupId同一个artifactId下,只能使用一个version!
  
  根据上图的依赖顺序,将使用1.2版本的jar。
  
  现在,我们可以思考下了,比如工程中需要引入A、B,而A依赖1.0版本的C,B依赖2.0版本的C,那么问题来了,C使用的版本将由引入A、B的顺序而定?这显然不靠谱!如果A的依赖写在B的依赖后面,将意味着最后引入的是1.0版本的C,很可能在运行阶段出现类(ClassNotFoundException)、方法(NoSuchMethodError)找不到的错误(因为B使用的是高版本的C)!
  
  这里其实涉及到了2个概念:依赖传递(transitive)、Maven的最近依赖策略。
  
  依赖传递:如果A依赖B,B依赖C,那么引入A,意味着B和C都会被引入。
  
  Maven的最近依赖策略:如果一个项目依赖相同的groupId、artifactId的多个版本,那么在依赖树(mvn dependency:tree)中离项目最近的那个版本将会被使用。(从这里可以看出Maven是不是有点小问题呢?能不能选择高版本的进行依赖么?据了解,Gradle就是version+策略)
  
  现在,我们可以想想如何处理依赖冲突呢?
  
  想法1:要使用哪个版本,我们是清楚的,那么能不能不管如何依赖传递,都可以进行版本锁定呢?
  
  使用<dependencyManagement> [这种主要用于子模块的版本一致性中]
  
  想法2:在依赖传递中,能不能去掉我们不想依赖的?
  
  使用<exclusions> [在实际中我们可以在IDEA中直接利用插件帮助我们生成]
  
  想法3:既然是最近依赖策略,那么我们就直接使用显式依赖指定版本,那不就是最靠近项目的么?
  
  使用<dependency>
  
  引入依赖的最佳实践,提前发现问题!
  
  在工程中,我们避免不了需要加一些依赖,也许加了依赖后运行时才发现存在依赖冲突在去解决,似乎有点晚!那么能不能提前发现问题呢?
  
  如果我们新加入一个依赖的话,那么先通过mvn dependency:tree命令形成依赖树,看看我们新加入的依赖,是否存在传递依赖,传递依赖中是否和依赖树中的版本存在冲突,如果存在多个版本冲突,利用上文的方式进行解决!
  
  Maven规范化目录结构
  
  这里需要注意2点: 第一:src/main下内容最终会打包到Jar/War中,而src/test下是测试内容,并不会打包进去。
  
  第二:src/main/resources中的资源文件会COPY至目标目录,这是Maven的默认生命周期中的一个规定动作。(想一想,hibernate/mybatis的映射XML需要放入resources下,而不能在放在其他地方了)
  
  Maven的生命周期
  
  我们只需要注意一点:执行后面的命令时,前面的命令自动得到执行。
  
  实际上,我们最常用的就是这么几个:
  
  clean:有问题,多清理!
  
  package:打成Jar or War包,会自动进行clean+compile
  
  install:将本地工程Jar上传到本地仓库
  
  deploy:上传到私服
  
  private void prepareContext(ConfigurableApplicationContext context,
  
  2 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
  
  3 ApplicationArguments applicationArguments, Banner printedBanner) {
  
  4 //设置容器环境
  
  5 context.setEnvironment(environment);
  
  6 //执行容器后置处理
  
  7 postProcessApplicationContext(context);
  
  8 //执行容器中的 ApplicationContextInitializer 包括spring.factories和通过三种方式自定义的
  
  9 applyInitializers(context);
  
  10 //向各个监听器发送容器已经准备好的事件
  
  11 listeners.contextPrepared(context);
  
  12 if (this.logStartupInfo) {
  
  13 logStartupInfo(context.getParent() == null);
  
  14 logStartupProfileInfo(context);
  
  15 }
  
  16
  
  17 // Add boot specific singleton beans
  
  18 //将main函数中的args参数封装成单例Bean,注册进容器
  
  19 context.getBeanFactory().registerSingleton("springApplicationArguments",
  
  20 applicationArguments);
  
  21 //将 printedBanner 也封装成单例,注册进容器
  
  22 if (printedBanner != null) www.oushenggw.com{
  
  23 context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
  
  24 }
  
  25
  
  26 // Load the sources
  
  27 Set<Object> sources = getAllSources(www.oushengyul.com);
  
  28 Assert.notEmpty(sources, "Sources must not be empty");
  
  29 //加载我们的启动类,将启动类注入容器
  
  30 load(context, sources.toArray(new Object[0]));
  
  31 //发布容器已加载事件
  
  32 listeners.contextLoaded(context);
  
  33 }
  
  复制代码
  
  首先看这行 Set<Object> sources = getAllSources(www.xcdeyiju.com); 在getAllSources()中拿到了我们的启动类。
  
  我们本文重点讲解这行 load(context, sources.toArray(new Object[0])); ,其他的方法请参阅注释。
  
  跟进load()方法,看源码
  
  复制代码
  
  1 protected void load(ApplicationContext context, Object[] sources) {
  
  2 if (logger.isDebugEnabled()) {
  
  3 logger.debug(
  
  4 "Loading source "baiyiyulgw.com + StringUtils.arrayToCommaDelimitedString(sources));
  
  5 }
  
  6 //创建 BeanDefinitionLoader
  
  7 BeanDefinitionLoader loader = createBeanDefinitionLoader(
  
  8 getBeanDefinitionRegistry(context), sources);
  
  9 if (this.beanNameGenerator !www.mingj2yl.com= null) {
  
  10 loader.setBeanNameGenerator(this.beanNameGenerator);
  
  11 }
  
  12 if (this.resourceLoader != null) {
  
  13 loader.setResourceLoader(this.resourceLoader);
  
  14 }
  
  15 if (this.environment != null) {
  
  16 loader.setEnvironment(this.environment);
  
  17 }
  
  18 loader.load();
  
  19 }
  
  既然,Maven的生命周期存在编译、测试、运行这些过程,那么显然有些依赖只用于测试,比如junit;有些依赖编译用不到,只有运行的时候才能用到,比如mysql的驱动包在编译期就用不到(编译期用的是JDBC接口),而是在运行时用到的;还有些依赖,编译期要用到,而运行期不需要提供,因为有些容器已经提供了,比如servlet-api在tomcat中已经提供了,我们只需要的是编译期提供而已。
  
  总结来说:
  
  compile:默认的scope,运行期有效,需要打入包中。
  
  provided:编译期有效,运行期不需要提供,不会打入包中。
  
  runtime:编译不需要,在运行期有效,需要导入包中。(接口与实现分离)
  
  test:测试需要,不会打入包中。
  
  system:非本地仓库引入、存在系统的某个路径下的jar。(一般不使用)

Maven 虐我千百遍,我待 Maven 如初恋的更多相关文章

  1. Maven虐我千百遍,我待Maven如初恋

    前言 在如今的互联网项目开发当中,特别是Java领域,可以说Maven随处可见.Maven的仓库管理.依赖管理.继承和聚合等特性为项目的构建提供了一整套完善的解决方案,可以说如果你搞不懂Maven,那 ...

  2. JAVA_集合框架虐我千百遍,虐也是一种进步

    1.Collection和Collections区别: Collection是java的一个集合接口,集合类的顶级接口 Collections是一个包装类(工具类),不能被实例化(由于其构造函数设为私 ...

  3. 微软虐我千百遍——记一次比较漫长的TFS数据库迁移

    起因 七月三日早晨刚到公司,同事就跟我讲TFS开始返回 TF30042错误,报告数据库已满.按照处理问题的第一直觉,我上bing的英文网站搜了一下,发现是部署TFS的时候使用的SQL Express限 ...

  4. 一个虐你千百遍的问题:“RPC好,还是RESTful好?”

    看到知乎上有这样一个问题 WEB开发中,使用JSON-RPC好,还是RESTful API好? 还有其他优秀的推荐方案吗? -------------------------------------- ...

  5. 漫说测试 | 研发虐我千百遍,我待bug如初恋

    的行业之一他们的运筹帷幄,他们的勾心斗角,只有自己知道.000,但绝对也是最枯燥的行业之一! IT可能是几个最高薪行业之一,但同时也绝对是最辛苦的行业之一!IT业是最需要创新能力的行业之一,但绝对也是 ...

  6. 硬不硬你说了算!35 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

    每日一句英语学习,每天进步一点点: 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百遍,我仍待 TCP 如初恋. 遥想小林 ...

  7. 被 GANs 虐千百遍后,我总结出来的 10 条训练经验

    一年前,我决定开始探索生成式对抗网络(GANs).自从我对深度学习产生兴趣以来,我就一直对它们很着迷,主要是因为深度学习能做到很多不可置信的事情.当我想到人工智能的时候,GAN是我脑海中最先出现的一个 ...

  8. Java千百问_05面向对象(005)_接口和抽象类有什么差别

    点击进入_很多其它_Java千百问 1.接口和抽象类有什么差别 在Java语言中.抽象类abstract class和接口interface是抽象定义的两种机制. 正是因为这两种机制的存在,才赋予了J ...

  9. Java千百问_05面向对象(011)_引用传递和值传递有什么差别

    点击进入_很多其它_Java千百问 1.什么是值传递 值传递,是将内存空间中某个存储单元中存放的值,传送给还有一个存储单元.(java中的存储单元并不是物理内存的地址,但具有相关性) 比如: //定义 ...

随机推荐

  1. Spring MVC 参数的绑定方法

    在Spring MVC中,常见的应用场景就是给请求的Url绑定参数.本篇就介绍两种最最基本的绑定参数的方式: 基于@RequestParam 这种方法一般用于在URL后使用?添加参数,比如: @Req ...

  2. 树状数组的神操作QAQ

    卧槽 厉害了,我的树状数组 1.单点修改,单点查询 用差分数组维护 #include<cstdio> #include<iostream> using namespace st ...

  3. EOS区块同步源码分析之见证者

    调了两天,终于把EOS的出块和DPOS共识机制的代码看了个大概,下面做个小总结.因为这一块是EOS比较核心的代码块,稍微复杂些,如果有理解错误之处,请大家见识并帮我指出来,我也会有可能随时改写这里的内 ...

  4. JSP-用网页输出乘法表 三角形及菱形

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  5. 消息中间件的研究 (四)RabbitMQ、Kafka、RocketMQ消息中间件的对比及分析

    RabbitMQ:     RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现.AMQP的主要特征是面向消息.队列.路由(包括点对点和发布/订阅).可靠性.安全.AM ...

  6. sourcetree基本使用

    非常有用的使用sourcetree开发的步骤文档 https://www.cnblogs.com/fps2tao/p/7825742.html 1) master,最终发布版本,整个项目中有且只有一个 ...

  7. 原生Ajax实现

    发现一件可怕的事情,前端学不完了,春招要开始了.唉,人生苦短.

  8. 在邮箱服务器上执行Powershell命令Get-MessageTrackingLog 报错

    开启对应的服务即可. 中文环境: 英文环境:

  9. POJ2758 Checking the Text 哈希

    注意到插入次数挺少的,于是每次暴力重构,然后哈希+二分 #include<cstdio> #include<iostream> #include<algorithm> ...

  10. GHOST来进行备份和还原及菜单介绍

    这篇文章主要说的是如何手工操作GHOST来进行备份和还原. GHOST的菜单及功能: 在主菜单中,有以下几项: l Local:本地操作,对本地计算机上的硬盘进行操作. l Peer to peer: ...