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)
}

源代码在此:JavaPluginConvention.java

results matching ""

    No results matching ""