Dependency Configuration

2020-03-03  本文已影响0人  CrazyOrr

一分为二

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。

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

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

如何正确使用

首要规则:

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

那么何时使用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:

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:

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

// 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

标注:

绿色的是使用者声明的,红色的是暴露给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:

Maven Dependency Scopes:

相对于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类型的。

上一篇 下一篇

猜你喜欢

热点阅读