本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Oracle推出的JDK 11是一个具有长期支持(LTS)的Java开发工具包版本,它提供8年以上的免费安全更新。本压缩包包含适用于Windows 64位、MacOS和Linux的JDK11安装文件。JDK 11引入了新特性,如模块化系统、增强的HTTP客户端、文本块、改进的switch语句、动态类型支持以及移除Nashorn JavaScript引擎等,以提高性能、安全性和开发效率。开发者可以通过安装和应用此版本,利用其新功能来构建和运行Java应用。
Java长期支持版本-JDK11.zip

1. JDK 11长期支持版本(LTS)概览

JDK 11的定位与重要性

Java Development Kit(JDK)11作为LTS(长期支持)版本,意味着它将获得Oracle更长时间的技术支持和维护,这使得JDK 11成为企业和组织在生产环境中部署Java应用的首选版本。通过引入新特性与性能改进,JDK 11旨在提供更为稳定和高效的开发体验。

新特性的简要介绍

JDK 11带来了包括HTTP/2支持、新的垃圾收集器、改进的switch语句、以及全新的字符串处理方法等特性。这些增强提升了Java语言的表达能力,同时也加强了其作为企业级语言的地位。

JDK 11在企业中的应用前景

在企业级应用开发中,JDK 11的模块化和新API有助于简化大型项目架构,提高代码可维护性和安全性。企业可预期利用JDK 11带来的新特性,实现更高效的开发周期和更好的系统性能。

2. JDK 11的安装与环境配置

2.1 JDK 11安装包的特点与兼容性

在本节中,我们将深入探讨JDK 11安装包的特点,包括它在主流操作系统下的安装方法,以及它与旧版本之间的差异和迁移指南。

2.1.1 主流操作系统下的JDK 11安装方法

在讨论JDK 11的安装前,有必要了解JDK(Java Development Kit)是开发者进行Java编程所必须的软件集合。它包含了Java运行时环境(JRE)、Java虚拟机(JVM)、类库以及Java编译器(javac)和其他工具。

在不同操作系统上安装JDK 11,主要步骤包括:

Windows:
1. 访问Oracle官网或OpenJDK官网下载Windows版本的JDK 11安装包。
2. 双击下载的 .exe 文件开始安装流程。
3. 跟随安装向导的提示进行安装,可以自定义安装路径或使用默认路径。
4. 完成安装后,验证安装是否成功,通过命令提示符运行 java -version 。

macOS:
1. 从Oracle或OpenJDK官网下载macOS版本的JDK 11安装包。
2. 双击下载的 .dmg 文件挂载安装包。
3. 拖动JDK 11的图标到“应用”文件夹完成安装。
4. 同样地,通过终端运行 java -version 验证。

Linux:
大多数Linux发行版提供了JDK的包管理器安装方式:

对于基于Debian的系统(如Ubuntu):

sudo apt-get update
sudo apt-get install openjdk-11-jdk
java -version

对于基于RPM的系统(如Fedora,CentOS):

sudo yum install java-11-openjdk-devel
java -version

安装注意事项:
- 路径选择: 确保安装路径对所有用户都可访问,否则可能需要调整环境变量。
- 版本管理: 如果系统中已安装了其他版本的Java,确保环境变量正确设置,以免造成版本冲突。

2.1.2 JDK 11版本与旧版的区别和迁移指南

从Java 9开始,Oracle采用了模块化系统以改进Java平台的性能和安全性。JDK 11作为继JDK 9和JDK 10之后的长期支持版本,虽然不包含模块化Java平台的全部特性,但已经包含了许多改进和新特性。

JDK 11与旧版主要区别:
1. 弃用与删除: 一些旧组件如Java EE和CORBA模块被弃用,Nashorn JavaScript引擎在JDK 11中完全被移除。
2. 新的API和特性: 引入了新的HTTP客户端和文本块等特性。

迁移指南:
1. 模块化更新: 对于使用Java 8或更早版本的项目,需要检查模块化可能带来的影响,并考虑是否需要调整项目结构。
2. 库更新: 需要检查并更新项目依赖库,以确保兼容性。
3. 编译器和运行环境: 如果你之前使用的是Oracle JDK 8,建议使用OpenJDK 11,并确保环境变量配置正确。

2.2 环境变量设置与项目配置

2.2.1 环境变量的配置详解

环境变量是操作系统用来决定程序运行环境的一些参数。在Java开发中,环境变量的配置对于编译和运行Java程序至关重要。

设置环境变量的基本步骤如下:

  1. JAVA_HOME: 首先,需要设置JAVA_HOME环境变量,它指向JDK的安装目录。

    • 在Windows上,可以在“系统属性”->“高级”->“环境变量”中设置。
    • 在Linux和macOS上,可以在用户的home目录下的 .bashrc 或 .zshrc 文件中添加以下行:
      sh export JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::") export PATH=$JAVA_HOME/bin:$PATH
    • 使用 source ~/.bashrc 或 source ~/.zshrc 重新加载配置文件。
  2. PATH: PATH环境变量需要包含JDK的bin目录,以便系统可以识别 java 和 javac 命令。

    • PATH变量中应加入 $JAVA_HOME/bin 。

验证配置:
打开命令行,输入以下命令检查Java版本:

java -version

如果配置正确,将看到JDK 11版本信息。

2.2.2 针对JDK 11的项目配置技巧

Java项目的配置主要涉及到构建工具的配置,如Maven和Gradle。

  1. Maven的pom.xml配置:
    xml <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>
    这里指定了源代码和目标字节码的版本。

  2. Gradle的build.gradle配置:
    gradle apply plugin: 'java' sourceCompatibility = 11 targetCompatibility = 11
    Gradle中通过设置兼容性来确保项目使用JDK 11。

  3. IDE配置:
    在Eclipse或IntelliJ IDEA中,也需要设置项目使用的JDK版本。

  • Eclipse: “Project” -> “Properties” -> “Java Build Path” -> “Libraries” -> “Configure Build Path” -> 选择JDK。
  • IntelliJ IDEA: “File” -> “Project Structure” -> “Project” -> “Project SDK”。

在接下来的章节中,我们将深入探讨JDK 11的新特性和技术应用,为Java开发者提供实用的工具和方法,以提高开发效率和项目质量。

3. JDK 11的新特性和技术应用

3.1 模块化系统(Project Jigsaw)

3.1.1 模块化系统的基本概念

自Java 9引入模块化系统以来,模块化已经成为Java平台的一个核心特性。模块化系统设计用于解决Java运行时系统的可维护性和可扩展性问题。它允许开发人员将大型应用拆分成更小、更易于管理的部分,同时提供了一种明确地声明这些部分之间依赖关系的方法。这种方式有利于提高系统的整体安全性,因为它限制了代码的可见性和访问性。

模块化系统的构建块是模块(module),一个模块包含了一组包和资源,以及一个模块声明文件(module-info.java),该文件中定义了模块的名称、依赖以及对外暴露的API。这种结构的好处在于它允许编译器在编译时检查模块间的依赖关系是否得到了正确处理,增强了代码的封装性和模块间的清晰界限。

3.1.2 如何将传统项目迁移到模块化系统

迁移到模块化系统是一项挑战,尤其是对于那些大型、历史遗留的Java项目。JDK 11提供了逐步迁移的途径,允许开发者以增量方式将项目模块化。迁移过程包括几个关键步骤:

  1. 识别项目的JAR依赖 :分析项目依赖哪些外部库以及库的版本。
  2. 创建模块描述符 :为每个模块创建module-info.java文件,定义模块名称和依赖。
  3. 模块化JDK使用 :确保使用的JDK版本支持模块化,并利用Java 9+的特性。
  4. 测试模块化系统 :在模块化环境中测试代码的运行,确保一切功能正常。
  5. 更新代码以适应模块化 :可能需要修改代码来适应新的封装规则,尤其是那些依赖于原来包级私有成员的代码。

迁移过程中,可以利用JDK提供的 jlink 工具来创建一个自定义的JRE,只包含你项目需要的模块,这有助于减少最终应用的大小。同时, jmod 工具可以用来创建和操作JDK模块,帮助管理模块文件。

3.2 内置HTTP客户端API的应用

3.2.1 HTTP客户端API的新增功能介绍

在JDK 9中引入的内置HTTP客户端API在JDK 11中得到了进一步改进。新版本的HTTP客户端API(java.net.http.HttpClient)提供了一种更高效、异步的方式来处理HTTP/1.1和HTTP/2请求。与旧的 HttpURLConnection 相比,新API提供了更好的性能和更丰富的功能,包括:

  • 异步请求处理,提高了并发性能。
  • 对HTTPS和HTTP/2的原生支持。
  • 响应体自动存储在内存或临时文件中,减少了内存使用。
  • 支持WebSocket和服务器推送特性。
  • 支持直接在内存中读写JSON和其他格式的响应体。
  • 灵活的重定向策略和更简单的cookie处理。

3.2.2 实战:构建高性能的HTTP客户端

要构建一个使用新HTTP客户端API的高性能HTTP客户端,我们需要了解如何利用其异步特性。下面是一个简单的例子,展示了如何异步请求一个网页并处理响应:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

public class HttpClientExample {

    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com"))
                .build();

        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join(); // wait for processing to complete
    }
}

在这个例子中, sendAsync 方法创建了一个异步的HTTP请求。 thenApply 方法用于处理响应体,而 thenAccept 用于输出响应内容。通过使用 CompletableFuture ,我们可以构建一个完整的异步管道,实现高效的请求-响应链。

HttpResponse.BodyHandlers.ofString() 是处理响应体的一个简单方法,它会自动将响应体内容转换为字符串。对于大型响应体或者对性能要求更高的场景,可以使用 HttpResponse.BodyHandlers.ofByteArray() 将响应体以字节数组的形式处理,或者实现自己的 BodyHandler 来处理原始的 InputStream 。

3.3 文本块(Text Blocks)的使用技巧

3.3.1 文本块的定义和优势

JDK 11中引入了一个非常有用的功能——文本块。文本块允许在Java代码中表示格式化的字符串字面量,它被定义在一对三重双引号( """ )之间,并且可以跨越多行。文本块解决了多行字符串处理中的几个常见问题:

  • 可读性 :文本块让多行字符串的可读性更好,无需转义字符。
  • 简化JSON和XML处理 :使用文本块可以更方便地在代码中处理大型字符串,如JSON、HTML或XML。
  • 减少不必要的空白字符 :文本块在编译时自动移除字符串内容开头和结尾的空白字符,减少错误和不一致性。

3.3.2 如何在代码中使用文本块优化数据处理

文本块在处理JSON和XML数据时特别有用。例如,假设我们需要在代码中嵌入JSON数据以发送给远程API,以前需要小心翼翼地转义反斜线和双引号,现在可以使用文本块来简化这个过程:

String jsonString = """
    {
        "name": "John",
        "age": 30,
        "city": "New York"
    }
    """;

// 使用文本块的字符串不会包含新行,除非显式指定。

文本块非常自然地支持字符串的格式化,你可以直接在代码中嵌入其他字符串表达式:

String name = "Alice";
String jsonString = String.format("""
    {
        "name": "%s",
        "age": %d,
        "city": "%s"
    }
    """, name, 28, "Seattle");

文本块还支持字符串插值,这是Java 13中引入的特性,允许在字符串字面量中直接嵌入表达式:

String jsonString = """
    {
        "name": "${name}",
        "age": ${age},
        "city": "${city}"
    }
    """.formatted(name, age, city);

请注意, formatted 方法是Java 13引入的,用于处理字符串插值。在JDK 11中,字符串插值还不能直接使用。

在实际使用中,需要注意的是,文本块中的换行符在编译时会被处理。在字符串字面量中,换行符会被转换成单个换行符 \n 。如果你需要保留字符串中的所有换行符,需要在字符串前加上 s 前缀,表示字符串是原始字符串(raw):

String multiLineString = """
    Hello
    World
    """;
// 编译后,此字符串的值为"Hello\nWorld\n"

String multiLineRawString = s"""
    Hello
    World
    """;
// 编译后,此字符串的值为"Hello\nWorld\n"

在处理大型字符串时,文本块能显著提高代码的可读性和可维护性。对于数据处理任务,如配置文件和国际化信息,文本块提供了一个更为直观和简洁的解决方案。

4. JDK 11的高级语言特性和API改进

4.1 改进的switch语句(Switch Expressions)

4.1.1 从传统switch到switch表达式的演变

在早期的Java版本中,switch语句一直保持其传统的格式,它通过使用 break 语句来防止语句的自动穿透。然而,这种传统的格式存在一定的局限性,比如代码冗长和容易出错等。从JDK 12开始,Java引入了基于箭头( -> )的switch表达式,它允许我们使用更简洁的语法来处理更复杂的逻辑分支。

// 传统 switch 示例
int numLetters;
switch (day) {
    case 1:
    case 2:
    case 3:
        numLetters = 6;
        break;
    case 4:
    case 5:
    case 6:
    case 7:
        numLetters = 7;
        break;
    default:
        throw new IllegalStateException("Invalid day: " + day);
}

// switch 表达式 示例
int numLetters = switch (day) {
    case 1, 2, 3 -> 6;
    case 4, 5, 6, 7 -> 7;
    default -> throw new IllegalStateException("Invalid day: " + day);
};

4.1.2 实践:使用switch表达式简化代码逻辑

使用switch表达式可以显著提高代码的可读性和简洁性。特别是在需要返回值的情况下,传统switch需要在每个case块中显式返回一个值,而switch表达式可以内联返回值,使得代码更加直观。

// 使用 switch 表达式来简化代码逻辑
String dayDescription = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    case 4 -> "Thursday";
    case 5 -> "Friday";
    case 6 -> "Saturday";
    case 7 -> "Sunday";
    default -> throw new IllegalArgumentException("Invalid day: " + day);
};

在这个例子中,通过一个switch表达式,我们就可以根据输入的 day 变量返回对应的星期名称。这种写法比传统的switch语句更为简洁和直观。

4.2 动态类型(var关键字)的应用案例

4.2.1 var关键字的介绍和使用场景

在JDK 10中,Java引入了一个新的局部变量类型推断的关键字 var ,它允许开发者在声明变量时省略类型信息,由编译器根据上下文推断出具体的类型。这在很多情况下可以简化代码的编写,尤其是当变量类型非常长或者使用了泛型时。

// 使用 var 关键字声明变量
var list = List.of("Apple", "Banana", "Cherry");
for (var fruit : list) {
    System.out.println(fruit);
}

在这个例子中,变量 list 和循环中的 fruit 都是使用 var 关键字声明的。这不仅减少了代码量,也使代码更加易于阅读。

4.2.2 代码示例:利用var简化变量声明

使用 var 关键字可以减少代码的冗余,并且使得代码更加清晰。特别是在复杂的泛型类型中,使用 var 可以避免写出冗长且难以理解的类型声明。

// 传统泛型类型声明
List<String> fruits = new ArrayList<>();

// 使用 var 关键字简化泛型声明
var fruits = new ArrayList<String>();

在上面的例子中,我们使用 var 关键字来声明一个字符串列表。这样做不仅减少了代码量,而且代码的可读性也有所提高,因为编译器能够准确地推断出变量的类型。

4.3 新的语言特性与API改进综述

4.3.1 JDK 11引入的新语言特性详解

JDK 11引入了几个新的语言特性,除了 var 关键字和switch表达式之外,还包括:

  • Local-Variable Syntax for Lambda Parameters : 允许在lambda表达式中使用 var 关键字。
  • Epsilon: A No-Op Garbage Collector : 作为一种实验性的垃圾收集器,它不进行任何实际的垃圾收集工作,旨在帮助开发者进行性能测试。

4.3.2 API改进及其对开发效率的影响

JDK 11的API改进包括对现有类库的增强以及新库的添加,例如:

  • 新的HTTP客户端 : Java 11引入了一个全新的 HttpClient ,它基于 java.net.http 包,提供了更高效的HTTP/2支持。
  • 新的API方法 : Files 类和 String 类都增加了新的方法,例如 Files.writeString 和 String.stripIndent ,它们使得处理文件和字符串变得更加方便。
// 使用 Files 类新方法
Path path = Paths.get("example.txt");
String content = "Hello, Java 11!";
Files.writeString(path, content);

// 使用 String 类新方法
String original = "  Hello, World!  ";
String stripped = original.stripIndent();
System.out.println(stripped);

在这个例子中, Files.writeString 方法允许直接将字符串写入文件,而 String.stripIndent 方法可以去除字符串的首尾空白字符,这些新API使得代码更加简洁和易于编写。

5. Nashorn JavaScript引擎的替代方案及安全更新

5.1 Nashorn引擎的移除原因及影响

5.1.1 Nashorn引擎的移除背景分析

在JDK 11的开发过程中,Nashorn JavaScript引擎被正式移除。Nashorn在JDK 8中被引入,目的是提供一个高性能的JavaScript执行环境。然而,随着时间的推移,JavaScript领域发生了显著变化,特别是在Web应用和Node.js的普及下,Nashorn所支持的JavaScript版本已不再符合现代Web开发的需求。

Nashorn引擎的主要问题在于其对于ECMAScript 6 (ES6) 及之后版本的支持不够全面。ES6引入了许多重要的新特性和改进,这是许多现代JavaScript项目不可或缺的一部分。由于Nashorn已经无法与这些新特性同步,Oracle决定在JDK 11中停止对它的支持,从而鼓励开发者转向更加现代和维护良好的JavaScript运行环境。

5.1.2 Nashorn引擎替代方案的探索

随着Nashorn的移除,开发者需要寻找替代方案来继续在Java平台上运行JavaScript代码。一个可行的选择是使用其他开源的Java绑定JavaScript的引擎,如GraalVM的Truffle。Truffle提供了更高级的性能优化选项,以及对新JavaScript版本的更好支持。

另一个备选方案是直接在Java代码中嵌入JavaScript代码,例如使用JEP 323: Project Valhalla中提出的外部函数和内存API,可以更高效地与本地代码交互。此外,Java社区也逐渐增加了对前端技术的关注,诸如JavaFX与Web技术的融合,甚至出现了将JavaScript代码编译为Java字节码的新工具,这些都可以作为Nashorn的替代方案。

5.2 安全更新和兼容性维护的重要性

5.2.1 安全更新机制和实践策略

软件的安全性是当今IT领域最为关注的议题之一,特别是在Java这类广泛使用的编程语言中。JDK 11作为一个长期支持版本,对安全更新和漏洞修补给予了高度重视。Oracle和社区都认识到,对于一个长期支持的软件,快速响应安全漏洞是至关重要的。

JDK的安全更新机制包括定期的安全补丁发布,这些补丁通常是针对已知漏洞的修复。为了保证更新的顺利进行,JDK提供了详细的更新指南和自动化更新工具。例如,对于企业用户,可以使用Java的更新工具(如Java Update Tool)来管理JDK的更新。在JDK 11中,Oracle提供了JEP 189,引入了新的安全更新机制,使得JDK可以更加方便地通过HTTP/2进行安全更新。

5.2.2 兼容性维护的最佳实践和建议

兼容性是长期支持版本中另一个关键的问题。JDK 11在设计之初就考虑到了与旧有系统的兼容性。为了帮助开发者平滑过渡,JDK 11包含了许多机制来确保新的Java版本可以和旧代码一起运行。

最佳实践建议开发者在进行JDK版本升级前进行充分的测试。可以使用Java的模块化特性来逐步迁移和隔离代码,确保新旧代码的兼容性。同时,为了更好地维护项目的依赖关系,建议使用Maven或Gradle等构建工具,它们可以自动管理项目依赖,简化迁移过程。最后,关注JDK的官方更新日志和安全公告,及时掌握关键更新信息,对于保持长期的项目稳定性和安全性至关重要。

通过以上介绍,我们对Nashorn引擎的移除、替代方案以及安全更新与兼容性维护的策略有了更全面的了解。接下来,在第六章中,我们将探讨JDK 11在企业级应用中的前景以及如何参与JDK社区和贡献代码。

6. JDK 11的未来展望和社区贡献

在软件开发领域,Java一直是常青树,而JDK 11作为一款长期支持版本(LTS),其稳定性和高效性为企业级应用提供了坚实的基础。本章将深入探讨JDK 11在企业级应用中的未来展望以及如何参与JDK社区并贡献代码。

6.1 JDK 11在企业级应用中的前景

6.1.1 企业级应用对JDK 11的需求分析

企业级应用通常要求系统具有较高的稳定性和性能,尤其是在处理大规模数据和高并发请求时。JDK 11通过引入新的API和语言特性,提供了对这些需求的支持:

  • HTTP/2客户端API :随着互联网的快速发展,后端服务需要更高效的通信方式。JDK 11原生支持HTTP/2和WebSocket,提升了网络通信的性能和稳定性。
  • 新的垃圾收集器 :G1垃圾收集器经过改进,新增了Epsilon和ZGC两款收集器,这些收集器的引入使得应用在高并发处理时更加稳定。

6.1.2 JDK 11的长期支持对企业的影响

对于企业而言,长期支持(LTS)版本的JDK意味着更少的迁移工作和更长的技术迭代周期。JDK 11作为一个LTS版本,企业可以借此机会优化现有系统,并平稳过渡到新的技术平台:

  • 成本效益 :使用JDK 11可以减少因频繁更新导致的维护成本。
  • 技术积累 :LTS版本为企业提供一个稳定的技术基础,使企业能够集中精力在业务创新上。

6.2 如何参与JDK社区和贡献代码

6.2.1 JDK社区的贡献途径和机制

参与开源社区是提高个人技能和扩大影响力的有效途径。JDK社区鼓励开发者通过多种方式贡献自己的力量:

  • 报告问题和缺陷 :在JIRA上为JDK报告问题和缺陷,帮助Oracle工程师更有效地定位和修复问题。
  • 编写文档 :为JDK项目编写和改进官方文档,让更多的开发者能够快速上手和有效使用JDK。
  • 贡献代码 :直接在GitHub上提交代码贡献,参与开源项目的新功能开发或现有功能的优化。

6.2.2 开源贡献的实践与心得体会

贡献代码并不简单,它需要耐心、细心以及良好的编程习惯。以下是一些实践技巧和心得体会:

  • 遵守编码规范 :在提交代码之前,确保遵守项目编码规范。JDK社区通常要求代码风格与Oracle的编码风格保持一致。
  • 编写单元测试 :为你的代码编写全面的单元测试,确保功能的稳定性和可靠性。
  • 积极参与讨论 :通过邮件列表、论坛或GitHub的Issues参与讨论,提出自己的见解并听取反馈。
  • 持续学习和改进 :通过参与开源项目,不断学习新技术、新思想,并在实践中不断改进自己的代码质量。
// 示例:JDK 11中引入的新API使用示例代码
try (var socketChannel = SocketChannel.open(new InetSocketAddress("example.com", 80))) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    while (true) {
        buffer.clear();
        var numRead = socketChannel.read(buffer);
        if (numRead == -1) {
            break;
        }
        buffer.flip();
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        System.out.println();
    }
}

通过上述代码块示例,我们可以看到在JDK 11中使用新的try-with-resources语句和var关键字的便利性。

参与开源项目不仅是一个技术实践的过程,也是一个与来自世界各地的开发者合作交流的过程。JDK社区的每位成员,无论经验多少,都有机会为JDK的进步作出自己的贡献。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Oracle推出的JDK 11是一个具有长期支持(LTS)的Java开发工具包版本,它提供8年以上的免费安全更新。本压缩包包含适用于Windows 64位、MacOS和Linux的JDK11安装文件。JDK 11引入了新特性,如模块化系统、增强的HTTP客户端、文本块、改进的switch语句、动态类型支持以及移除Nashorn JavaScript引擎等,以提高性能、安全性和开发效率。开发者可以通过安装和应用此版本,利用其新功能来构建和运行Java应用。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐