果冻

果冻 查看完整档案

呼和浩特编辑内蒙古大学  |  计算机科学与技术 编辑中国移动  |  开发运维工程师 编辑 www.jellythink.com/ 编辑
编辑

向全栈进军!!!

个人动态

果冻 发布了文章 · 2020-10-27

一篇文章搞懂Slf4j和Log4j2

前言

一如既往的上班,一如既往的打开IDEA,一如既往的写下了这样的一行代码:

private static final Logger log = LoggerFactory.getLogger(CommonController.class);

等等,写了这么久了,只知道这样可以很方便的使用log对象记录日志,那这到底是如何记录的呢?那么今天就来对这行代码背后使用的Slf4j和Log4j2一探究竟。

关于Slf4j和Log4j2

说起Slf4j和Log4j2,我们都知道这个是记录日志的,但是在Java的世界中,记录日志的框架那么多,那么这么多日志框架中,我们是如何相中Slf4j和Log4j2的?Slf4j和Log4j2和又是什么关系呢?好的,收起我们的疑问,你将在这篇文章中,一扫你的疑惑,满足你对Slf4j和Log4j2的一切想象。

首先,你要知道,Slf4j是一个日志框架,它是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,它需要和具体的日志框架来配合使用;

其次,你要知道,Log4j2是一个日志实现,它是一种记录日志的具体实现;在Java中具体的日志实现有好多中,比如Log4j、Log4j2、Slf4j、JDKLog、Logback等等;

最后,你要知道,框架和实现的这种搭配使用方式,是为了以后如果项目对日志有其它要求而需要更换日志框架时可以不改动代码,只需要把依赖的jar包换掉就可以了。

在上面提到的日志框架中,以Slf4j和Log4j2的使用组合最为常见,但是我们知道Log4j目前已经停止更新了。Apache推出了新的Log4j2来代替Log4j,Log4j2是对Log4j的升级,与其前身Log4j相比有了显着的改进,并提供了许多Logback可用的改进,同时解决了Logback体系结构中的一些固有问题。因此,Slf4j和Log4j2应该是未来的大势所趋。

与Spring Boot集成

在普通的Maven项目中,需要使用Slf4j和Log4j2时,添加以下Maven依赖即可:

<!--log4j2核心包-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.9.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.9.1</version>
</dependency>
<!--用于与slf4j保持桥接-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.0</version>
</dependency>
<!-- slf4j核心包-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

平时工作中使用Spring Boot比较多,所以这里着重总结一下Spring Boot与log4j2的集成。Spring Boot默认使用logback,但相比较而言,log4j2在性能上面会更好。log4j2在使用方面与log4j基本上没什么区别,比较大的区别是log4j2不再支持properties配置文件,支持xml、json格式的文件。

Log4j2配置说明

Log4j2的与Spring Boot的集成和使用都没有什么难点,而对于Log4j2的配置来说,则是整个的精华所在,也是整个的难点所在。所以这里有必要对Log4j2的配置进行详细的总结说明。

Log4j2日志有以下日志级别,从小到大依次为:tracedebuginfowarnerror,级别越大,输出的日志信息越少。接下来说说配置文件的结构:

+-Configuration
    +-Properties
    +-Appenders:
        +-Appender
            +-Layout
            +-Policies
            +-Strategy
    +-Loggers
        +-Logger
        +-RootLogger

根节点Configuration有两个属性:statusmonitorinterval

  • status:用来指定log4j本身的打印日志的级别;
  • monitorinterval:用于指定log4j2自动重新配置的监测间隔时间,单位是s,最小是5s。

节点说明:

  • Properties:一般用来做属性定义;
  • Appender:可以理解为一个管道,定义了日志内容的输出位置:

    • Console:用来定义输出到控制台Appender;

      • name:指定Appender的名字;
      • target:SYSTEM_OUT或SYSTEM_ERR,一般只设置默认:SYSTEM_OUT;
      • PatternLayout:输出格式,不设置默认为:%m%n;
      • Filter:配置日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立);
    • File:用来定义输出到指定位置的文件Appender;

      • name:指定Appender的名字;
      • fileName:指定输出日志的目的文件带全路径的文件名;
      • PatternLayout:输出格式,不设置默认为:%m%n;
      • Filter:配置日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立);
    • RollingFile:用来定义超过指定大小自动删除旧的创建新文件Appender;

      • name:指定Appender的名字;
      • fileName:指定输出日志的目的文件带全路径的文件名;
      • PatternLayout:输出格式,不设置默认为:%m%n;
      • Filter:配置日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立);
      • PatternLayout:输出格式,不设置默认为:%m%n;
      • Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志;
      • Strategy:配置Strategy以控制日志如何(How)进行滚动。
  • Loggers:简单说Logger就是一个路由器,指定类、包中的日志信息流向哪个Appender,以及控制他们的日志级别;

    • Root:必须要配置;用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出;

      • level:属性;用来指定日志输出级别;
      • AppenderRefRoot的子节点,用来指定该日志输出到哪个Appender
    • Logger:用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。

      • level:属性;用来指定日志输出级别;
      • name:属性;用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点;
      • AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender;如果没有指定,就会默认继承自Root;如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。

上面说到的Filter一般常用的有以下三种:

  • LevelRangeFilter:输出指定日志级别范围之内的日志;
  • TimeFilter:在指定时间范围内才会输出日志;
  • ThresholdFilter:输出符合特定日志级别及其以上级别的日志。

上面说到的Policies一般常用的有以下三种:

  • SizeBasedTriggeringPolicy:根据日志文件的大小进行滚动;单位有:KB,MB,GB;
  • CronTriggeringPolicy:使用Cron表达式进行日志滚动,很灵活;
  • TimeBasedTriggeringPolicy:这个配置需要和filePattern结合使用,注意filePattern中配置的文件重命名规则。滚动策略依赖于filePattern中配置的最具体的时间单位,根据最具体的时间单位进行滚动。这种方式比较简洁。

使用示例

为了方便大家参考学习,提供一个样例配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定义日志存储的路径,不要配置相对路径 -->
        <property name="FILE_PATH" value="E:\logs\log4j2" />
        <property name="FILE_NAME" value="springbootlog4j2" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>

        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

    </appenders>

    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>

        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>

总结

如果每天学习一个知识点,那么一年就是365个,加油吧,少年。
关注我,一起进步。

查看原文

赞 2 收藏 2 评论 0

果冻 关注了用户 · 2020-09-20

花裤衩 @panjiachen

show me the code

关注 2687

果冻 发布了文章 · 2019-10-06

Maven基础教程之Archetype

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

作为整个系列的最后一篇,写到这个时候,多多少少都会有一点惰性,在4月初定计划时,原计划用一个月的时间把整个Maven系列整理完,但是这都到了5月份了,罪过、罪过!

虽然作为整个系列的最后一篇文章,我也是丝毫不敢有所懈怠,仍要认真的来总结一番。

Archetype是什么?

在《Maven基础教程之使用入门》这篇文章中,就有说到Archetype,但是在那篇文章中并没有细说。其实,我们可以将Archetype理解成Maven项目的模板,通过Archetype,我们可以快速生成项目框架。阅读过之前的文章的小伙伴,肯定也就知道了,整个Maven的具体功能都是通过插件来完成的;同理,Archetype也是通过插件来完成的。这个名为maven-archetype-plugin的插件提供了Archetype的所有功能。由于Archetype使用范围非常广,在很多有名的IDE中都集成了Archetype特性,以方便我们快速的创建Maven项目。下图就是IDEA创建Maven项目时继承的Archetype:

IDEA集成Archetype

如何使用Archetype

关于Archetype,Maven 2和Maven 3有很大的区别,考虑到咱们现在基本上都是使用的Maven 3,所以这里就重点总结Maven 3中Archetype的使用。

我们在命令行输入mvn archetype:generate后,Archetype插件会输出一个Archetype列表供我们选择,我们根据我们的需要选择不同的Archetype模板,然后根据提示,输入groupIdartifactIdversion等必要的信息,就可以创建不同的Maven项目。虽然不是非常友好的UI界面,但是使用起来也是非常简单的。

由于Archetype列表比较多,我们在选择的时候难免就会出现选择恐惧症,所以,对于一些常用的Archetype,大家需要有所熟悉,对于一些常用的Archetype,这里推荐一篇博文和大家分享——《Maven 三种archetype说明》。

总结

其实对于Archetype,我们只需要明白这是一个脚手架就OK了,更深的东西,比如如何编写符合我们自己要求的Archetype啊,我这里没有涉及,因为考虑到99%的情况下,我们不会涉及这个的,现有的Archetype也基本上都满足我们的日常开发需要了。所以,那就这样吧,这个系列圆满结束!

果冻想,玩技术,玩代码!

2019年5月5日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之多环境构建

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

这篇文章总结的多环境构建绝对是会在实际工作中会用到的内容。比如我现在的这家公司的Maven项目,基本上都使用了这篇文章将要总结的多环境构建。那到底什么是多环境构建呢?

我们想象一下这样的一个场景。一般我们的项目都会有开发环境、测试环境和生产环境,这些环境的数据库等配置基本上都是不一样的,那么我们在进行项目构建的时候就需要能够识别所在的环境并使用正确的配置数据。对于多个环境,我们如何能够灵活的使用不同的配置数据呢?

在Maven中,为了灵活的支持这种场景,内置了三大特性,即属性、Profile和资源过滤。下面我们将通过具体的代码示例来细说这三大特性。

Maven属性

对于Maven属性,在前面的文章我们也接触过,比如之前是这样用的:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.jellythink.BookStore</groupId>
    <artifactId>project-A</artifactId>
    <version>1.0.0</version>

    <properties>
        <springframework.version>5.1.6.RELEASE</springframework.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${springframework.version}</version>
        </dependency>
    </dependencies>
</project>

通过<properties>元素,我们可以自定义一个或多个Maven属性,然后在POM的其它地方使用${属性名称}的方式引用该属性,这种做法的最大意义在于消除重复,便于后期统一修改。但是这不是Maven属性的全部,实际上,在Maven中包含以下六类属性:

  • 内置属性
    主要有以下两个常用内置属性:

    • ${basedir}表示项目根目录,即包含pom.xml文件的目录
    • ${version}表示项目版本
  • POM属性
    我们可以使用POM属性引用POM文件中对应元素的值,比如${project.artifactId}就对应了<project><artifactId>元素的值,常用的POM属性包括:

    • ${project.build.sourceDirectory}:项目的主源码目录,默认为src/main/java/
    • ${project.build.testSourceDirectory}:项目的测试源码目录,默认为src/test/java/
    • ${project.build.directory}:项目构建输出目录,默认为target/
    • ${project.outputDirectory}:项目主代码编译输出目录,默认为target/classes/
    • ${project.testOutputDirectory}:项目测试代码编译输出目录,默认为target/test-classes/
    • ${project.groupId}:项目的groupId
    • ${project.artifactId}:项目的artifactId
    • ${project.version}:项目的version,与${version}等价
    • ${project.build.finalName}:项目打包输出文件的名称,默认为${project.artifactId}-${project.version}
这些属性都对应一个POM元素,有一些属性的默认值都是在超级POM中定义的。
  • 自定义属性
    自定义属性就是通过<properties>元素定义的属性,最开始的例子就已经讲的很明白了。
  • Settings属性
    大家还记得Maven中的settings.xml文件吗?不记得的伙伴可以去看下这篇《Maven基础教程之安装与配置》。而这个Settings属性就表示我们可以使用以settings.开头的属性引用settings.xml文件中XML元素的值,比如我们可以使用${settings.localRepository}来引用用户本地仓库的地址。
  • Java系统属性
    所有的Java系统属性都可以使用Maven属性引用,比如${user.home}指向用户的目录。我们可以使用mvn help:system查看所有的Java系统属性。
  • 环境变量属性
    所有环境变量都可以使用以env.开头的Maven属性引用。比如${env.JAVA_HOME}指向了JAVA_HOME环境变量的值。我们可以使用mvn help:system查看所有的Java系统属性。

正确的使用这些Maven属性可以帮助我们简化POM的配置和维护工作。

资源过滤

在我们开发过程中,经常会碰到这样的配置文件:

database.jdbc.driverClass = com.mysql.jdbc.driverClass
database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev
database.jdbc.username = develop
database.jdbc.password = develop-password

上面的配置数据只是对开发人员的,如果测试人员进行时,则使用的如下这样的一套配置文件:

database.jdbc.driverClass = com.mysql.jdbc.driverClass
database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test
database.jdbc.username = test
database.jdbc.password = test-password

也就是说,在开发环境和测试环境,我们需要使用不同的配置文件,在没有使用Maven之前,我们都是手动的修改对应的配置数据,话又说回来了,这样很麻烦,还很容易出错。现在有了Maven,我们需要作出一点改变。

为了应对不同的使用环境,我们需要将配置文件中变化的部分使用Maven属性替换,比如上面的配置文件,我们需要修改成这个样子:

database.jdbc.driverClass = ${db.driver}
database.jdbc.connectionURL = ${db.url}
database.jdbc.username = ${db.username}
database.jdbc.password = ${db.password}

我们在配置文件中定义了四个Maven属性:db.driver、db.url、db.username和db.password。接下来,我们就需要在某个地方定义这些属性。在Maven中,我们只需要使用一个额外的profile来定义这些属性就可以了。

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <db.driver>com.mysql.jdbc.driverClass</db.driver>
            <db.url>jdbc:mysql://localhost:3306/dev</db.url>
            <db.username>develop</db.username>
            <db.password>develop-password</db.password>
        </properties>
    </profile>
</profiles>

这里通过profile定义了这些属性,并使用了一个id为dev的值来区别这个profile,这样以后我们就可以针对不同的环境定义不同的profile,就可以非常的灵活。

有了属性定义,配置文件中也使用了这些属性,这样就可以了吗?不是这么简单的!我们都知道,Maven属性默认只有在POM中才会被解析。也就是说,${db.username}放到POM中会被解析成develop,但是如果放到src/main/resources/目录下的文件中,构建的时候它还是${db.username}。所以,我们需要让Maven解析资源文件中的Maven属性。

资源文件的处理其实是maven-resources-plugin的工作,但是它默认的行为只是将项目主资源文件复制到主代码编译输出目录中,将测试资源文件复制到测试代码编译输出目录中。我们只需要开启资源过滤,这个插件就能够解析资源文件中的Maven属性。

为主资源目录开启过滤:

<build>
    <!-- 为主资源目录开启过滤 -->
    <resources>
        <resource>
            <directory>${project.basedir}/src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    
    <!-- 为测试资源目录开启过滤 -->
    <testResources>
        <testResource>
            <directory>${project.basedir}/src/main/resources</directory>
            <filtering>true</filtering>
        </testResource>
    </testResources>
</build>

我们通过mvn clean package -Pdev命令进行构建。其中-P参数表示在命令行激活一个profile。对于profile没有看懂,不要紧,下面我们再细说。构建完成后,输出目录中的数据库配置就是开发环境的配置了:

database.jdbc.driverClass = com.mysql.jdbc.driverClass
database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev
database.jdbc.username = develop
database.jdbc.password = develop-password

Maven Profile

上面说到profile,对于profile大家可能还非常的懵,这里就对profile进行详细的总结。profile是一个非常有用的功能,至少在我们公司的项目中大量使用。profile是专为不同的环境,实现无缝迁移而定制的。我们可以不同的环境配置不同的profile,比如上面提到的开发环境和测试环境两种环境下不同的配置信息,我们可以通过profile进行配置:

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <db.driver>com.mysql.jdbc.driverClass</db.driver>
            <db.url>jdbc:mysql://localhost:3306/dev</db.url>
            <db.username>develop</db.username>
            <db.password>develop-password</db.password>
        </properties>
    </profile>
    <profile>
        <id>test</id>
        <properties>
            <db.driver>com.mysql.jdbc.driverClass</db.driver>
            <db.url>jdbc:mysql://localhost:3306/test</db.url>
            <db.username>test</db.username>
            <db.password>test-password</db.password>
        </properties>
    </profile>
</profiles>

可以看到,同样的属性在两个profile中的值是不一样的;同样的,我们还可以添加更多的profile配置,比如生产配置等。接下来,我们在构建应用时,可以使用-Pdev激活dev profile,使用-Ptest激活test profile。

总结

关于Maven的多环境构建,这篇文章基本上把我工作中遇到的各种语法都进行了总结,而且这些用法也比较有代表性,希望通过我这里的总结,对大家有所帮助,在日后的工作中,大家遇到这种语法不会搞到陌生;也更希望大家在构建自己的项目中,可以使用这些用法来提升自己的工作效率。

果冻想,玩代码,玩技术!

2019年5月5日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之测试

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

关于使用Maven进行测试,我本来是不想总结的,然后考虑到这个功能在实际开发中还经常使用,或者说,有的同学毕业后得第一份正式工作就是搞测试,编码写测试用例(其实我就是这样的)。所以,结合我自身的工作经历来看,我个人是非常崇尚测试,重视测试的,所以,这里我再通过这篇文章,对Maven是如何集成测试的进行简单的总结,方便大家有一个整体的印象和大的概念。

maven-surefire-plugin简介

通过之前的学习,我们都知道Maven本身并不是一个单元测试框架,在Java中,主流的单元测试框架是JUnit和TestNG。而Maven所做的只是在构建执行到特定生命周期阶段的时候,通过插件来执行JUnit或者TestNG的测试用例。而这一常用的插件就是maven-surefire-plugin

在默认情况下,maven-surefire-plugin的test目标会自动执行测试源码路径下所有符合一组命名模式的测试类。这组模式为:

  • **/Test*.java:任何子目录下所有命名以Test开头的Java类
  • **/*Test.java:任何子目录下所有命名以Test结尾的Java类
  • **/*TestCase.java:任何子目录下所有命名以TestCase结尾的Java类

只要将测试类按照上述模式命名,Maven就能自动运行它们,用户也就不再需要定义聚合测试用例。

为了能够运行测试,Maven需要在项目中引入测试框架的依赖,在前面的文章都有说到,这里就不再总结。

跳过测试

有的时候,我说的是有些时候哈,为了更快的完成构建,我们会跳过费时的测试阶段,这个时候,我们只需要在执行mvn命令时加上skipTests参数即可,比如这样:

mvn clean package -DskipTests

或者,我们也可以在POM中对插件进行配置,比如这样:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12.4</version>
    <configuration>
        <skipTests>true</skipTests>
    </configuration>
</plugin>

动态指定要运行的测试用例

上面直接跳过全部测试,这样的确有点极端,很多时候,我们增加了一个测试类,为了节省时间,就不想将所有的测试用例都运行一篇,而只是将新增的测试类运行一遍就OK了,这个时候我们就需要动态指定要运行的测试用例。

maven-surefire-plugin提供了一个test参数让Maven用户能够在命令行指定要运行的测试用例。比如这样:

mvn test -Dtest=LoginTest

这里test参数的值是测试用例的类名,这行命令的效果就是只有LoginTest这一个测试类得到运行。当然了,maven-surefire-plugin的test参数还支持一些高级的赋值模式,比如这样:

mvn test -Dtest=Admin*Test

星号可以匹配零个或多个字符,上述命令会运行项目中所有类名以Admin开头、Test结尾的测试类。除了星号匹配,还可以使用逗号指定多个测试用例:

mvn test -Dtest=LoginTest,AdminSearchTest

包含与排除测试用例

通过命令行动态指定要运行的测试用例确实不错,但是如果要动态指定运行的测试用例比较多时,通过命令就比较麻烦;换一种方式来思考,那就是如何排除指定的测试用例呢?

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12.4</version>
    <configuration>
        <excludes>
            <exclude>**/LoginTest.java</exclude>
            <exclude>**/Admin*Test.java</exclude>
        </excludes>
    </configuration>
</plugin>

有了上面所示的excludes配置后,maven-surefire-plugin就不再自动运行它们了。

测试报告

在我以前的东家,如果需要安排系统上线,就必须要提交系统测试报告。而通过maven-surefire-plugin插件,就可以生成简单明了的系统测试报告。默认情况下,maven-surefire-plugin会在项目的target/surefire-reports目录下生成两种格式的错误报告:

  • 简单文本格式
    简单的文本格式信息如下:

    -------------------------------------------------------------------------------
    Test set: com.jellythink.HelloWorld.AppTest
    -------------------------------------------------------------------------------
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.047 sec
  • 与JUnit兼容的XML格式
    XML格式的测试报告主要是为了支持工具的解析。

上面就看个人项目的需要了,按照需要生成即可,非常的方便。

总结

这篇文章主要总结了Maven中的测试,具体的就是围绕着maven-surefire-plugin这个官方默认插件进行的总结。这里总结的测试,在你将来的职业生涯中不一定会遇到或者使用到,但是至少我这里总结的内容会对你以后的工作可以提供一点点的指点,那就足够了!

果冻想,玩代码,玩即使!

2019年4月28日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之继承

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

上一篇文章就聚合进行了详细的总结,通过聚合,解决了我们构建多个项目的繁琐问题。但是,通过总结上一篇文章,大家可能会发现这么个问题,Project-A和Project-B项目的POM文件,有很多相同的部分。通过以往的开发经验,如果多个模块有相同的部分,那就意味着我们可以把相同的部分抽取出来,作为公共的部分进行使用,比如在Java中,我们可以把相同的部分放在父类中,子类继承父类,就搞定了。在Maven的世界里,也有类似的机制能让我们提取出重复的配置,这就是POM的继承,而这篇文章就对Maven中的POM继承进行详细的总结。

小试牛刀

这里我还是将通过一个例子来了解一下Maven继承的初步使用配置。还是使用三个工程项目Project-Parent、Project-C和Project-D来进行说明,三个项目关系如下:

项目关系图

Project-Parent工程POM文件如下:

<?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.jellythink.ExtendDemo</groupId>
  <artifactId>Project-Parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>Project-Parent</name>

</project>

看这个POM文件,会发现和聚合有几分相像,只是没有modules节点,同样需要注意的是packaging的取值,必须使用pom。

Project-C的POM文件如下:

<?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>

  <parent>
    <groupId>com.jellythink.ExtendDemo</groupId>
    <artifactId>Project-Parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../Project-Parent/pom.xml</relativePath>
  </parent>

  <artifactId>Project-C</artifactId>

  <name>Project-C</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
  </dependencies>
</project>

Project-D的POM文件如下:

<?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>

  <parent>
    <groupId>com.jellythink.ExtendDemo</groupId>
    <artifactId>Project-Parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../Project-Parent/pom.xml</relativePath>
  </parent>

  <artifactId>Project-D</artifactId>

  <name>Project-D</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
  </dependencies>
</project>

在Project-C和Project-D工程中都使用了parent元素声明父模块,parent下的坐标元素groupIdartifactIdversion是必须的,它们指定了父模块的坐标;元素relativePath表示父模块POM的相对路径。在项目构建时,Maven会首先根据relativePath检查父POM。

同时,在Project-C和Project-D工程中,我们都没有在POM中指定groupIdversion值,但是在构建时,依然可以成功构建。实际上,在Project-C和Project-D工程从父模块继承了这两个元素,这也就消除了一些不必要的配置。在这个例子中,子模块同父模块使用了同样的groupIdversion值,如果遇到子模块需要使用和父模块不一样的groupId或者version的情况,我们则完全可以再子模块中显示声明。

可继承的POM元素

在上面,可以看到groupIdversion是可以被继承的,那么还有哪些POM元素可以被继承呢?这里我将一些我们经常用作继承的元素做一下汇总,并进行简单的说明:

  • groupId:项目组ID
  • version:项目版本
  • distributionManagement:项目的部署配置
  • properties:自定义的Maven属性
  • dependencies:项目的依赖配置
  • dependencyManagement:项目的依赖管理配置
  • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等

依赖管理

上面说到dependencies是可以被继承的,那我们在Project-C和Project-D工程中很多公共的部分就可以提取出来,统一放到Project-Parent工程中去了,这样就可以移除公共配置,简化配置。

上面说的做法是可行的,但是会存在问题。我们考虑一下这样的一个场景。Project-C和Project-D工程中公共的部分都提取到Project-Parent工程中去了,如果此时新增了一个Project-E工程,而Project-E工程有80%的依赖在Project-Parent工程中可以找到,另外那20%的依赖是个性化的;也就是说,Project-Parent工程中有20%的依赖对于Project-E工程来说是没有用的。这个时候如果Project-E工程继承Project-Parent工程,则会引入无用的依赖,违背了Maven的使用原则。对于这种场景,Maven中也有很完美的解决方案。

Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。现在将Project-Parent工程的POM文件修改如下:

<?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.jellythink.ExtendDemo</groupId>
  <artifactId>Project-Parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>Project-Parent</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <springframework.version>5.1.6.RELEASE</springframework.version>
        <junit.version>4.11</junit.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${springframework.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

这里使用dependencyManagement,将springframework和junit依赖提取了出来,放到了Project-Parent工程中。这样声明的依赖既不会给Project-Parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。现在,我们将Project-C工程的POM修改为以下内容:

<?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>

  <parent>
    <groupId>com.jellythink.ExtendDemo</groupId>
    <artifactId>Project-Parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../Project-Parent/pom.xml</relativePath>
  </parent>

  <artifactId>Project-C</artifactId>

  <name>Project-C</name>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
    </dependency>
  </dependencies>
</project>

Project-D项目和Project-C类似,这里不再累述。上面的POM中依赖配置比原来简单了不少,所有的springframework依赖只配置了groupId和artifactId,省去了version。这些依赖的配置都在Project-Parent工程中有了配置,子模块只需要简单的配置groupId和artifactId即可。如果在子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。

插件管理

Maven提供了dependencyManagement元素帮助管理依赖,类似地,Maven也提供了pluginManagement元素帮助管理插件。同dependencyManagement一样,在pluginManagement元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。

总结

到这里,关于Maven中的继承概念就总结完了。其实关于Maven中的聚合和继承,还有一些比较冷门的知识点,我这里没有总结,比如反应堆的裁剪等,如果比较感兴趣,可以自行去学习。

期待五一假期!

果冻想,玩代码,玩技术!

2019年4月27日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之聚合

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

通过前面几篇文章,我把Maven中的坐标仓库依赖生命周期插件这五大核心概念进行了总结,掌握了这五大核心概念,基本上也就把住了Maven的脉。从这篇文章开始,我们将从实际的应用场景出发,总结Maven的一些比较常见的用户,通过这些常见的用户来更好的把握Maven,学习Maven。

我们的需求

在前后台分离,微服务大行其道的今天,我们的应用不再是一个超级大的包了,而是分成了多个子项目模块,每个子项目模块都是一个单独的工程项目。这就出现应用多的情况,此时如果你负责一个功能开发时,需要修改多个子项目模块时,就需要去修改不同的工程项目,当你开发完进行联调时,就需要一次构建多个工程项目,当出现问题时,又可能同时修改多个工程项目,此时你是一个一个工程项目的去构建呢?还是希望有一种办法一次性可以构建多个工程项目。

当然了,我们肯定希望存在一种办法,我们通过点击某个按钮就可以开始构建多个工程项目,然后我们去喝杯咖啡的。那我们的这种需求在Maven中是否能实现呢?毫无疑问,Maven是可以搞定这个问题的,这就是Maven中的聚合,通过聚合我们就可以解决这个痛点问题。下面我就通过实际的项目来说说Maven中的聚合到底是个什么鬼。

聚合实战

现在我准备了两个基于Maven的子工程项目,分别是Project-A和Project-B。这两个项目都可以单独编译,单独构建。但是为了能够使用一条命令就可以构建Project-A和Project-B这两个子工程项目,我们需要创建一个额外的名为Project-Aggregator的工程项目,然后通过该模块构建整个项目的所有模块。由于这个Project-Aggregator的工程项目是一个聚合项目,它是不需要src和test目录的,只需要有一个POM就OK了,下面就是这个Project-Aggregator工程项目的POM文件内容:

<?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.jellythink.AggregatorDemo</groupId>
  <artifactId>Project-Aggregator</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Project-Aggregator</name>

  <modules>
    <module>../Project-A</module>
    <module>../Project-B</module>
  </modules>
</project>

接下来,我们在Project-Aggregator工程项目目录下执行mvn clean package命令,就会看到以下输出:

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Project-A
[INFO] Project-B
[INFO] Project-Aggregator
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Project-A 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ Project-A ---
[INFO] Deleting E:\Code\Spring\Project-A\target
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ Project-A ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\Code\Spring\Project-A\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ Project-A ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\Code\Spring\Project-A\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ Project-A ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\Code\Spring\Project-A\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ Project-A ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\Code\Spring\Project-A\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ Project-A ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.jellythink.AggregatorDemo.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s - in com.jellythink.AggregatorDemo.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ Project-A ---
[INFO] Building jar: E:\Code\Spring\Project-A\target\Project-A-1.0-SNAPSHOT.jar
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Project-B 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ Project-B ---
[INFO] Deleting E:\Code\Spring\Project-B\target
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ Project-B ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\Code\Spring\Project-B\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ Project-B ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\Code\Spring\Project-B\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ Project-B ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\Code\Spring\Project-B\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ Project-B ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\Code\Spring\Project-B\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ Project-B ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.jellythink.AggregatorDemo.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.046 s - in com.jellythink.AggregatorDemo.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ Project-B ---
[INFO] Building jar: E:\Code\Spring\Project-B\target\Project-B-1.0-SNAPSHOT.jar
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Project-Aggregator 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ Project-Aggregator ---
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Project-A .......................................... SUCCESS [  3.513 s]
[INFO] Project-B .......................................... SUCCESS [  1.424 s]
[INFO] Project-Aggregator ................................. SUCCESS [  0.047 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.093 s
[INFO] Finished at: 2019-04-22T21:54:08+08:00
[INFO] Final Memory: 19M/274M
[INFO] ------------------------------------------------------------------------

从输出可以看到,我们在构建Project-Aggregator项目时,Project-A和Project-B就会一同被构建,在存在多个项目时,这是非常方便的。

总结

对于聚合,内容不多,但是这种使用手法在实际工作中我们也会碰到,以后你在碰到这种用法时,不要疑惑。希望这篇文章能够帮助到你,更加丰富你的Maven知识,让你对Maven的使用有一个更新的认识。

果冻想,玩代码,玩技术!

2019年4月22日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之插件

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

上一篇《Maven基础教程之生命周期》中也讲到了,Maven中通过模板方法这样的设计模式,生命周期只“立牌坊”,而实际上是插件在背后默默的奉献着。也就是说,在Maven中,真正的完成生命周期中那些阶段该干的活,都是由插件来做的。所以,从上面的描述,大家也能感受到Maven中插件的重要性了;插件非常重要,所以它的知识点肯定也少不了,对吧;然而,我的这篇关于插件的文章,我并不打算长篇大论的铺开来将Maven中的插件,因为我觉的有些东西,并不是那样的重要,所以,这篇关于插件的文章,点到为止,意会即可!

插件目标?

在继续下面的总结之前,我们需要先来理解“插件目标”这个概念。根据我们的开发经验,在开发一个软件时,这个软件肯定会包含很多的功能,并不会一个功能搞一个软件;同理,Maven中的插件也是这样的,比如下图所示:

Maven插件目标

而这里的每一个功能就对应一个插件目标。比如maven-dependency-plugin插件有十多个目标,每个目标对应一个功能。在《Maven基础教程之依赖》中讲到的dependency:listdependency:treedependency:analyze这些都是插件目标,这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。

插件绑定

说完了插件目标,那这个插件目标到底如何使用呢?上一篇说了生命周期,这个生命周期和插件是相互独立存在的,如果需要插件完成生命周期对应阶段的任何,那就需要将生命周期与插件相互绑定,也就是说需要将生命周期的阶段与插件的目标相互绑定,这样才能完成某个具体的构建任务。

Maven插件绑定

就如上图所示,default生命周期的compile阶段与maven-compiler-plugin插件的compile插件目标,到时候,就由compile插件目标完成default生命周期的compile阶段对应的实际操作。

关于插件绑定,主要分为内置绑定和自定义绑定两大类。

  • 内置绑定
    我们都知道,为了让Maven开箱即用,Maven开发了很多默认的插件来完成每个生命周期对于阶段的一些工作,同时,也将这些生命周期的一些主要的阶段和这些默认插件的插件目标进行了绑定,这就是内置绑定。对于内置绑定,我们知道有这么一回事,知道Maven的实现原理即可,至于哪个插件的插件目标和那个生命周期的阶段进行内置绑定,有了问题再查也OK的。
  • 自定义绑定
    为了能补充内置绑定的不足,完成更多个性化的任务,Maven社区的大牛开发了很多的插件,当然了,我们自己也可以开发,后面会讲到的。那这些插件如何和Maven的生命周期的阶段进行绑定呢,这就是我们要说的自定义绑定。下面我们通过一个例子来说明自定义绑定:

    <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.1</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>shade</goal>
                </goals>
                <configuration>
                  <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                      <mainClass>com.jellythink.HelloWorld.App</mainClass>
                    </transformer>
                  </transformers>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
    </build>

    在POM的build元素下的plugins子元素中声明插件的使用,插件在Maven中同其它包一样,也是作为独立的构建存在,所以也需要通过指定groupIdartifactIdversion这三个坐标元素去仓库中定位插件。除了基本的插件坐标声明外,还有插件执行配置,executions下每个execution子元素可以用来配置执行一个任务。上述例子中通过phase配置,将其绑定到package生命周期阶段上,再通过goals配置指定要执行的插件目标,这样自定义插件绑定就完成了。

    执行mvn clean install命令,我们就可以看到这样的输出:

    [INFO] --- maven-shade-plugin:3.2.1:shade (default) @ hello-world ---
    [INFO] Replacing original artifact with shaded artifact.
    [INFO] Replacing E:\Code\Spring\helloworld\target\hello-world-1.0-SNAPSHOT.jar with E:\Code\Spring\helloworld\target\hello-world-1.0-SNAPSHOT-shaded.jar

    可以看到执行了maven-shade-plugin插件的shade插件目标。

    有的时候,你会看到有的插件不通过phase元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。这个时候也不要惊讶,这主要是很多插件的目标在编写时已经定义了默认绑定阶段,我们可以通过maven-help-plugin查看插件的详细信息,了解插件目标的默认绑定阶段。执行mvn help:describe -Dplugin=org.apache.maven.plugins:maven-shade-plugin:3.2.1 -Ddetail就可以看到插件的完整信息,比如这个插件有几个插件目标,有哪些参数,默认绑定阶段等,通过查找Bound to phase: package,我们就可以看到默认绑定到哪个阶段。

插件配置

我们在实现一个功能时,也会想着通过传递参数来实现更强大的功能。Maven插件也是这样的,我们可以配置插件目标的参数,满足我们对插件更加个性化的要求。对于插件的参数配置,有以下两种常用方式:

  • 通过命令行进行插件配置
    我们经常看到以下这个命令:

    mvn clean install -Dmaven.test.skip=true

    这个就是典型的通过命令行进行插件配置,maven.test.skip是maven-surefire-plugin提供的一个参数,我们通过命令行传入一个true参数,表示跳过执行测试。

    参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性。Maven简单的重用了该参数。

  • 通过POM文件进行插件全局配置
    比如有些参数配置,从项目创建到项目发布都不会改变,或者基本上很少改变。对于这种场景,就更适合通过POM文件进行插件配置。比如以下的配置我们经常在一些项目中看到:

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

    这样就是一个全局的配置,也就是说,所有使用该插件目标的任务,都会使用这些配置。再回头看看上面插件绑定中自定义绑定里的那个例子,那个参数就是插件特有的参数,我们就可以通过参数实现一个不一样的功能。

总结

Maven中最后一个核心概念终于总结完了。集齐了Maven中坐标、仓库、依赖、生命周期和插件这五大核心概念,应该就对Maven有一个比较全面的理解和认识了。这篇对Maven中的插件进行了比较全面的理解,当然了还有一些用的很少的知识点,或者我认为即使你不知道这些知识点也不会影响你对Maven插件的理解,在这里没有进行总结,如果你感兴趣,也可以自己阅读以下《Maven实战》这本书,你也许会有不同的收获。Maven的插件总结就到此完毕,接下来的文章将偏重于Maven的使用实战,感谢大家的一路支持,我们下一篇再见~祝好!

果冻想,玩代码,玩技术!

2019年4月17日,于内蒙古呼和浩特。


图片描述

查看原文

赞 1 收藏 1 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之生命周期

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

在Maven中,核心概念一共有五个,包括前面已经总结完成的坐标、依赖和仓库,以及这里要讲的生命周期,还有将在下一篇要讲的插件。掌握了这五个核心概念,也就基本上把握了Maven的命脉。不夸张的说,把握了这五大核心概念,你可以自豪的说你在Maven的掌握上,超过了80%的人。而除了这五大概念,我总结的其它Maven系列的文章,无法就是Maven的具体场景应用,大体上都离不开这五大核心概念。

废话不多说,开始总结今天的总结!

Maven生命周期是什么?

人生老病死,这是一个生命周期。在Maven中,也有一个生命周期的概念。不管是刚刚入门的开发菜鸟,还是做了多年开发的大佬,每天干的工作无非就是对自己负责的项目进行清理、编译、测试和部署。虽然大家每天都在做这些工作,但是公司和公司之间、项目与项目之间,往往使用不同的方式做这些工作。有的是手工来搞定这些,有的人可能会聪明一些,写一些自动化脚本来搞定这些。不管大家怎么搞,都是能满足自己当下的工作需要,很好的完成自己当下的工作。可能换了个公司,或者换了个项目,把自己之前写的脚本,改吧改吧,接着来。你改吧改吧,能用就好,无可厚非,但是的确很麻烦,搞不好改了半天,发现之前的脚本不好用,可能还要重写。是的,你的痛点问题,Maven也知道,Maven说了,它来帮你搞定这些问题。所以就提出了Maven生命周期的概念。

Maven生命周期就是为了对所有的构建过程进行抽象和统一,开发了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。换句话说,几乎所有项目的构建,都能映射到这样一个生命周期上。

Maven的生命周期是抽象的,也就是说生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务都交给插件来完成。这种思想与设计模式中的模板方法非常相似。对模板方法设计模式不清楚的伙计可以看这里。Maven采用这样的设计,既保证了Maven整体框架的轻便,也最大程度的扩展性。

Maven生命周期抽象了构建的各个步骤,明确了它们的逻辑次序,但没有提供具体的实现。Maven通过插件机制,这些插件来完成实际的工作,同时每个构建步骤都可以绑定一个或者多个插件行为。为了让Maven开箱即用,Maven为大多数构建步骤编写并绑定了默认插件。比如针对编译的插件有maven-compiler-plugin,针对测试的插件有maven-surefire-plugin等。虽然在大多数时间里,用户几乎都不会感觉到插件的存在,而Maven如此的强大,都是因为在幕后有功能强大的插件,这一切实际的工作都是由这些插件来完成的。

通过Maven定义的生命周期和插件机制保证了所有Maven项目有一致的构建标准,简化了项目的构建工作。

详解Maven生命周期

在Maven中,有三套相互独立的生命周期,分别是cleandefaultsite

  • clean:clean生命周期的目的是清理项目;
  • default:default生命周期的目的是构建项目;
  • site:site生命周期的目的是建立项目站点。

每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。我们和Maven最直接的交互方式就是通过调用这些生命周期阶段。以clean生命周期为例,它包含的阶段有pre-cleancleanpost-clean。当我们调用pre-clean的时候,只有pre-clean阶段后执行;当我们调用clean的时候,pre-cleanclean阶段会按顺序执行;当我们调用post-clean的时候,pre-cleancleanpost-clean都会按顺序执行。

和生命周期阶段的前后依赖关系相比,cleandefaultsite这三套生命周期本身是相互独立的,我们可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其它生命周期产生任何影响。

Maven生命周期

常用命令详解

在《Maven基础教程之使用入门》这篇文章中,讲到了一些通过命令行来编译、测试和打包程序的命令,现在总结完了生命周期,再回过头去看这些命令,你将会有更深刻的认识。

  • mvn clean:调用实际插件完成clean生命周期的clean阶段的操作,实际调用的是pre-cleanclean两个阶段;
  • mvn test :调用default生命周期对应的阶段的插件,完成从validatetest阶段的所有操作;
  • mvn clean install:调用clean周期的clean阶段和defaultinstall阶段,实际调用的是pre-cleanclean以及validateinstall阶段;
  • mvn clean deploy site-deploy:调用完整的三个生命周期所有阶段(post-clean不被调用)。

总结

理论性的东西还是蛮多的,而这些知识点是我们后续用好Maven的基础,希望大家通过这里总结的内容,能对Maven中的生命周期这个重要概念有所理解。下一篇将重点总结插件,下一篇再见。

果冻想,玩代码,玩技术!

2019年4月14日,于内蒙古呼和浩特。


图片描述

查看原文

赞 0 收藏 0 评论 0

果冻 发布了文章 · 2019-10-06

Maven基础教程之仓库

这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

在前面的几篇关于Maven的总结中,都说到只要指定了groupIdartifactIdversion坐标信息,就可以从中央仓库中找到对应的jar包。等一下,仓库?大家肯定会问,仓库是什么?这个仓库在哪里?为什么Maven会自动去那个仓库找我要的jar包呢?好的,我知道大家肯定有一堆的疑问,一头雾水,这篇文章就来解决大家的这些疑问,拨开疑雾,对这个“仓库”一探究竟。跟着我的步伐,Let's go!

Maven仓库是什么?

现在大家想一下之间开发的非Maven项目,是不是在每个项目下面都有一个lib目录。是的,你不用去翻看你以前做的项目了,没有错,没有Maven之前,我们项目依赖的包,我们都会下载下来,统一放到对应项目的lib目录下去。同一个包,比如Spring框架的包,项目A要使用,就拷贝一份到项目A的lib目录下去;项目B也要使用,那就再拷贝一份到项目B的目录下去。这样下去,你会发现同样的依赖包,需要拷贝N份,这样不仅造成了磁盘空间的浪费,而且也难于统一管理。

现在好了,有了Maven,基于Maven的坐标机制,任何Maven项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven可以在某个位置统一存储所有Maven项目共享的包,而这个统一存放依赖包的位置就是仓库。说白了,Maven仓库就是存放依赖包的地方。

有了这个Maven仓库后,上面的问题就有了一个完美的解决方案。基于Maven开发的项目不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候,Maven会自动根据坐标找到仓库中的包,并正确使用它们。

仓库的分类

在使用Maven的过程中,我们需要知道Maven去哪里找那个所谓的“仓库”,从而加载依赖。所以,我们就需要知道Maven仓库的分类。

在Maven中,仓库分为以下两类:

  • 本地仓库
  • 远程仓库

Maven根据依赖坐标去仓库中找对应的包,是遵循这样的一个轨迹:

  1. 首先去本地仓库查找,如果本地仓库有对应的依赖包,则直接就使用;
  2. 如果本地仓库不存在对应包时,或者需要查看是否有更新的包版本时,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库在使用;
  3. 如果本地仓库和远程仓库都没有需要的包,Maven就会报错。

上面简单将Maven仓库进行的分类,但是对于远程仓库,它又分为好几种:

Maven仓库分类

  • 本地仓库
    上面也说到了Maven根据依赖坐标去仓库中找对应的包是有遵循的轨迹的。Maven最开始都是从本地仓库寻找依赖的包。默认情况下,不管是在Windows还是在Linux上,每个用户在自己的用户目录下都有一个路径名为.m2/repository的仓库目录。这个就是默认的本地仓库地址。

    有的时候,用户可能会自定义本地仓库的目录地址(我一般都会这么干)。此时,可以通过编辑~/.m2/settings.xml,设置localRepository元素的值就OK了,比如我的是这样子的:

    <localRepository>E:/repository</localRepository>

    这样,该用户的本地仓库地址就被设置成了E:/repository。

  • 中央仓库
    由于最开始的本地仓库是空的,Maven必须知道至少一个可用的远程仓库,这样才能在执行Maven命令的时候下载到需要的构件。中央仓库就是这样一个默认的远程仓库,Maven的安装文件中自带了中央仓库的配置。使用解压缩工具打开$M2_HOME/lib/maven-model-builder-3.5.0.jar文件,在org\apache\maven\model目录下有一个pom-4.0.0.xml文件,该文件里面有这么一段代码,它配置了默认的中央仓库:

    <repositories>
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    pom-4.0.0.xml文件是所有Maven项目都会继承的超级POM,这段配置使用id central对中央仓库进行唯一标识,同时设置snapshots元素,其子元素enabled的值为false,表示不从该中央仓库下载快照版本的包。

    中央仓库是一个大而全的包仓库,它包含了这个世界上绝大多数流行的开源Java包,以及源码等信息。一般来说,一个简单Maven项目所需要的依赖包都能从中央仓库下载到,这也就解释了为什么Maven能做到“开箱即用”。

  • 私服
    玩游戏的时候,经常会听到私服。但是在学习Maven的时候,也听到私服,这个就比较特殊了。在Maven中,私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。整体架构如下图所示:

    Maven私服

    当Maven需要下载依赖包的时候,它从私服请求,如果私服上不存在该依赖包,则从外部的远程仓库下载,缓存到私服上之后,再为Maven的下载请求提供服务。另外,一些无法从外部仓库下载到的依赖包也能从本地上传到私服上供大家使用。

    为啥要用私服呢?肯定是有少好处的。像在我们公司,在全国31个省都有分公司,同时总部研发中心还会开发一堆的公共JAR包,给31个分公司使用,这样通过私服就可以很好的解决研发中心和分公司之间的公共包分发等问题。对于使用私服,它有以下这些优点:

    1. 加快Maven构建;我们知道,不停的连接外部仓库下载依赖包是一件非常耗费时间的事情,而私服部署在局域网,则可以大大的降低依赖包的下载时间,提高Maven构建效率;
    2. 部署第三方包;比如我们公司的研发中心,会开发很多公共的包,而这些包又无法上传至中央仓库,所以这些包部署在私服就再适合不过了。

远程仓库配置

没有一个平台能够大而全到包含所有的东西,同理,中央仓库也是这样的,虽然它包含了我们需要的大部分的依赖包,但是还是有一些包在中央仓库中是找不到的。这个时候,我们就需要配置一些其它远程仓库来补充中央仓库中没有的依赖包,与中央仓库配合完成工作,当中央仓库也没有对应的依赖包时,Maven则遍历所有的远程仓库。

我们需要在pom.xml中配置即可,比如这样:

<project>
    ......

    <!-- 配置远程仓库 -->
    <repositories>
        <repository>
            <id>jboss</id>
            <name>JBoss Repository</name>
            <url>http://repository.jboss.com/maven2/</url>
            <releases>
                <enabled>true</enabled>
                <updatePolicy>daily</updatePolicy>
            </releases>
            <snapshots>
                <enabled>false</enabled>
                <checksumPolicy>warn</checksumPolicy>
            </snapshots>
            <layout>default</layout>
        </repository>
    </repositories>
    ......
</project>
  • repository:在repositories元素下,可以使用repository子元素声明一个或者多个远程仓库。
  • id:仓库声明的唯一id,尤其需要注意的是,Maven自带的中央仓库使用的id为central,如果其他仓库声明也使用该id,就会覆盖中央仓库的配置。
  • name:仓库的名称,让我们直观方便的知道仓库是哪个,暂时没发现其他太大的含义。
  • url:指向了仓库的地址,一般来说,该地址都基于http协议,Maven用户都可以在浏览器中打开仓库地址浏览构件。
  • releasessnapshots:用来控制Maven对于发布版构件和快照版构件的下载权限。需要注意的是enabled子元素,该例中releases的enabled值为true,表示开启JBoss仓库的发布版本下载支持,而snapshots的enabled值为false,表示关闭JBoss仓库的快照版本的下载支持。根据该配置,Maven只会从JBoss仓库下载发布版的构件,而不会下载快照版的构件。
  • layout:元素值default表示仓库的布局是Maven2及Maven3的默认布局,而不是Maven1的布局。基本不会用到Maven1的布局。
  • 其他:对于releases和snapshots来说,除了enabled,它们还包含另外两个子元素updatePolicy和checksumPolicy。

    元素updatePolicy用来配置Maven从远处仓库检查更新的频率,默认值是daily,表示Maven每天检查一次。其他可用的值包括:never-从不检查更新;always-每次构建都检查更新;interval:X-每隔X分钟检查一次更新(X为任意整数)。

    元素checksumPolicy用来配置Maven检查校验和文件的策略。当构建被部署到Maven仓库中时,会同时部署对应的检验和文件。在下载构件的时候,Maven会验证校验和文件,如果校验和验证失败,当checksumPolicy的值为默认的warn时,Maven会在执行构建时输出警告信息,其他可用的值包括:fail-Maven遇到校验和错误就让构建失败;ignore-使Maven完全忽略校验和错误。

大部分公共的远程仓库无须认证就可以直接访问,但我们在平时的开发中往往会架设自己的Maven远程仓库,出于安全方面的考虑,我们需要提供认证信息才能访问这样的远程仓库。配置认证信息和配置远程仓库不同,远程仓库可以直接在pom.xml中配置,但是认证信息必须配置在settings.xml文件中。这是因为pom往往是被提交到代码仓库中供所有成员访问的,而settings.xml一般只存在于本机。因此,在settings.xml中配置认证信息更为安全。比如这样配置:

<servers>
    <server>
        <id>deploymentRepo</id>
        <username>repouser</username>
        <password>repopwd</password>
    </server>
</servers>

这里的关键是id元素,settings.xml中server元素的id必须与pom.xml中需要认证的repository元素的id完全一致。正是这个id将认证信息与仓库配置联系在了一起。

部署至远程仓库

很多时候,我们编译完成后,会将我们的负责的模块包部署至私服,以供其它团队成员使用。那如何将我们的包部署到远程仓库呢?

我们配置项目的pom.xml文件即可,配置如下所示:

<project>
    ......
    <distributionManagement>
        <repository>
            <id>releases</id>
            <name>public</name>
            <url>http://59.50.95.66:8081/nexus/content/repositories/releases</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <name>Snapshots</name>
            <url>http://59.50.95.66:8081/nexus/content/repositories/snapshots</url>
        </snapshotRepository>
    </distributionManagement>
    ......
</project>

distributionManagement包含repository和snapshotRepository子元素,前者表示发布版本(稳定版本)构件的仓库,后者表示快照版本(开发测试版本)的仓库。

配置正确后,运行mvn clean deploy命令,Maven就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本的仓库地址,否则就部署到发布版本的仓库地址。

快照版本

如果去你的本地仓库,你可能会看到以这种类似于1.0.0、1.3-alpha-1、2.0、2.1-SNAPSHOT或者2.1-20190412.221213-52这样的版本号命名的JAR包。其中,1.0.0、1.3-alpha-1和2.0是稳定的发布版本,而2.1-SNAPSHOT和2.1-20190412.221213-52是不稳定的快照版本。

Maven为什么要区分发布版本和快照版本呢?为什么还要有2.1-SNAPSHOT和2.1-20190412.221213-52这样的命名区分呢?

现在我们来思考一种现实的开发场景。现在软件开发都是多人分模块开发,比如小明开发A模块,你开发B模块,而你的B模块是需要依赖A模块的,在开发过程中,小明需要经常将自己最新的代码提交编译,生成A模块包,交给你,供你开发和集成调试。这种开发场景,如何完美的实现多人协同开发、模块开发呢?

你可能想你自己每次单独从代码更把A模块代码Pull下来,自己编译,生成自己需要的依赖包。这么做没有问题,但是带来的问题是你需要去拉代码,还要去编译别人的模块,如果编译不顺利,各种错误,你可能一脸懵逼,只能去找小明解决了。这样无形的就把工作流程搞砸了,你本来就只想要一个A模块的包,但是你却干了一堆不相干的事情,浪费时间。

你可能又想,小明每次将A模块编译完成后,使用mvn clean deploy命令部署到私服,这样你在编译你的模块时,Maven就会自动的去私服下载对应的依赖包。想起来挺美的,但是在Maven中,同样的版本和同样的坐标就意味着同样的包,所以,如果你的本地仓库中已经包含了模块A的某个版本的包,Maven就不会再对照远程仓库进行更新。除非你每次执行Maven命令之前,清除本地仓库,但这种要求手工干预的做法显然也是不可取的。

此时你可能又想,小明不断的修改A模块的版本号,你也按照小明提供的版本号,修改A模块的依赖版本。是的,这样是可以的,这就需要你和小明频繁的修改POM,如果有更多的模块依赖,这个小问题就会变成一个大问题,而且还很容易出错。

Maven的快照机制就是为了解决上述问题。在这个开发场景中,小明只需要将模块A的版本设定为2.1-SNAPSHOT,然后发布到私服中,在发布的过程中,Maven会自动为包打上时间戳。比如上面的2.1-20190412.221213-52就表示2019年4月12日22点12分13秒的第52次快照。有了这个时间戳,Maven就能随时找到仓库中该包2.1-SNAPSHOT版本最新的文件。当你编译B模块时,Maven会自动从仓库中检查模块A模块的2.1-SNAPSHOT的最新输出包,当发现有更新时便进行下载。

基于快照版本机制,在不需要额外手工操作的情况下,就能完美的解决上述问题。在知道了快照的原理之后,我们的项目不应该依赖任何快照版本的依赖包,由于快照版本的不稳定性,这样的依赖会造成潜在的危险。

镜像

如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。也就是说,任何一个可以从仓库Y获得的依赖包,都能够从它的镜像中获取。比如http://maven.aliyun.com/nexus/content/groups/public是阿里提供的中央仓库的镜像,由于地理位置等其它因素,该镜像往往能够提供比中央仓库更快的服务。因此,一般情况下,我们会配置镜像来替代中央仓库。编辑settings.xml文件即可,比如配置阿里提供的镜像:

<mirrors>
    <mirror>
        <id>alimaven</id>  
        <name>aliyun maven</name>  
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>  
        <mirrorOf>central</mirrorOf>    
    </mirror> 
</mirrors>

mirrorOf的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像;idnameurl的配置与一般仓库配置一样,表示该镜像仓库的唯一标识符、名称以及地址。

总结

回头一看,Maven仓库的知识点这么多。这篇文章主要以理解性的知识点为主,很多内容都参考了网上的其它博文。这里的知识点你不一定在使用Maven的过程中都会用到,但是理解了,就让你以后的工作更加得心应手。不求都会,但求上面的内容你都看一遍,心中对Maven仓库的概念和相关知识点有一个比较全面的理解和认识。全文读完,用不了十分钟。用十分钟学习一个知识点,还是非常值得的。

果冻想,玩代码,玩技术!

2019年4月13日,于内蒙古呼和浩特。


微信扫码关注公众号

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 21 次点赞
  • 获得 9 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 8 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-01-20
个人主页被 815 人浏览