Java库插件扩展的功能的Java插件,通过提供有关Java库的具体知识. 特别是,Java库向使用者(即使用Java或Java库插件的其他项目)公开API. 使用此插件时,Java插件公开的所有源集,任务和配置都是隐式可用的.

Usage

要使用Java库插件,请在构建脚本中包括以下内容:

示例1.使用Java库插件
build.gradle
plugins {
    id 'java-library'
}
build.gradle.kts
plugins {
    `java-library`
}

API and implementation separation

标准Java插件和Java库插件之间的主要区别在于,后者引入了向消费者公开的API的概念. 库是一个Java组件,打算由其他组件使用. 在多项目构建中,这是一个非常常见的用例,但在您具有外部依赖关系时也是如此.

该插件暴露了两个配置 ,可以用来声明依赖性: apiimplementation . api配置应用于声明由库API导出的依赖关系,而implementation配置应用于声明组件内部的依赖关系.

例子2.声明API和实现依赖
build.gradle
dependencies {
    api 'org.apache.httpcomponents:httpclient:4.5.7'
    implementation 'org.apache.commons:commons-lang3:3.5'
}
build.gradle.kts
dependencies {
    api("org.apache.httpcomponents:httpclient:4.5.7")
    implementation("org.apache.commons:commons-lang3:3.5")
}

api配置中出现的依赖项将传递给库的使用者,并因此而出现在使用者的编译类路径上. 另一方面,在implementation配置中找到的依赖项不会暴露给使用者,因此不会泄漏到使用者的编译类路径中. 这有几个好处:

  • 依赖项不会再泄漏到使用者的编译类路径中,因此您永远不会意外地依赖于传递性依赖项

  • 减少类路径大小,加快了编译速度

  • 实施依赖项发生更改时,重新编译次数更少:无需重新编译使用者

  • 更清洁的发布:与新的maven-publish插件一起使用时,Java库生成的POM文件可准确区分针对该库进行编译所需的内容和在运行时使用该库所需的内容(换句话说,不要混合编译库本身所需的内容和对库进行编译所需的内容).

compile配置仍然存在,但不应使用,因为它不能提供apiimplementation配置所提供的保证.

如果您的构建使用带有POM元数据的已发布模块,则Java和Java库插件会通过pom中使用的作用域来实现api和实现分离. 这意味着编译类路径仅包括compile范围的依赖关系,而运行时类路径也添加了runtime范围的依赖关系.

这通常对用Maven发布的模块没有影响,在Maven中,定义项目的POM直接作为元数据发布. 在那里,编译范围既包括编译项目所需的依赖关系(即实现依赖关系),又包括针对已发布库进行编译所需的依赖关系(即API依赖关系). 对于大多数已发布的库,这意味着所有依赖项都属于编译范围. 如果您在现有库中遇到此类问题,则可以考虑使用组件元数据规则来修复构建中不正确的元数据. 但是,如上所述,如果该库与Gradle一起发布,则生成的POM文件仅将api依赖项放入编译范围,而将其余implementation依赖项放入运行时范围.

如果您的构建使用带有Ivy元数据的模块,则如果所有模块都遵循特定的结构,则可以按此处所述激活api和实现分离.

在Gradle 5.0+中,默认情况下,将模块的编译和运行时范围分开是活动的. 在Gradle 4.6+中,您需要通过在settings.gradle中添加enableFeaturePreview('IMPROVED_POM_SUPPORT')来激活它.

Recognizing API and implementation dependencies

本节将帮助您使用简单的经验法则来识别代码中的API和实现依赖性. 第一个是:

  • 尽可能将implementation配置apiapi

这使依赖项脱离使用者的编译类路径. 此外,如果任何实现类型意外泄漏到公共API中,使用者将立即无法编译.

那么什么时候应该使用api配置呢? API依赖关系是至少包含一种在库二进制接口(通常称为ABI(应用程序二进制接口))中公开的类型. 这包括但不限于:

  • 超类或接口中使用的类型

  • 公共方法参数中使用的类型,包括通用参数类型(其中public是编译器可见的东西.即Java世界中的publicprotectedpackage private成员)

  • 公共领域中使用的类型

  • 公开注释类型

相比之下,下表中使用的任何类型都与ABI不相关,因此应将其声明为implementation依赖项:

  • 方法主体中专门使用的类型

  • 专用于私人会员的类型

  • 内部类中专有的类型(将来的Gradle版本将允许您声明哪些包属于公共API)

下面的类使用了几个第三方库,其中一个在类的公共API中公开,另一个仅在内部使用. import语句不能帮助我们确定哪个是哪个,因此我们必须查看字段,构造函数和方法:

Example: Making the difference between API and implementation

src/main/java/org/gradle/HttpClientWrapper.java
// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class HttpClientWrapper {

    private final HttpClient client; // private member: implementation details

    // HttpClient is used as a parameter of a public method
    // so "leaks" into the public API of this component
    public HttpClientWrapper(HttpClient client) {
        this.client = client;
    }

    // public methods belongs to your API
    public byte[] doRawGet(String url) {
        HttpGet request = new HttpGet(url);
        try {
            HttpEntity entity = doGet(request);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            entity.writeTo(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            ExceptionUtils.rethrow(e); // this dependency is internal only
        } finally {
            request.releaseConnection();
        }
        return null;
    }

    // HttpGet and HttpEntity are used in a private method, so they don't belong to the API
    private HttpEntity doGet(HttpGet get) throws Exception {
        HttpResponse response = client.execute(get);
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            System.err.println("Method failed: " + response.getStatusLine());
        }
        return response.getEntity();
    }
}

HttpClientWrapper公共构造函数使用HttpClient作为参数,因此它公开给使用者,因此属于API. 请注意, HttpGetHttpEntity用于私有方法的签名中,因此它们不计入使HttpClient成为API依赖项的过程.

另一方面,来自commons-lang库的ExceptionUtils类型仅在方法主体(而不是其签名)中使用,因此是实现依赖.

因此,我们可以推断出httpclient是API依赖关系,而commons-lang是实现依赖关系. 该结论转化为构建脚本中的以下声明:

例子3.声明API和实现依赖
build.gradle
dependencies {
    api 'org.apache.httpcomponents:httpclient:4.5.7'
    implementation 'org.apache.commons:commons-lang3:3.5'
}
build.gradle.kts
dependencies {
    api("org.apache.httpcomponents:httpclient:4.5.7")
    implementation("org.apache.commons:commons-lang3:3.5")
}

The Java Library plugin configurations

下图描述了使用Java库插件时的主要配置设置.

java library ignore deprecated main
  • 绿色的配置是用户应用来声明依赖项的配置

  • 粉红色的配置是组件编译或针对库运行时使用的配置

  • 蓝色的配置在组件内部,供其自己使用

下图描述了测试配置设置:

java library ignore deprecated test

从Java插件继承的compiletestCompileruntimetestRuntime配置仍然可用,但已弃用. 您应该避免使用它们,因为仅保留它们是为了向后兼容.

下表描述了每种配置的作用:

表1. Java库插件-用于声明依赖关系的配置
配置名称 Role Consumable? Resolvable? Description

api

声明API依赖项

no

no

在这里,您应该声明依赖关系,这些依赖关系将传递给使用者,以进行编译.

implementation

声明实现依赖

no

no

在这里,您应该声明依赖关系,这些依赖关系完全是内部的,并且不打算向消费者公开.

compileOnly

声明仅编译依赖项

no

no

在这里,您应该声明仅在编译时需要的依赖项,而不应泄漏到运行时. 这通常包括在运行时找到时会被阴影化的依赖项.

runtimeOnly

声明运行时依赖项

no

no

在这里,您应该声明仅在运行时才需要的依赖项,而在编译时则不需要.

testImplementation

测试依赖

no

no

在这里您应该声明用于编译测试的依赖项.

testCompileOnly

声明测试仅编译依赖项

no

no

在这里,您应该声明仅在测试编译时需要的依赖项,而不应泄漏到运行时. 这通常包括在运行时找到时会被阴影化的依赖项.

testRuntimeOnly

声明测试运行时依赖项

no

no

在这里,您应该声明仅在测试运行时才需要的依赖项,而在测试编译时则不需要.

表2. Java库插件—使用者使用的配置
配置名称 Role Consumable? Resolvable? Description

apiElements

用于针对该库进行编译

yes

no

使用者将使用此配置来检索对该库进行编译所需的所有元素. 与default配置不同,这不会泄漏实现或运行时依赖项.

runtimeElements

用于执行此库

yes

no

使用者将使用此配置来检索对该库运行所需的所有元素.

表3. Java库插件-库本身使用的配置
配置名称 Role Consumable? Resolvable? Description

compileClasspath

用于编译该库

no

yes

此配置包含此库的编译类路径,因此在调用java编译器进行编译时使用.

runtimeClasspath

用于执行此库

no

yes

此配置包含此库的运行时类路径

testCompileClasspath

用于编译该库的测试

no

yes

This configuration contains the test compile classpath of this library.

testRuntimeClasspath

用于执行此库的测试

no

yes

此配置包含此库的测试运行时类路径

Using classes instead of jar for compilation

A feature of the java-library plugin is that projects which consume the library only require the classes folder for compilation, instead of the full JAR. This enables lighter inter-project dependencies as resources processing (processResources task) and archive construction (jar task) are no longer executed when only Java code compilation is performed during development.

班输出,而不是JAR中的使用与否是消费者决定. 例如,Groovy使用者将请求类和已处理资源,因为在编译过程中执行AST转换可能需要这些类和已处理资源.

Increased memory usage for consumers

间接的结果是,最新的检查将需要更多的内存,因为Gradle会快照单个类文件而不是单个jar. 这可能会导致大型项目的内存消耗增加,并具有在更多情况下使compileJava任务为最新状态的好处(例如,更改资源不再更改上游项目的compileJava任务的输入).

Significant build performance drop on Windows for huge multi-projects

单个类文件快照的另一个副作用,仅影响W​​indows系统,是在编译类路径上处理大量类文件时,性能可能会大大下降. 这仅涉及非常大的多项目,其中通过使用许多api或(不建议使用的) compile依赖项,在类路径上存在很多类. 为了减轻这种情况,可以将org.gradle.java.compile-classpath-packaging系统属性设置为true以更改Java库插件的行为,以将jars而不是类文件夹用于编译类路径上的所有内容. 请注意,由于这会带来其他性能影响和潜在的副作用,因此通过在编译时触发所有jar任务,只有在Windows上遇到上述性能问题时,才建议激活此功能.

Distributing a library

除了将库发布到组件存储库之外,有时您可能还需要将库及其依赖项打包在可分发的分发包中. Java库分发插件可以帮助您做到这一点.