Spring 如何实现与 Java 6、7 和 8 的兼容性

工程 | Stéphane Nicoll | 2015年4月3日 | ...

从 Spring Framework 4.0 开始,Java 8 就被支持为一等公民,从那时起,我们在 Spring 社区中看到了一些困惑。毕竟,我们如何才能同时支持 Java 8 并保持与 Java 6 和 Java 7 的兼容性?这篇博文提供了一些关于我们在框架代码库中如何处理此问题的见解。

Java 8 语言特性与 Java 8 API

首先,必须区分在给定 Java 版本(例如 Java 8)中使用新的语言特性和新的 API。如果一个类使用了 Java 8 的语言特性,例如 lambda 表达式,它必须使用 -source 1.8 -target 1.8 进行编译,因此整个编译单元只能在 Java 8 及更高版本上运行。但是,如果库中的某个特定类可以选择性地使用新的 Java 8 接口,例如 java.util.stream.Stream,则只要它使用例如 -source 1.6 -target 1.6 进行编译,并且只要该特定 Stream 基类 的使用仅在实际运行在 Java 8 及更高版本时才生效,该库仍然可以在以前的 Java 版本上运行。正如您可能已经猜到的,我们在 Spring Framework 代码库中广泛使用了这种安排!

我们已经宣传了 Spring Framework 4.0 如何自然地适合 Java 8 lambda 表达式。例如,使用 ConnectionCallback 获取给定 JDBC 连接的目录可以编写如下代码

jdbcTemplate.execute(connection -> connection.getCatalog())

事实上,Spring Framework 多年来一直拥有现在所谓的函数式接口,我们不必更改任何这些 API 就能符合 Java 8 编译器对函数式接口的规则。像上面这样的基于 lambda 的代码,调用 Spring API,可以在任何 Spring 应用程序中使用 - 这显然需要 Java 8 运行时。但是,如果您选择使用传统的内部类方法,针对同一个 Spring 版本中的相同 Spring API 编写此类代码,您也可以使用 Java 6+ 运行时来完成。

jdbcTemplate.execute(new ConnectionCallback<String>() {
    @Override
    public String doInConnection(Connection con) throws SQLException {
        return con.getCatalog();
    }
});

底线是选择权在您手中:我们仔细设计了 Spring Framework 4.x,使其自然地兼容 Java 6、7 和 8,使用相同的 Spring jar 且无需特殊的设置步骤。我们自己的代码中不使用任何 Java 8 的语言特性,因此我们可以使用 -source 1.6 -target 1.6 编译我们的框架代码库,并且我们可以在该代码库安排中自动检测并自动激活许多 Java 8 API 特性(如果在运行时可用)。然后,您的应用程序代码可以选择使用 Java 6、7 或 8 语言级别本身,与我们的框架安排进行交互,并自然地充分利用您正在使用的 JDK - 无需任何额外设置,只需在运行时将 Spring 与您的 JDK 相结合即可。

我们支持哪些 Java 8 API 特性?

我们专门支持许多 Java 8 特定的 API 特性,例如 java.util.Optionaljava.util.stream.Streamjava.time(JSR-310)、可重复注解、方法/构造函数参数名称,甚至 java.util.Base64 实用程序类。当您选择在自己的应用程序类中使用这些特性时,这些特性会在运行时被反射式地检测到,Spring Framework 会根据条件激活其对这些 Java 8 特性的支持,例如在 Java 8 存在于运行时时注册 OptionalStream 的默认转换器。

让我们看一个例子。在即将推出的 Spring Framework 4.2 中,如果您定义了一个 Collection 或数组类型的值,您可以将其注入为 Stream,我们会为您进行转换。您可以在 github 上找到 StreamConverter 的完整代码,但这里有一段摘录

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.core.convert.*;
import org.springframework.lang.UsesJava8;

@UsesJava8
public class StreamConverter implements ConditionalGenericConverter {
    ....
}

StreamConverter 是一个使用 Java 8 特定 API 的隔离类,因此我们现在需要做的是,如果 Java 8 存在于运行时,则有条件地将 StreamConverter 添加到 DefaultConverterService 中。

public class DefaultConversionService extends GenericConversionService {

    /** Java 8's java.util.stream.Stream class available? */
    private static final boolean streamAvailable = ClassUtils.isPresent(
            "java.util.stream.Stream", 
            DefaultConversionService.class.getClassLoader());

    private static void addCollectionConverters(
            ConverterRegistry converterRegistry) {
        ...

        if (streamAvailable) {
            converterRegistry.addConverter(
                    new StreamConverter(conversionService));
        }
    }
}

我们有条件地检查 API 是否存在于运行时,并根据此做出决定,作为用户,您只需体验默认情况下完全适应 Java 8 的设置即可。这有点类似于 Spring Boot 中的 条件基础设施,只是它更底层且更内部。

检查 Java 6 兼容性

由于我们在几个隔离的地方使用了 Java 8 特定的 API,因此我们需要 JDK 8 来编译整个框架代码库。因此,存在一个风险,即我们可能会意外地在需要保持 Java 6 兼容性的位置引入 Java 8 特定的 API 调用。

幸运的是,我们的 CI 构建计划配置为在每次构建时执行 Animal Sniffer。这会根据给定的 Java API 签名(在我们的例子中是 Java 6 更新 18)检查我们的代码,如果发现某些不正确的用法,则构建失败。那么,在我们确实需要调用 Java 7 或 8 API 的合法用例中会发生什么情况呢?您可以配置 sniffer 以排除类列表,或者更好地提供一组注解来标记此类异常情况。

这正是 StreamConverter 上的 @UsesJava8 注解(见上文)所指示的:它将整个类标记为 Java 6 API 兼容性规则的例外。您可以以类似的方式标记内部类甚至方法。通过查看我们对该注解的使用,我们知道在我们的代码库中使用 Java 7/8 特定 API 的所有位置。

Animal Sniffer 配置非常简单:查看 我们的构建官方文档以了解更多详细信息。

总结

我们选择不在自己的代码库中使用任何 Java 7 或 Java 8 的语言特性,以便让您灵活地为 Java 6、7 或 8 编写 Spring 4 应用程序。同时,如果您决定使用 Java 8,我们允许您体验非常自然的方法,在这种情况下,Spring Framework 对您来说基本上看起来像是基于 Java 8 的。

幸运的是,Java 8 的函数式接口约定对我们来说并不陌生。许多现有的 Spring API 可以与 Java 8 lambda 表达式无缝地使用,因为它们自然地遵循相同的约定。如果您选择在自己的代码中使用新的 Java 8 API,例如 java.time(JSR-310)、OptionalStream,框架会自动支持它们。

展望未来,从 4.2 开始,我们的代码库甚至已经在使用早期的 JDK 9 版本进行检查!这将在明年 JDK 9 普遍可用后导致框架中出现独特的情况:在同一发行版本中支持四个 Java 版本 - 您可以在同一 Spring Framework 版本中选择 JDK 6、7、8 或 9!

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以助您快速提升进度。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部