Maven POM文件常用标签解释

本文档仅描述POM文件中最常用的一部分标签,完整的标签说明请查阅官方文档
另外,项目查找依赖包的优先级:local > setting-profiles > pom-repositories > setting-mirror/central

基本标签

Label Description
modelVersion 声明所使用POM模型的版本
parent 项目的父模块,需要填写父模块的groupIdartifactIdversion;另外relativePath是设置父模块的位置,设置后会优先查找该目录而非Maven仓库
groupId 模块所属的group的唯一id,设置parent后可以不填groupId,会默认使用parentgroupId
artifactId 本模块的唯一ID
version artifact的版本号
packaging 本模块打包类型,可选jarpomwarear,默认为jar
name/url/description 为描述项目而设置的标签,非必须

- modules

列出本项目的子模块,可使用相对路径。

- repositories

Label Description
releases 设置是否下载release的包
snapshots 设置是否下载snapshot的包,一般公共仓库都设置为false
id 仓库ID,需要和setting文件中的server的ID配置相同
name 仓库名称
url 仓库的地址
layout Maven1的layoutlegacy,默认是default

- pluginRepositories

配置基本和远程一样,但是查找插件时会到这里的插件仓库照而不是到repositories配置的仓库中找。

- distributionManagement

通过deploy命令把构建的包分发到远程仓库。

Label Description
snapshotRepository 快照仓库
repository release仓库
id/name/url 仓库的配置

此外,一般还需要在setting文件配置对应id的server:

1
2
3
4
5
6
7
8
<servers>
<server>
<id>repo1</id>
<username>repouser</username>
<password>my_login_password</password>
<!-- To encrypt passwords: http://maven.apache.org/guides/mini/guide-encryption.html -->
</server>
</servers>

- dependencyManagement

负责管理相关依赖的版本等信息。本标签下的dependency只管理信息而不直接加载依赖,当本POM文件或者继承的POM文件中的dependencies标签中的dependency只指定了groupId和artifactId而没有描述version、scope等信息,就会默认从dependencyManagement中查找相关信息。

- dependencies

负责加载项目的相关依赖。

Label Description
groupId 依赖包所在group的ID
artifactId 依赖包的ID
version 依赖包的版本号
groupId 依赖包所在group的ID
exclusions 配置此项来排除依赖,去除本依赖包所依赖的其他包
classifier 用来指定依赖包有多个输出包时应该使用哪一个,如有多个由不同jdk编译出来的版本时需要指定使用哪一个版本
systemPath 当依赖包在本地而非仓库时,sytemPath指出了jar包的路径
type 指出所依赖的包的类型,默认为jar
optional 表示其他项目依赖本项目时,是否传递该依赖。true表示不传递,默认为false
scope 打包时对依赖包的不同操作模式

下面是scope的几种模式:

  • compile: 默认值,表示当前依赖需要参与到项目的编译、测试、运行,打包时会被include进去;
  • test: 表示当前依赖仅参与测试的工作,包括测试代码的编译和执行,打包时不会被include进去;
  • runtime: 表示当前依赖不参与项目的编译,但会参与代码的测试和执行,打包时会被include进去;
  • provided: 相当于compile,但打包时不会include该依赖,即使运行需要也是由外部环境提供;
  • system: 相当于provided,但是该依赖由本地文件系统提供,需要添加systemPath定义所在路径;

- plugins

Maven本质上是一个插件框架,它的核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成。通过plugins的配置可以完成一些额外的构建任务,或者修改构建过程所使用的插件的版本。

- profiles

指定基于不同环境参数的构建所使用的各种参数的值。

- build

指定构建过程中的各种自定义工作。

Label Description
sourceDirectory Java文件目录
testSourceDirectory 测试Java文件目录
resources 资源文件目录
testResources 测试资源文件
outputDirectory 源文件输入出目录
testOutputDirectory 测试文件输出目录
finalName 指出最终打包的包名
defaultGoal 指定默认的参数,当执行maven命令时没有指定参数是使用默认参数,如compileinstall
filters 使用配置文件中的值替换项目中的占位符

- properties

通过key-value的方式自定义一些参数。

Linux上配置Java环境

说明

  • 通过yum、apt等包管理工具安装的一般是openjdk,如果需要oracle jdk的话需要到oracle官网下载包进行安装;
  • 安装Java环境一般分为两种,安装JDK和安装JRE:
    • 如果不需要进行开发、编译等操作,只用Java环境来运行一些Jar、War包的软件的话可以只安装JRE环境;
    • JDK额外提供了一些开发工具包,如果需要进行编译操作,如使用javac、Maven等执行编译,则应该安装JDK环境;

配置过程

此处以安装oracle jdk为例:

  • 下载

    • 官方下载链接需要认证,不能直接用wget下载,可以先通过浏览器下载再上传到对应位置;
  • 解压

    • 解压命令:tar -zxvf jdk-8u201-linux-x64.tar.gz
    • 移动到对应目录,一般可以放在/usr/local/目录下;
  • 环境配置

    • 编辑配置文件: vim /etc/profile

    • 添加以下配置(根据实际情况修改路径):

      1
      2
      export JAVA_HOME=/usr/local/jdk1.8.0_201
      export PATH=$JAVA_HOME/bin:$PATH

      说明:Java1.5版本以后就不需要配置CLASSPATH了。

  • 使环境变量配置生效并检验是否配置成功

    • 使用source命令使之生效:source /etc/profile

    • 查看配置结果:echo $JAVA_HOME

      效果图

设计模式 - 单例模式

单例模式是指一个类只能构建一个对象的设计模式。单例模式根据构建对象的时间分为两类:启动时就构建好的饿汉式和初次被调用才构建的懒汉式。实现一个单例模式主要需要考虑的是线程安全问题,另外是否能懒加载和是否能防止反射构建也是需要考虑的一部分。

双重检测锁实现

双重检测锁是单例模式的一种实现,可以满足线程安全和懒加载的需要,不过不能防止利用反射来构建多个对象。

基本实现

首先我们从最简单的代码开始一步步构建双重检测锁的单例实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {} //构造函数私有化
private static Singleton instance = null; //单例对象

//静态初始化方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
  • 首先第一步,我们是把构造方法给私有化,防止外部随意调用构造方法构建对象;
  • instance便是我们的单例对象,因为是一个静态成员,赋值为null实现了懒加载,当然也可以写成new Singleton()实现饿汉式加载;
  • getInstance()是获取单例对象的方法,通过if (instance == null)这一步来确保对象只会被构建一次。

确保线程安全

上面的基本实现能保证在单线程下实现单例模式,但在多线程的情况下可能会出现一些问题:
刚开始instance还是等于null的时候,if (instance == null)这一步判断是为true,当线程A通过了这一步判断进入了分支里但还没来得及构建对象的时候,instance实际上还是为null,这时候线程B依然能通过判断进入分支体,于是就会出现多个线程同时在分支体里面,导致分支体里的instance = new Singleton();会被执行多次,构建了多次对象。

添加双重检测机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private Singleton() {} //构造函数私有化
private static Singleton instance = null; //单例对象

//静态初始化方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class) { //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
  • 通过添加同步锁synchronized的方式,保证同一时刻最多只会有一个线程在执行同步锁里的程序,保证instance = new Singleton();只会被执行一次;
  • 同步锁外的if (instance == null)判空是指当不是null的时候就直接跳过整个分支体,而不用等待完同步锁后才发现instance不为null,减少不必要的时间开销;
  • 同步锁里的if (instance == null)判空是为了防止线程A进入了锁里面还没来得及创建对象时,线程B就通过了外部判空开始等待锁的情况出现,这种机制就叫做双重检测锁。

禁止指令重排序

上面的实现看似保证了线程安全,但实际上并非如此,原因在于Java指令的重排序。
什么是指令重排序?上面构建对象部分的代码instance = new Singleton();,实际上会被编译器编译成以下指令:

  1. 分配对象需要的内存空间
  2. 在分配的内存中初始化对象
  3. instance指向上面分配到的内存地址

指令流水线并非是串行的,多条指令可以同时被执行,为了使性能更优,JVM和CPU可能会把这些指令进行优化重排序,导致出现以下的顺序:

  1. 分配对象需要的内存空间
  2. instance指向上面分配到的内存地址
  3. 在分配的内存中初始化对象

结果会导致这样一种情况:线程A正在执行instance = new Singleton(),获取了内存地址但是还没有初始化对象,而此时线程B在外部判空时发现instance不等于null,于是直接返回了instance,便造成线程B拿到了一个还没完成初始化的对象。

添加修饰符volatile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private Singleton() {} //构造函数私有化
private volatile static Singleton instance = null; //单例对象

//静态初始化方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class) { //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}

使用volatile修饰符禁止了指令重排序,保证指令是按顺序执行的,不会出现一种中间态。另外一提,volatile关键字除了禁止指令重排外,还能确保其修饰的值被修改后马上写回到主内存中,保证线程访问的该变量值是最新值。

至此为止,代码已经实现了线程安全且懒加载的单例模式,然而,硬要用反射来使构造函数可见,还是能构建多个对象的。

Git配置多用户

场景描述

对于使用Git仓库,我们可能需要在同一个系统中使用不同的邮箱账号进行开发工作,比如工作时需要使用工作邮箱和易于识别的名字,而社区则是使用私人邮箱和网络名称,如果只使用一套全局配置会很不方便。Git提供了处理这个问题的机制。

原理

基础配置本质上存在于两个配置文件中:

  • 全局配置,一般存放在用户目录下的.gitconfig文件中;
  • 局部配置,存放在仓库目录下的.git/config文件中;

优先级上,__局部配置大于全局配置__。Git提供的git config命令本质上就是改变这两个文件的配置,其中加了--global参数的命令改动的是全局配置,而没有加该参数的则是改变对应仓库的局部配置。因此我们可以先全局配置一个相对常用的用户,然后再在不使用该用户信息的仓库里进行局部配置。

相关操作

方法一:命令行操作

1
2
3
4
5
6
7
# 配置用户名和邮箱
git config [--global] user.name "xxx"
git config [--global] user.email "xxx"

# 取消全局设置
git config [--global] --unset user.name
git config [--global] --unset user.email

方法二:直接编辑配置文件

除了命令的方式外,当然也可以直接编辑.gitconfig或者.git/config

1
2
3
[user]
name = xxx
email = xxx

Linux命令行常用快捷键

光标位置移动

Shortcut Remark
*Ctrl+A 移动到行首
*Ctrl+E 移动到行末
Ctrl+B 向左边移动一个字符,同Left键
Ctrl+F 向右边移动一个字符,同Right键
*Ctrl+Left & Ctrl+Right 以单词为单位左右移动
Alt+B/Esc to B 向左边移动一个单词
Alt+F/Esc to F 向右边移动一个单词

编辑命令

Shortcut Remark
*Ctrl+L 清屏操作,作用同输入clear/reset
*Ctrl+U 删除光标前的输入(配合Ctrl+Y可粘贴删除的内容)
*Ctrl+K 删除光标后的输入(配合Ctrl+Y可粘贴删除的内容)
Ctrl+Y 粘贴被Ctrl+U/Ctrl+K 删掉的内容
Ctrl+W 往光标左边删除单词
Alt+D/Esc to D 往光标右边删除单词
Ctrl+T 交换光标左边的两个字符的位置

查找历史命令

Shortcut Remark
Ctrl+P 显示当前命令的上一条命令,同UP键
Ctrl+N 显示当前命令的下一条命令,同DOWN键
*Ctrl+R 开启历史命令搜索,Enter执行搜索结果,Esc显示搜索结果但不执行,再次输入Ctrl+R可以往前回溯符合s搜索条件的命令
*Ctrl+G 退出历史命令搜索

控制命令

Shortcut Remark
*Ctrl+S 阻止屏幕输出
*Ctrl+Q 允许屏幕输出
*Ctrl+C 终止命令,也可用于忽略当前输入直接跳到下一行
*Ctrl+Z 暂停命令,可以通过jobs查看作业,使用fg/bg加作业ID可前台/后台继续运行进程

Bang命令

Shortcut Remark
*!! 执行上一条命令
*!$ 添加上一条命令的最后一个参数
!-n 执行前n条命令
^xxx 删除上一条命令的xxx字符并执行
^xxx^yyy 替换上一条命令的xxx字符为yyy