问题描述

背景:一个基于maven的spring mvc项目,提供三种环境配置dev、beta、prod,选择dev环境配置(右键Mark Directory as…将profile/dev设置成Resources Root)

IDE: IntelliJ IDEA

  1. 使用mvn compile编译项目,运行程序,报错FileNotFoundException
  2. 使用Build—>Make Project编译项目,运行程序,成功执行
  3. 先mvn compile,再make project,运行程序,报错FileNotFoundException
  4. 先make project,再mvn compile,运行程序,成功执行
FileNotFoundException: class path resource [db.properties] cannot be opened because it does not exist

问题分析

报FileNotFoundException异常是因为profile/dev中的db.properties没有输出到主源码编译输出目录target/classes中,明明已经将profile/dev设置成了Resource Root,为什么编译的时候没有输出呢?

查阅相关资料,《Maven实战》中讲到,maven支持6种方式激活profile:

  1. 命令行激活
    mvn clean install –Pdev 激活开发环境的配置文件
  2. 默认激活
  3. settings文件显示激活
  4. 系统属性激活
  5. 操作系统环境激活
  6. 文件存在与否激活

另外,Maven的超级pom定义了项目默认的主源码目录src/main/java、测试源码目录src/test/java、主资源目录src/main/resources、测试资源目录src/test/resources,构建输出目录target/,主源码编译输出目录target/classes,测试源码编译输出目录target/test-classes,插件maven-resources-plugin负责处理资源文件,其默认将主资源文件复制到主源码编译输出目录target/classes,将测试资源文件复制到测试源码编译输出目录target/test-classes。

由于一开始没有使用maven的方式激活profile,所以使用maven命令编译项目的时候,没有检测到有效的profile,导致profile文件没有copy到主源码输出目录target/classes中,选择以上方式激活profile/dev后,profile文件正常输出,程序也可正常运行。

为什么make project可以正常输出profile文件呢?为什么先mvn compile,再make project,运行程序报错呢?

这里先了解一下IntelliJ IDEA的编译方式,idea提供3种编译方式:

  1. Compile:对选定的目标(Java 类文件),进行强制性编译,不管目标是否是被修改过。
  2. Rebuild:对选定的目标(Project),进行强制性编译,不管目标是否是被修改过。
  3. Make:对选定的目标(Project 或 Module)进行编译,但只在有文件修改时编译,没有修改过的文件不会编译,最常用的编译方式。

由于idea使用的编译器是Javac(在settings—Java Compiler里配置),右键Mark Directory as…将profile/dev设置成Resources Root,其实就是将profile/dev设置成主资源目录, Javac会将主资源目录里的文件copy到编译输出目录target/classes(在Project Structure—Modules—Paths中设置),所以使用make project可以正常输出profile文件,当然使用Compile、Rebuild也是可以正常输出profile文件的。

而先mvn compile,再make project,idea可能没有检测到有效的文件修改,实际上没有触发编译行为,运行程序仍然使用的是mvn compile的编译结果。

设置编译器和编译输出目录如下图所示:

idea files type 中没有freemarker template idea为什么没有.out_spring

idea files type 中没有freemarker template idea为什么没有.out_生命周期_02

为什么先make project,再mvn compile,仍然可以正常输出profile文件?为什么mvn compile没有覆盖make project的编译结果?

在这里需要先了解一下maven的生命周期和命令行的相关知识:
maven有三套生命周期:clean,default,sitte

  1. clean:清理项目,包含pre-clean、clean、post-clean
  2. default:构建项目,主要包含compile、test、package、install、deploy等,其中compile编译 java文件和资源文件并输出到classpath
  3. site:建立项目站点,包含pre-site、site、post-site、site-deploy

maven不同生命周期的各个阶段相互独立,同一个生命周期的各个阶段是有前后依赖关系的,例如调用clean生命周期的clean阶段不会触发default生命周期的任何阶段,调用default生命周期的compile阶段,也不会触发clean生命周期的任何阶段,但是调用某个生命周期的某个阶段,会执行该生命周期内该阶段前的所有阶段。

maven命令行通过调用maven的生命周期阶段来执行maven任务,典型的maven命令有:
mvn clean:调用clean生命周期的clean阶段,实际执行clean生命周期的pre-clean和clean阶段。
mvn clean install:调用clean生命周期的clean阶段和default生命周期的install阶段,实际执行clean生命周期的pre-clean、clean阶段,以及default生命周期install和install之前的所有阶段。

根据以上可知, 命令行mvn compile调用default生命周期的compile阶段,实际执行default生命周期compile和compile之前的所有阶段,未执行clean不会清空target目录,同样地,maven在编译的也会检测是否有文件修改,如果没有文件修改,就不会触发编译行为。通过maven执行的日志信息“Nothing to compile - all classes are up to date”可以知道,mvn compile没有进行实际的编译操作,运行程序仍然使用的make project的编译结果,而前文已述,make project可以正常输出profile文件,所以程序可以成功运行。

至此结束。