gradle依赖配置

Feb 22, 2017


问题引出

在配置Spring Boot程序时,build.gradle中配置:

dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

虽使用./gradlew bootRun运行一切正常,但在Idea中运行程序直接退出而不是启动服务端程序。

而在将providedRuntime修改为compile,idea运行正常。

问题合理解决的另一个方式,修改为providedRuntime('org.springframework.boot:spring-boot-starter-tomcat@jar')

但是这后面隐藏着什么样的知识呢,到底什么是compile,什么是providedRuntime?

依赖配置

依赖管理主要包括两部分:一是依赖,即项目构建或运行时所需要的一些文件;二是发布,即构建完成后上传到某个地方。本文只记录了依赖配置部分。

在build.gradle中配置项目所用到的依赖,gradle可以根据配置的依赖从远程的Maven,Ivy库或本地目录中下载依赖。依赖在build.gradle中配置的例子。

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	compileOnly('org.springframework.boot:spring-boot-starter-tomcat')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

Idea的java插件,Java插件定义了一些标准配置(compile, runtime, testCompile, testRuntime),这些配置一起形成了插件本身的类路径库,其都有着不同的含义:

  • compile: 该依赖对于编译发行是必须的。

  • runtime: 该依赖对于运行时是必须的,默认包含编译时依赖。

  • testCompile: 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依赖。

  • testRuntime: 该依赖对于测试是必须的,默认包含编译、运行时、测试编译依赖。

  • compileOnly: gradle 2.12后引入的。改依赖只用于编译器,不用于运行期。

依赖仓库

Gradle 是在一个被称之为仓库的地方找寻所需的外部依赖。仓库即是一个按 group,name 和 version 规则进行存储的一些文件。Gradle 可以支持不同的仓库存储格式,如 Maven 和 Ivy,并且还提供多种与仓库进行通信的方式,如通过本地文件系统或 HTTP。

默认情况下,Gradle 没有定义任何仓库,你需要在使用外部依赖之前至少定义一个仓库,例如 Maven 中央仓库。

repositories {
    mavenCentral()
}

使用其他远程仓库:

repositories {
    maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
}

依赖配置与打包的关系

War插件添加了两个依赖配置: providedCompile和providedRuntime。这两个配置的与compile和runtime配置有相同作用于,不同的是是它们不会添加到WAR文件中。

providedCompile - Additional compile classpath for libraries that should not be part of the WAR archive.

providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive.

要特别注意的是,这些provided配置是有传递效应的。假设你添加commons-httpclient:commons-httpclient:3.0依赖到任何一个build.grale的provided配置。而commons-httpclient依赖于commons-codec。由于commons-httpclient是provided配置,它的所有依赖都不会被打包到war中(即便你在build.gradle中定义了compile …commons-codec…)。如果想去除这种传递效应,可以在provided依赖时加上@jar, 如 providedCompile commons-httpclient:commons-httpclient:3.0@jar.

providedCompile ('commons-httpclient:commons-httpclient:3.0@jar') {
    transitive = false
  }

在开篇时引起本文的问题就是使用@jar来解决问题,使用./gradlew dependencies可以查看项目的依赖,在去掉@jar时。providedRuntime的依赖被解析成:

providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive.
\--- org.springframework.boot:spring-boot-starter-tomcat: -> 1.5.1.RELEASE
     +--- org.apache.tomcat.embed:tomcat-embed-core:8.5.11
     +--- org.apache.tomcat.embed:tomcat-embed-el:8.5.11
     \--- org.apache.tomcat.embed:tomcat-embed-websocket:8.5.11
          \--- org.apache.tomcat.embed:tomcat-embed-core:8.5.11

在有@jar时,providedRuntime的依赖被解析成:

providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive.
\--- org.springframework.boot:spring-boot-starter-tomcat: -> 1.5.1.RELEASE

我们再对比在初始./gradlew build后生成的war的差别。

去掉@jar的情况,tar tvf build/libs/demo-0.0.1-SNAPSHOT.war

WEB-INF/lib-provided/spring-boot-starter-tomcat-1.5.1.RELEASE.jar
WEB-INF/lib-provided/tomcat-embed-core-8.5.11.jar
WEB-INF/lib-provided/tomcat-embed-el-8.5.11.jar
WEB-INF/lib-provided/tomcat-embed-websocket-8.5.11.jar

有@jar的情况

WEB-INF/lib/tomcat-embed-core-8.5.11.jar
WEB-INF/lib/tomcat-embed-el-8.5.11.jar
WEB-INF/lib/tomcat-embed-websocket-8.5.11.jar
WEB-INF/lib-provided/spring-boot-starter-tomcat-1.5.1.RELEASE.jar

现在还剩下一个问题没有解释清楚,就是为什么./gradlew bootRun能在没有@jar的时候运行正常,查看其log,发现:

Application started with classpath: [.....gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/8.5.11/72761f51fc7cef3ee19d4aafc7adc605df9f611f/tomcat-embed-core-8.5.11.jar ......]

其将tomcat启动需要的库都加入到了程序运行时的classpath中,自然也就能正常启动tomcat了。

以上内容也是本人的理解,如有谬误,请自行屏蔽上述内容

另测试了一下使用compileOnly,打包后发现tomcat相关的包都还在WEB-INF/lib下,具体问题查看compileOnly not useable for excluding jars from WAR?

自定义配置

providedCompile是war插件提供的,如果在打jar包时也希望不打包指定的依赖?gradle 2.12版本提供的compileOnly很好的解决了这个问题。如果想自己定义一个配置来满足这个需求呢?

configurations { providedCompile }

dependencies {
    providedCompile "javax.servlet:javax.servlet-api:3.+"
}

sourceSets.main.compileClasspath += configurations.providedCompile
sourceSets.test.compileClasspath += configurations.providedCompile
sourceSets.test.runtimeClasspath += configurations.providedCompile

在sourceSets.main.runtimeClasspath并没有将其提供在其中,所以运行期不会提供此库。次解决方案貌似eclipse上有问题。

参考资料