一:Maven 简介与简单实用
Maven 是 Java 世界中最流行的项目构建工具之一。
说白了, Maven 的主要职责就是“包管理”。在没有像 Maven 这样的包管理工具之前,我们做一个 Java 项目,需要第三方依赖包要怎么做呢?我们需要将别人打好的 Jar 包下载到本地,然后手动指定给项目。这种操作十分麻烦,比如版本控制,我们用的第三方包需要使用新版本怎么办呢?只能重新进行下载,重新指定。
而使用了 Maven 项目构建工具后,需要什么第三方包,我们直接可以在 pom.xml 文件中添加几行 XML 代码,指定包名,版本等信息就可以了。另外,Maven 还提供了很多插件,比如常用的打包插件,调试插件等等,方便我们项目的开发和部署。
Maven 的生命周期与插件
1. 生命周期
Maven 拥有三套相互独立的生命周期:
- clean
- default
- site
clean 生命周期的目的是清理项目,default 生命周期的目的是构建项目,site 生命周期的目的是建立项目站点。
每个生命周期包含一些阶段(phase),这些阶段是有先后顺序的,并且后面的阶段依赖于前面的阶段,用户和 Maven 最直接的交互方式就是好调用这些生命周期的阶段。
三套生命周期本身是相互独立的,用户可以仅调用 clean 生命周期的某个阶段,或者仅仅调用 default 生命周期的某个阶段的时候不会触发其他生命周期的任何阶段。
clean 生命周期
clean 生命周期的目的是清理项目,它包含三个阶段:
- pre-clean:执行一些清理前需要完成的工作
- clean:清理上一次构建生成的文件
- post-clean:执行一些清理后需要完成的工作
default 生命周期
default 生命周期定义了真正构建项目时所需要执行的步骤,它是所有生命周期中最核心的部分,其包含的阶段如下:
- validate:验证工程是否正确,所需的信息是否完整
- initialize:初始化构建平台
- generate-sources
- process-sources
- generate-resources:
- process-resources:处理项目主资源文件,一般来说,是针对 src/main/resources 目录内容进行变量替换等工作后,复制到项目输出的主 classpath 目录中
- compile:编译项目的代码。一般来说,是编译 src/main/java 目录下的 java 文件至项目输出的主 classpath 目录中
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources:处理项目测试资源文件。一般来说,是对 src/test/resources 目录的内容进行变量替换工作后,复制到项目输出的测试 classpath 目录中
- test-compile:编译项目的测试代码。一般来说,是编译 src/test/java 目录下的 java 文件至项目输出的测试 classpath 目录中
- process-test-classes
- test:使用单元测试框架运行测试,测试代码不会被打包或者部署
- prepare-package
- package:接受编译好的代码,将工程文件打包为指定的格式,例如 Jar,War 等等
- pre-integration-test
- integration-test:集成测试
- post-integration-test
- verify:检查 package 是否有效,符合标准
- install:将包安装至 Maven 本地仓库,供本地其他的 Maven 项目使用
- deploy:将最终的包复制到远程仓库,供其他开发人员和 Maven 项目使用
site 生命周期
site 生命周期的目的是建立和发布项目站点, Maven 能够基于 pom 文件所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目。该生命周期包含阶段如下:
- pre-site:执行一些在生成项目站点之前需要完成的工作
- site:生成项目站点文档
- post-site:执行一些在生成项目站点之后需要完成的工作
- site-deploy:将生成的项目站点发布到服务器上
2. 插件与目标
Maven 三套生命周期定义的各个阶段我们已经大致介绍完毕了。
Maven 三套生命周期定义的各个阶段实际上是不会做任何工作的,这些实际的工作是由插件(plugin)来完成的,每个生命周期阶段都是由插件的目标(goal)来完成的。
Maven 的生命周期与插件相互绑定,用以完成实际的构建任务。Maven 的核心仅仅是定义了抽象的生命周期,然而具体的任务还是要由插件来完成,如果当前的生命周期阶段没有绑定任何插件的任何目标,那么这个阶段就什么都不会做。
例如项目编译这一任务,它对应了 default 生命周期的 compile 这一阶段,而 maven-compiler-plugin 这一插件的 compile 目标能够完成该任务。因此将它们互相绑定,就能够实现项目编译的目的。
为了让用户不用做任何配置就能构建 Maven 项目,Maven 为一些主要的生命周期阶段绑定了很多插件的目标,这些是内置绑定,当用户通过命令行调用生命周期阶段时,对应的插件目标就会执行相应的任务,一些内置绑定关系如下:
clean 生命周期阶段与插件目标的内置绑定:
生命周期阶段 | 插件目标 | 执行任务 |
---|---|---|
pre-clean | ||
clean | maven-clean-plugin:clean | 清理上一次构建生成的文件 |
post-clean |
default 生命周期阶段与插件目标的内置绑定:
生命周期阶段 | 插件目标 | 执行任务 |
---|---|---|
process-resources | maven-resources-plugin:resources | 复制主资源文件至主输出目录 |
compille | maven-compiler-plugin:compile | 编译主代码至主输出目录 |
process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件至测试输出目录 |
test-compile | maven-compiler-plugin:testCompile | 编译测试代码至测试输出目录 |
test | maven-surefire-plugin:test | 执行测试用例 |
package | maven-jar-plugin:jar | 创建项目jar包 |
install | maven-install-plugin:install | 将项目输出构件安装到本地仓库 |
deploy | maven-deploy-plugin:deploy | 将项目输出构件部署到远程仓库 |
default 生命周期还有很多的阶段,默认他们没有绑定任何插件,因此也没有任何的实际行为。
site 生命周期阶段与插件目标的内置绑定:
生命周期阶段 | 插件目标 | 执行任务 |
---|---|---|
pre-site | ||
site | maven-site-plugin:site | 生成项目站点文档 |
post-site | ||
site-deploy | maven-site-plugin:deploy | 将生成的项目站点发布到服务器上 |
除了内置绑定外,用户还能够自己选择将某个插件的目标绑定到生命周期的某个阶段上。该部分内容,后面会介绍到。
3. 执行一个 Maven 命令,发生了什么?
执行一个 Maven 的命令,例如 mvn test
这个命令,Maven 会将包含 test 这个阶段之前所有的阶段全部执行一遍!
如果该阶段没有绑定任何插件目标,那就什么也不执行。
拿一个经典面试题来举例:
问:
我们经常使用 “mvn clean package” 命令进行项目打包,请问该命令执行了哪些动作?
在这个命令中,我们调用了 Maven 的 clean 生命周期的 clean 阶段绑定的插件目标,以及 default 生命周期的 package 阶段以前所有阶段绑定的插件目标:
- maven-clean-plugin:clean ->
- maven-resources-plugin:resources ->
- maven-compiler-plugin:compile ->
- maven-resources-plugin:testResources ->
- maven-compiler-plugin:testCompile ->
- maven-surefire-plugin:test ->
- maven-jar-plugin:jar
二:IDEA 生产力翻倍的快捷键
- 万用键:强大的智能提示(option + enter)
- Search everywhere:双击 Shift
- 查看定义:Declaration (command + B)
- 查看文件:Navigate-File
- 高级查找:Find in Path (shift + command + F)
- 快速生成:Generate(control + enter)
- 格式化:Reformat Code(option + command + L)
- 优化导入语句:Optimize imports(control + option + O)
- 导航:Navigate-Back/Forward
- 谁调用了这个方法:Call Hierarchy(shift + command + H)
- 所有的实现类:Implementation
- 文件大纲:File Structure
- 下一处错误:Next Highlighted Error
- 高级重命名:Rename,批量重命名(shift + fn + F6)
- 调试器快捷键:F5/F6/F7/F8
- 注释多行代码:(command + /)
- 快速复制光标所在的那一行代码:(command + D)
- 快速删除光标所在的那一行代码:(command + X)
三:在 IDEA 中使用Git工具查找背锅侠
- 查找背锅侠:Annotate/Blame
- 查看当前文件的历史版本
- 高级筛选方式查找 commit 记录
- 显示差异:show diff
- Open in GitHub
四:其他 IDEA 的小功能
IDEA 中支持一些快捷生成代码的缩写
- psvm:快速生成 main 方法
- sout :
System.out.println()
- foreach :快捷生成
for(:){}
代码块 - fori : 快捷生成
for(int i = 0; i < ; i++){}
代码块
IDEA 也有非常方便的一些插件:
在 Preferences -> Plugins 里面,我们可以为自己的 IDEA 安装自己需要的插件
譬如:
- Lombok
- ASM Bytecode Viewer
- Maven Helper
- Mybatis Log Plugin
- … …
五:调试器入门与详解
为什么要使用调试器(debugger)
- 理解程序执行的过程
- 理解 JVM 的内部构造
- 非常方便地检查在任意时间点 JVM 的内部状态
调试器的使用:
- step over:快捷键 F8,单步跳步,每次执行一行
- step into:快捷键 F7,单步进入,比如当前行是一个方法,单步进入会进入到该方法中
- Resume Program:全速运行到下一个断点位置,在 IDEA Debugger 中,该符号为:
- step out:快捷键 shift + F8,跳出,同 step into 相反,每点一次,就会跳出一个方法
- Condition:条件断点,在断点中输入条件,为正常的 Java 代码
- Evaluate Expression:执行表达式
条件断点示例题:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
public class Main {
public static void main(String[] args) throws IOException {
String fileContent = readFileContent();
for (int i = 0; i < 10000; ++i) {
calculate(i, fileContent);
}
}
public static String readFileContent() throws IOException {
Stream<String> entries =
Stream.of(System.getProperty("java.class.path").split(File.pathSeparator));
File targetClassDir =
entries.filter(
entry ->
entry.endsWith("target/classes")
|| entry.endsWith("target\\classes"))
.findFirst()
.map(File::new)
.orElseThrow(IllegalStateException::new);
File mainDotJava = new File(targetClassDir, "../../src/main/java/Main.java");
return IOUtils.toString(new FileReader(mainDotJava)).replaceAll("\\s", "");
}
public static String calculate(int i, String fileContent) throws IOException {
// Please set a condition breakpoint with "i==5000" here to catch the value of result
// 请在这里设置一个i==5000的条件断点,捕捉result的值
String result = DigestUtils.md5Hex(fileContent + i);
return result;
}
}
答案:
在图示位置打断点,右键输入条件:i == 5000
,调试模式运行下,程序会在条件 i == 5000
时停下,然后单步执行,就会显示出result
结果值为:64dd23deced75e175a8fc4c725a9f3b3