Dependency Configuration

一分为二

dependencies中原来的compile一分为二,改为api或者implementation

为什么compile要一分为二呢?apiimplementation的区别又是什么?

先来看官方文档说明:

The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.

Dependencies appearing in the api configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in the implementation configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath.

以一个library为例,api声明的依赖会暴露给library的使用者,而implementation声明的不会。

看具体例子:

假设我们有一个叫做LibraryA的library,它内部使用了另一个叫做LibraryB的library的方法。

// 'LibraryA' module
public class LibraryA {
    public static String myString(){
        return LibraryB.giveMeAString(); // 使用LibraryB的方法
    }
}
// 'LibraryB' module
public class LibraryB {
    public static String giveMeAString(){
        return "hello";
    }
}

然后在我们的app中使用LibraryA

dependencies {
    implementation project(':LibraryA')
}

然后我们分别来看apiimplementation2种情况:

1.假设LibraryA的build.gradle的dependencies中使用api(或者deprecated的compile)声明对LibraryB的依赖:

dependencies {
    api project(':LibraryB')
    // compile project(':LibraryB') // deprecated
}

此时在app中我们可以使用LibraryALibraryB

// 可以使用LibraryA
LibraryA.myString();
// 可以使用LibraryB
LibraryB.giveMeAString();

2.假设LibraryA的build.gradle的dependencies中使用implementation声明对LibraryB的依赖:

dependencies {
    implementation project(':LibraryB')
}

此时在app中我们只能使用LibraryA,而无法使用LibraryB

// 可以使用LibraryA
LibraryA.myString();
// 无法使用LibraryB,编译报错
//LibraryB.giveMeAString();

由此可见,一分为二之后,api基本等同于原来的compile,而implementation则可以让我们隐藏transitive dependency。

这样有什么好处呢?摘录如下:

  • dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive dependency
  • faster compilation thanks to reduced classpath size
  • less recompilations when implementation dependencies change: consumers would not need to be recompiled
  • cleaner publishing (don’t mix what is needed to compile the library itself and what is needed to compile against the library)

其中的二、三两条都有助于缩短编译时间。
那么,我们该如何正确的使用api/implementation呢?

如何正确使用

首要规则:

  • Prefer the implementation configuration over api when possible

面向对象封装的概念,类似类成员的最小可见性原则,不暴露实现,方便修改,加速编译。

那么何时使用api

An API dependency is one that contains at least one type that is exposed in the library binary interface, often referred to as its ABI (Application Binary Interface). This includes, but is not limited to:

  • types used in super classes or interfaces
  • types used in public method parameters, including generic parameter types (where public is something that is visible to compilers. I.e. , public, protected and package private members in the Java world)
  • types used in public fields
  • public annotation types

By contrast, any type that is used in the following list is irrelevant to the ABI, and therefore should be declared as an implementation dependency:

  • types exclusively used in method bodies
  • types exclusively used in private members
  • types exclusively found in internal classes (future versions of Gradle will let you declare which packages belong to the public API)

这里引用官方文档的示例:

// 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();
    }
}
dependencies {
    api 'org.apache.httpcomponents:httpclient:4.5.7'
    implementation 'org.apache.commons:commons-lang3:3.5'
}

原理

那么,Gradle到底是如何实现这一区分的呢?

Java Library Plugin

先来看一张描述 Gradle main configurations setup 的图

Image of Gradle main configurations setup

标注:

  • The configurations in green are the ones a user should use to declare dependencies
  • The configurations in pink are the ones used when a component compiles, or runs against the library
  • The configurations in blue are internal to the component, for its own use
  • (C) for consumable configurations, (R) for resolvable configurations

绿色的是使用者声明的,红色的是暴露给consumer(编译/运行)用的,蓝色的是仅限自己(编译/运行)用的。
由上图可以看出,api声明的依赖会进入真正用来编译的全部4个configuration:apiElements,compileClasspath,runtimeElements,runtimeClasspath
implementation声明的依赖只会进入除apiElements以外的其他3个configuration。
apiElements是用来"For compiling against this library",也就是说consumer编译时无法获得这些依赖,但运行时仍旧包含,所以虽然无法编译但依然能够运行。

Android Gradle Plugin

Android Gradle plugin中的实现略有不同:

Configuration Behavior
implementation Gradle adds the dependency to the compile classpath and packages the dependency to the build output. However, when your module configures an implementation dependency, it's letting Gradle know that you do not want the module to leak the dependency to other modules at compile time. That is, the dependency is available to other modules only at runtime.
api Gradle adds the dependency to the compile classpath and build output. When a module includes an api dependency, it's letting Gradle know that the module wants to transitively export that dependency to other modules, so that it's available to them at both runtime and compile time.

区别在于implementation的依赖被打包(package)后放到build output,而api的直接放,
这样implementation的依赖只在runtime能被其他module使用,而api的在runtime和compile time都可以使用。

Gradle vs Maven

以上介绍的都是Gradle中的情况,但是当我们需要发布一个module时,我们采用的是Maven的形式。
此时就需要将Gradle的Configurations对应为Maven的Scopes,分别列出两者如下:

Gradle Dependency Configurations:

  • api
  • implementation
  • compileOnly
  • runtimeOnly
  • testImplementation
  • testCompileOnly
  • testRuntimeOnly

Maven Dependency Scopes:

  • compile
  • provided
  • runtime
  • test
  • system
  • import

相对于Maven,Gradle更新,开发维护更活跃,所以Gradle的发展早已超越Maven。

以上两者大致的对应关系如下:

Maven Scope Gradle Configuration
compile api/implementation
provided compileOnly
runtime runtimeOnly

Maven没有区分exposed/internal dependency的概念,所以Gradle的api/implementation依赖在写到Maven POM中时只能都映射为compile
发布的module再次被Gradle引入时,它的transitive dependencies就相当于都变成了api类型的。

你可能感兴趣的:(Dependency Configuration)