Spring 5.0.0框架介绍_中文版_第二章

文章作者:Tyan
博客:noahsnail.com

2.Spring框架介绍

Spring框架是一个为支持开发Java应用提供全面基础架构的Java平台。Spring处理基础架构,因此你可以集中精力在你有应用上。

Spring使你能创建简单Java对象(POJO)并能非侵入式的将企业服务应用到简单Java对象(POJO)上。

作为一个应用开发者,下面是一些你能从Spring平台受益的例子:
  • 在一个数据库业务中执行一个Java方法而不必处理业务APIs
  • 使一个本地的Java方法可以远程调用而不必处理远程APIs
  • 使一个本地Java方法变为管理操作而不必处理JMX APIs
  • 使一个本地Java方法变为消息处理器而不必处理JMS APIs

2.1依赖注入和控制反转

Java应用——一个不精确的术语,既可以表示受限制的嵌入式应用又可以表示N层服务端的企业级应用——通常由许多对象构成,这些对象协作形成完整的应用程序。因此一个应用程序中的对象是相互***依赖***的。

尽管Java平台提供了大量的应用开发功能,但是它缺少把这些基本构建模块组织成一个连贯整体的方法,并把组织基本构建模块的任务留给了架构师和开发者。虽然你可以使用设计模式例如***工厂模式、抽象工厂模式、生成器模式、装饰模式、服务定位模式***来创建构成应用的各种类和对象实例,但这些设计模式很简单:命名的最佳方法、模式的作用描述、应用模式的位置、模式解决的问题等等。模式使最佳实践形式化了,这意味着你必须在你的应用中***自己实现它***。

Spring框架中的***控制反转***(IoC)组件通过提供一种形式化方法解决了这个问题,这个形式化方法将不同的组件创建到一个随时可用的完整的工作应用中。Spring框架将形式化的设计模式编码成了你可以集成到你自己的应用中的最好对象。许多组织和机构用这种方式应用Spring框架来构建鲁棒的、***可维护***的应用。

背景
“问题是什么是控制反转?” 2004年Martin Fowler在他的网站上提出了这个关于控制反转(IoC)问题。Fowler建议重新命名这个原理使它更一目了然并且提出了依赖注入

2.2 模块

Spring框架包含的功能大约由20个模块组成。这些模块按组可分为核心容器、数据访问/集成,Web,AOP(面向切面编程)、设备、消息和测试,如下图所示。

图2.1 Spring框架概述
image

接下来的章节列出了每个功能可用的模块、它们的工件名字以及它们包含的主题。工件名字与依赖管理工具中使用的***artifact IDs***有关。

2.2.1 核心容器

核心容器功能包括`spring-core`, `spring-beans`, `spring-context`, `spring-context-support`, and `spring-expression`(Spring表现语言)模块。

`spring-core`和`spring-beans`模块提供了框架的基础结构部分,包含控制反转(IoC)和依赖注入(DI)功能。`BeanFactory`是工厂模式的高级实现。它去掉了程序单例模式的需求并且允许你从实际的程序逻辑中解耦配置和依赖的指定。

上下文(`spring-context`)模块建立在由Core模块和Beans模块提供的坚实基础上:它是在类似于JNDI注册表式的框架风格模式中访问对象的一种方法。上下文模块继承了Beans模块的功能,并添加了对国际化(例如使用资源捆绑)、事件传播、资源加载和上下文透明创建(例如通过Servlet容器)的支持。上下文模块也支持Java EE功能例如EJB,JMX和基本的远程。`ApplicationContext`接口是上下文模块的焦点。`spring-context-support`支持将第三方库集成进Spring应用程序上下文中,特别是缓存(EhCache, JCache)和定时执行(CommonJ, Quartz)。

2.2.2 面向切面编程(AOP)和设备(Instrumentation)

`spring-aop`模块提供了***AOP*** Alliance-compliant(AOP联盟)面向切面编程的实现,例如允许你自定义方法拦截器和切入点来清晰的解耦功能实现上应该分开的代码。使用源码级的元数据功能,你也可以将行为信息合并到你的代码中,在某种程度上这类似于.NET的属性值。

独立的`spring-aspects`模块提供了与AspectJ的集成。

`spring-instrument`模块提供了类设备支持和类加载器的实现,它们可以在某些应用服务器中使用。`spring-instrument-tomcat`模块包含了Tomcat的Spring设备代理。

2.2.3 消息

Spring 4框架中包含了`spring-messaging`模块,它对***Spring集成***项目例如`Message`, `MessageChannel`, `MessageHandler`和其它作为消息应用服务基础的项目进行了重要的抽象。这个模块也包含了一系列将消息映射到方法上的注解,这个注解与基于编程模型Spring MVC注解类似。

2.2.4 数据访问/集成

***数据访问/集成***层包括JDBC,ORM,OXM,JMS和事务模块。

`spring-jdbc`模块提供了JDBC抽象层,不需要再编写单调的JDBC代码,解析数据库提供商指定的错误编码。

`spring-tx`模块为实现指定接口和所有的简单Java对象(POJOs)的类提供编程式(programmatic)和声明式(declarative)的业务管理。

`spring-orm`模块提供流行的对象关系映射APIs的集成层,包括JPA和Hibernate。在使用`spring-orm`模块时,你可以将Spring的其它功能与这些O/R-mapping框架结合起来使用,例如前面提到的简单声明式业务管理的功能。

`spring-oxm`模块提供对Object/XML映射实现例如JAXB,Castor,JiBx和XStream的抽象层。

`spring-jms`模块(Java消息服务)包含产生和处理小心的功能。从Spring 4.1框架开始它提供了与`spring-messaging`的集成。

2.2.5 网络

网络层包含`spring-web`, `spring-webmvc`和`spring-websocket`模块。

`spring-web`模块提供基本的面向网络集成功能,例如multipart文件上传功能,使用Servlet监听器来初始化Ioc容器和面向网络的应用程序上下文。它也包含了HTTP客户端和Spring远程支持中网络相关的部分。

`spring-webmvc`模块(也被称为***Web-Servlet***模块)包含了Spring的model-view-controller(MVC)和REST Web Services的网络应用实现。Spring的MVC框架提供了对域模型代码,web表单,Spring框架其他功能的完全分离。

2.2.6 测试

`spring-test`模块支持单元测试,Spring组件和JUnit或TestNG的集成测试。它提供了Spring的`ApplicationContexts`加载和这些上下文缓存的一致。它也提供了可以单独测试代码的模拟对象。

2.3 使用场景

前面描述的构建模块使Spring在许多场景中都有一个合理选择,从运行在资源受限的嵌入式应用到全面成熟的企业级应用都在使用Spring的业务管理功能和网络框架集成。

图2.2标准成熟的Spring web应用
image

Spring的声明式业务管理功能使web应用全面的事务化,如果你用过EJB容器管理业务的话你会发现它们基本一样。你所有自定义的业务逻辑都可以用POJOs实现并通过Spring的IoC容器管理。附加业务包括支持邮件发送和验证,这个是独立于web层之外的,你可以自由选择验证规则执行的位置。Spring对ORM的支持与JPA和Hibernate进行了集成;例如,当你使用Hibernate时,你可以继续使用你现有的映射文件和标准的Hibernate `SessionFactory`配置。表单控制器被无缝的将web层和领域模型进行了集成,对于你的领域模型来讲不再需要`ActionForms`或其它的将HTTP参数转换成值的类。

图2.3. 使用第三方web框架的Spring中间层
image

有时候环境不允许你完全转成一个不同的框架。Spring框架***不***强迫你都采用它内部的东西;它不是一个***要么全有要么全无***的解决方案。现有的采用Struts,Tapestry,JSF或其它UI框架构建的前端可以与基于Spring的中间层进行集成,这可以让你使用Spring的业务功能。你只需要简单的用`ApplicationContext`和`WebApplicationContext`绑定你的业务逻辑然后集成到web层即可。

图2.4. 远程应用场景
image

当你需要通过web服务访问现有代码时,你可以使用Spring的`Hessian-`, `Rmi-` 或 `HttpInvokerProxyFactoryBean`类。这能让远程访问现有应用变得很容易。

Figure 2.5. EJBs-包装现有的POJOs
image

Spring框架也为企业JavaBeans提供了访问和抽象层,使你能重用你现有的POJOs,为了可扩展使用可以将它们包装成无状态的session beans,自动防故障的web应用可能需要声明安全。

2.3.1 依赖管理和命名约定

依赖管理和依赖注入是完全不同的两件事。为了能你的应用中使用Spring的优秀特性(像依赖注入),你需要收集所有必要的库(jar文件)并在运行时将它们添加到classpath中,有可能在编译时就需要添加。这些依赖不是要被注入的虚拟组建,而是文件系统中的物理资源(通常情况下)。这些依赖管理的过程包括资源的定位、存储和添加到classpath中。依赖可以是直接的(例如:我的应用在运行时依赖Spring),或间接的(例如:我的应用依赖`commons-dbcp`,而它依赖`commons-pool`)。间接依赖也被称为"传递式"的,这些依赖也是最难识别和管理的。

如果你想使用Spring,你需要有包含你需要的Spirng功能的jar库副本。为了使这个更容易,Spring被打包成了一系列尽可能将依赖分离开的模块,例如你不想写web应用那你就不需要spring-web模块。为了在本指南中谈及Spring的库模块,我们使用了一个简写命名约定`spring-*`或`spring-*.jar`,`*`表示模块的简写名字(例如`spring-core`, `spring-webmvc`, `spring-jms`等等)。实际中你使用的jar文件名字通常是模块名加上版本号(例如`spring-core-5.0.0.BUILD-SNAPSHOT.jar`)。

Spring框架的每次发布都会下面的地方公布artifacts:
  • Maven Central,Maven查询的默认仓库,使用时不需要任何特定的配置。Spring依赖的许多共通库也可以从Maven Central获得,Spring社区的很大一部分都在使用Maven进行依赖管理,因此这对他们来说是很方便的。jar包的命名形式是spring-*-<version>.jar,Maven GroupId是org.springframework
  • 由Spring掌管的公开Maven库。除了最终的GA release(公开可获得的版本)之外,这个仓库也有开发版本的快照和milestone版本。jar包的命名形式和Maven Central一样,这是一个可以使用Spring开发版本有用地方,而其它的库部署在Maven Central。这个库也包含的捆绑分布的zip文件,这个zip文件中所有的Spring jar包被捆绑到一起很容易下载。

    你将在下面找到Spring artifacts列表。想要每个模块更全面的描述,请看2.2小节。
    

表 2.1. Spring Framework Artifacts
image

Spring依赖和依赖Spring

虽然Spring提供集成并支持大范围内的企业和其它外部工具,但它有意使它的强制性依赖到一个绝对最小化的程度:对于简单的用例你不应该为了使用Spring而定位和下载(即使是自动的)许多jar库。对于基本的依赖注入仅有一个强制性的外部依赖,那个依赖是关于日志的(在下面可以看到日志选择更详细的描述)。

接下来我们概述配置一个依赖于Spring的应用需要的基本步骤,首先Maven的,其次是Gradle的,最后是Ivy的。在所有的案例中,如果有任何不清楚的地方,请参考你的依赖管理系统的文档,或者看一些示例代码——Spring本身构建时使用Gradle来管理依赖,我们例子中大多数是使用Gradle和Maven的。

Maven依赖管理

如果你正在使用Maven来进行依赖管理,那你不必显式的提供日志依赖。例如,为了创建一个应用上下文,使用依赖注入来配置一个应用,你的Maven依赖看上去是这样的:
1
2
3
4
5
6
7
8
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies>
就是它。注意如果你不需要编译Spring APIs,scope可以被声明成rumtime,这是典型的基本依赖注入的情况。

上面的例子是采用Maven中心仓库的。为了使用Spring Maven仓库(例如:使用milestone版本或snapshot版本),你需要在Maven配置中指定仓库的位置,完整的版本:
1
2
3
4
5
6
7
<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于milestone版本:
1
2
3
4
5
6
7
<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于snapshot版本:
1
2
3
4
5
6
7
<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>

Maven “材料清单” 依赖

在使用Maven时,有可能会偶然的将不同版本的Spring JARs混合起来。例如,你可能找到一个第三方库,或另一个Spring项目,通过传递依赖进入了一个更旧的版本。如果你忘了自己显式的声明一个直接依赖,会产生各种意想不到的问题。

为了解决这种问题,Maven支持"材料清单"(BOM)依赖的概念。你可以在你的`dependencyManagement`部分导入`spring-framework-bom`来确保所有的Spring依赖(直接和传递的)都是同一个版本。
1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用BOM的额外好处是当依赖Spring框架的artifacts时你不再需要指定`<version>`属性:
1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>

Gradle依赖管理

为了在Gradle构建系统中使用Spring仓库,在`repositories`部分需要包含合适的URL:
1
2
3
4
5
repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}
当合适的时候你可以修改`repositories`的URL从`/release`到`/milestone`或`/snapshot`。一旦一个仓库被配置了,你可以用通常的Gradle方式声明依赖:
1
2
3
4
dependencies {
compile("org.springframework:spring-context:5.0.0.BUILD-SNAPSHOT")
testCompile("org.springframework:spring-test:5.0.0.BUILD-SNAPSHOT")
}

Ivy依赖管理

如果你更喜欢使用Ivy来管理依赖,这有类似的配置选择。

为了配置Ivy指定Spring仓库,添加下面的解析器到你的`ivysettings.xml`:
1
2
3
4
5
<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>
当合适的时候你可以更改根URL从`repositories`的URL从`/release`到`/milestone`或`/snapshot`。

一旦配置了,你可以通过一般的方式添加依赖。例如(在`ivy.xml`):
1
2
<dependency org="org.springframework"
name="spring-core" rev="5.0.0.BUILD-SNAPSHOT" conf="compile->runtime"/>

发行版Zip文件

尽管使用一个支持依赖管理的构建系统是获得Spring框架的推荐方式,但仍然可以下载发行版的Zip文件。

发行版的zips是被发布到Spring Maven仓库(这只是为了我们的方便,为了下载它们你不需要Maven或任何其它的构建系统)。

为了下载发行版zip,打开浏览器输入[http://repo.spring.io/release/org/springframework/spring](http://repo.spring.io/release/org/springframework/spring),然后选择你想要的版本的合适子文件夹。发行版文件以`-dist.zip`结尾,例如spring-framework-{spring-version}-RELEASE-dist.zip。发行版也可以公布milestone版本或snapshots版本。

2.3.2 日志

日志对于Spring来说是一个非常重要的依赖,因为:*a)*它是唯一的强制性外部依赖,*b)*每个人都喜欢从他们使用的工具中看到一些输出,*c)*Spring集成了许多其它的工具,这些工具也选择了日志依赖。应用开发者的一个目标就是对于整个应用来讲,经常要有一个中心地方来进行日志的统一配置,包括所有的外部组件。比它更困难的可能是有太多的日志框架去选择。

Spring中的强制日志依赖是Jakarta Commons Logging API (JCL)。我们编译JCL并使JCL`log`对象对类是可见的,这扩展了Spring框架。所有版本的Spring采用同一个日志库:移植是容易的,因为即使应用扩展了Spring但保留了向后兼容性,这一点对用户来说很重要。我们实现这个的方式是让Spring的模块之一显式的依赖`commons-logging`(JCL的标准实现),然后使其它模块在编译时依赖这个模块。例如如果你在使用Maven,想找出依赖于`commons-logging`的依赖在哪,它在Spring中,更确切的说它是在Spring的中心模块`spring-core`中。

关于`commons-logging`的一件好事是要使你的应用工作你不需要任何其它的东西。它有一个运行时发现算法,这个算法能寻找其它的日志框架在知名的classpath中,并使用一个它认为是合适的(或者你告诉它你想用哪个如果你需要的话)。如果找不到任何别的你可以从JDK中找到一个非常美好漂亮的日志(java.util.logging或缩写为JUL)。在大多数环境中你可以发现你的Spring应用恰当地运行并输出日志到控制台输出框中,那是很重要的。

不使用Commons Logging

不幸的是, 虽然`commons-logging`的运行时发现算法对于终端用户是方便的,但它是有问题的。如果我们将时钟回拨,把Spring作为一个新项目重新开始,将会选择一个不同的日志依赖。第一个选择可能是Simple Logging Facade for Java(SLF4J),应用内部使用Spring的人使用的许多其它工具也用了SLF4J。

这儿有两种方式关掉`commons-logging`:
  1. spring-core模块排除依赖(因为它是唯一的显式依赖)commons-logging的模块
  2. 依赖于一个特定的commons-logging依赖,用一个空jar替换这个依赖(更多细节可以在SLF4J FAQ中找到)。

    为了排除`commons-logging`,把下面的内容加入到你的`dependencyManagement`部分:
    
1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
现在这个应用可能是坏了的,因为在classpath中没有JCL API的实现,为了解决这个问题必须提供一个新的实现。在接下来的部分我们将向你展示怎样提供一个JCL替代实现,使用SLF4J就是一个例子。

使用SLF4J

SLF4J是一个更纯净的依赖并且在运行时比`commons-logging`更有效,因为它使用编译时绑定来代替运行时查找集成的其它日志框架。这也意味着你必须更清楚你想要运行时发生什么,然后相应的声明它或配置它。SLF4J提供跟许多常用日志框架的绑定,因此你通常可以选择一个你正在使用的日志框架,然后绑定到配置和管理上。

SLF4J提供跟许多常用日志框架的绑定,包括JCL,它做的恰恰相反,建立其它日志框架和它自己的纽带。因此为了在Spring中使用SLF4J,你需要用SLF4J-JCL连接器取替换`commons-logging`依赖。一旦你在Spring内部使用了日志调用,Spring会将日志调用变为调用SLF4J API,如果你应用中其它的库调用了那个API,你将有一个单独的地方配置和管理日志。

一个常用的选择连接Spring和SLF4J,然后提供SLF4J到Log4J的显式绑定。你需要提供四个依赖(排除现有的`commons-logging`):连接、SLF4J API、到Log4J的绑定、Log4J本身的实现。在Maven中你可能这么做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
这可能看起来为了得到一些日志需要很多依赖。还好它是可选的,比起`commons-logging`的关于类加载器的问题,尤其是你在一个像OSGi平台那样严格的容器中的时候,它应该更好操作。据说这儿也有一个性能提升,因为绑定是在编译时而不是在运行时。

在SLF4J用户中,一个更通用的选择是直接绑定到[Logback](http://logback.qos.ch/),这样使用步骤更少且依赖也更少。这去除了外部绑定步骤,因为Logback直接实现了SLF4J,因此你仅需要依赖两个库而不是四个(`jcl-over-slf4j`和`logback`)。如果你这样做的话你可能也需要从其它的外部应用中(不是从Spring)排除slf4j-api依赖,因为你在classpath中仅需要一个版本的API。

使用Log4J

许多人使用Log4j作为配置和管理的日志框架。它有效且完善的,当我们构建和测试Spring时,实际上这就是在运行时我们使用的东西。Spring也提供一些配置和初始化Log4j的工具,因此在某些模块有可选的Log4j的编译时依赖。

为了使Log4j能与默认的JCL依赖(`commons-logging`)一起工作,所有你需要做的是把Log4j放到classpath中,并提供一个配置文件(`log4j.properties`或`log4j.xml`在classpath的根目录)。对于Maven用户依赖声明如下:
1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
下面是一个log4j.properties输出日志到控制台的样本:

image

运行时容器使用本地化JCL

许多人在容器中运行他们的Spring应用,容器本身提供了一个JCL实现。IBM Websphere Application Server (WAS) 是原型。这经常会引起问题,不幸的是没有一劳永逸的解决方案;在大多数环境下简单的执行`commons-logging`是不够的。

为了使这个更清楚:报告的问题本质上一般不是关于JCL的,或关于`commons-logging`的:而是他们去绑定`commons-logging`到其它的框架上(通常是Log4j)。这可能会失败因为`commons-logging`在一些容器的旧版本(1.0)和大多数人使用的现代版本(1.1)中改变了运行时发现方式。Spring不使用JCL API的和任何不常用的部分,因此不会有问题出现,但是一旦Spring或你的应用试图去输出日志,你可能发现到Log4j的绑定是不起作用的。

在这种情况下使用WAS最容易做的事是逆转类加载层(IBM称为"parent last"),为的是应用能控制依赖,而不是容器。虽然这种选择并非总是公开的,但在公共领域对于替代方法有许多其它的建议,你的解决这个问题花的时间可能是不同的,这取决于确定的版本和容器集合的特性。

Part II. 核心技术

这部分参考文档包含了所有完全集成到Spring框架中的那些技术。

在这些中最重要的是Spring框架的控制反转(IoC)容器。对Spring框架IoC容器的彻底处理是紧随其后的Spring面向切面编程(AOP)技术的全面覆盖。Spring框架有它自己的AOP框架,这在概念上很容易理解,在Java企业级开发中成功了解决了AOP需求中80%的关键点。

Spring也提供了AspectJ的全面集成(目前是最丰富的-考虑到功能-并且确定在Java企业中是最成熟的AOP实现)。
  • Chapter 3, The IoC container
  • Chapter 4, Resources
  • Chapter 5, Validation, Data Binding, and Type Conversion
  • Chapter 6, Spring Expression Language (SpEL)
  • Chapter 7, Aspect Oriented Programming with Spring
  • Chapter 8, Spring AOP APIs
如果有收获,可以请我喝杯咖啡!