Java插件
使用Java插件很简单,如果遵循Maven项目目录结构,你几乎不需要什么特殊配置,一句话就搞定,这就是基于约定的好处:
apply plugin: 'java'
但是如果你需要一些自定义,比如,正在做Ant项目到Gradle的迁移,你就有可能需要修改源代码的位置,这个时候,Java插件中有几个核心概念,你就需要了解:Source sets,Configuration等。
Source sets详解
首先Source sets的概念并不是Java插件提供的,它属于Gradle,其他插件,比如Scala,Groovy等都会使用。
Source sets,顾名思义,是源文件的一个集合,这些源文件会一同被编译,一同被执行。这些源文件包含Java文件和资源文件。一个Source Set包含有相关联的编译时classpath和运行时classpath。
Source sets的一个主要作用就是源代码按照不同的目的/作用,有逻辑性的进行分组。
那么,Java插件有哪些source sets,即有哪些源代码的分组呢?Main和Test。
Main中存放的是产品代码,编译,打包成Jar包(也可以War包或者Ear包),Test包含测试代码,编译和执行测试(不一定是单元测试)。我们来看一下,Java插件提供的默认的Main和Test两个source set的目录结构:
目录 | 含义 |
---|---|
src/main/java | 产品Java代码 |
src/main/resources | 产品资源文件 |
src/test/java | 测试Java代码 |
src/test/resources | 测试资源文件 |
假如,你的项目产品代码不是在src/main/java下,而是src/java,你需要改变源文件路径,你就会像下面这样做:
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
表面上很简单,这个一个Gradle的DSL闭包,用来做source sets的自定义配置,闭包的结构和默认配置的项目源代码目录结构一致(src/main/java),比较好理解。
我们来仔细阅读一下这个DSL(详解源代码):
就像我前面介绍过的(“任务”和“任务脚本的本质”章节):构建脚本build.gradle和Project对象的关系(一一对应和代理)。所以这个sourceSets在project上,但是它是由Java插件添加上去的。
Java插件提供一个叫做JavaPluginConvention的类,当你使用(apply)JavaBasePlugin或者JavaPlugin,它就会被混入到project对象中。
JavaPluginConvention类有一个方法sourceSets,接受一个闭包:
public class JavaPluginConvention {
public Object sourceSets(Closure closure) {
return sourceSets.configure(closure);
}
...
}
而其中的sourceSets是一个SourceSetContainer对象,很明显它是一个容器类。
所以sourceSets接受一个闭包,是对SourceSetContainer集合的配置,集合中的内容是一个个独立的SourceSet,比如main和test。而这两个SourceSet是Java插件帮你创建的,看下面:
public class JavaPlugin implements Plugin<ProjectInternal> {
private void configureSourceSets(final JavaPluginConvention pluginConvention) {
final Project project = pluginConvention.getProject();
SourceSet main = pluginConvention.getSourceSets().create(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet test = pluginConvention.getSourceSets().create(SourceSet.TEST_SOURCE_SET_NAME);
test.setCompileClasspath(project.files(main.getOutput(), project.getConfigurations().getByName(TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME)));
test.setRuntimeClasspath(project.files(test.getOutput(), main.getOutput(), project.getConfigurations().getByName(TEST_RUNTIME_CONFIGURATION_NAME)));
}
...
}
在SourceSet中提供了配置java和resources的方法,看下面的源代码:
public interface SourceSet {
SourceSet java(Closure configureClosure)
SourceSet resources(Closure configureClosure)
...
}
其中java和resources方法同样接受一个闭包,分别配置一个SourceDirectorySet的对象,来表示Java源代码和资源文件。而SourceDirectorySet有一个srcDir,用来指定路径。
public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
SourceDirectorySet srcDir(Object srcPath)
}
以上就是你在sourceSets闭包中,看到的全部关键代码的出处。你可能会有一个问题,为什么可以这样写,道理是Groovy的闭包代理
Groovy的闭包代理
而之所以,在闭包中可以直接调用某一个对象的方法,原因在于Groovy的闭包代理。看下面的例子:
class MyOtherClass {
String myString = "I am over in here in myOtherClass"
}
class MyClass {
def closure = {
println myString
}
}
MyClass myClass = new MyClass()
def closure = new MyClass().closure
closure.delegate = new MyOtherClass()
closure() // outputs: "I am over in here in myOtherClass"
Configuration
Configuration这个关键字,你可能比较陌生,但是,实际上你无时无刻都在使用它。它也并不来自于Java插件,它是Gradle中的一个概念。
Configuration又称为dependency configuration,它表示一组artifacts(构件)和它对应的依赖。Configuration本质上是FileCollection类的一个实例,也就是说,它是一个文件的集合。
Java插件提供了以下几个Configuration:compile,compileOnly,compileClasspath,runtime,testCompile,testCompileOnly,testCompileClasspath,testRuntime,archives,default。其中,compile,runtime,testCompile,testRuntime是比较常用的Configuration。
比如,compile,在这个文件集合中,包含的是在编译产品代码阶段需要使用到的依赖。runtime继承自compile,包含在产品运行时需要的依赖,但同时也包含编译阶段的依赖。又比如,testCompile继承自compile,则包含测试的编译阶段所需要的依赖,同时,还包含产品代码的编译结果,以及产品代码编译阶段所需要的额依赖,这点应该很好理解。
它们之间的关系如下图:
如何自定义一个Configuration
自定义一个Configuration很简单,如下:
configurations {
database
}
println configurations.database.name
什么时候使用呢?需要定义一些不需要在打包和运行测试时所需要的依赖。一个常见的例子就是加载JDBC的驱动,如下:
def registerDriver(driverName, configurationName) {
URLClassLoader loader = GroovyObject.class.classLoader as URLClassLoader
project.configurations[configurationName].each { File file -> loader.addURL(file.toURL()) }
DriverManager.registerDriver(loader.loadClass(driverName).newInstance() as Driver)
}