集成和 Apex 实用程序

Apex 允许您使用标注与外部 SOAP 和 REST Web 服务集成。 您可以使用用于 JSON、XML、数据安全和编码的实用程序。通用实用程序 对于带有文本字符串的正则表达式,也提供了。

  • 使用 Apex 调用标注
  • JSON 支持 Apex 中的 JavaScript 对象表示法 (JSON) 支持
    将 Apex 对象序列化为 JSON 格式,并对序列化的 JSON 内容进行反序列化。
  • XML Support
    Apex 提供实用程序类,这些类支持使用流和 DOM 创建和解析 XML 内容。
  • 保护数据
    可以使用类提供的方法保护数据。Crypto
  • 对数据
    进行编码 可以使用类提供的方法对 URL 进行编码和解码,并将字符串转换为十六进制格式。EncodingUtil
  • 使用模式和匹配器 Apex 提供了模式和匹配器
    ,使您能够使用正则表达式搜索文本。

使用 Apex 调用标注

借助 Apex 标注,您可以通过以下方式将 Apex 与外部服务紧密集成 调用外部 Web 服务或从 Apex 代码发送 HTTP 请求,然后接收 响应。Apex 提供与利用 SOAP 和 WSDL 或 HTTP 的 Web 服务的集成 服务(RESTful 服务)。

注意

在任何 Apex 标注可以调用外部站点之前,该站点 必须在“远程站点设置”页中注册,否则标注将失败。Salesforce的 防止调用未经授权的网络地址。

如果标注指定了命名 凭据作为终结点,则无需配置远程站点设置。一个命名的 credential 指定标注端点的 URL 及其所需的身份验证 一个定义中的参数。若要设置命名凭据,请参阅“定义命名凭据” 在 Salesforce 帮助中。

若要详细了解标注类型,请参阅:

  • SOAP 服务:从 WSDL 文档定义类
  • 调用 HTTP 标注
  • 异步 长时间运行的请求的标注

提示

标注使 Apex 能够调用外部 Web 或 HTTP 服务。Apex Web 服务允许外部应用程序调用 Apex 方法 通过 Web 服务。

  1. 添加远程站点设置
  2. 命名凭据作为标注端点 命名凭据在一个定义中指定标注端点
    的 URL 及其所需的身份验证参数。Salesforce 管理将指定命名凭据作为标注端点的 Apex 标注的所有身份验证,以便您的代码不必这样做。对于命名凭据中定义的站点,还可以跳过远程站点设置,否则,外部站点的标注将需要这些设置。
  3. SOAP 服务:从 WSDL 文档定义类
  4. 调用 HTTP 标注
  5. 使用证书
  6. 标注限制和局限性
  7. 使用延续创建长时间运行的标注
    使用异步标注从 Visualforce 页面或 Lightning 组件向外部 Web 服务发出长时间运行的请求,并在回调方法中处理响应。

添加远程站点设置

在任何 Apex 标注可以调用外部站点之前,该站点 必须在“远程站点设置”页中注册,否则标注将失败。Salesforce的 防止调用未经授权的网络地址。

注意

如果标注指定命名凭据作为终结点,则无需配置 远程站点设置。命名凭据指定标注端点的 URL 及其 一个定义中所需的身份验证参数。若要设置命名凭据,请参阅“定义” 命名凭据”。

若要添加远程站点设置,请执行以下操作:

  1. 在“设置”中,输入“快速” “查找”框,然后选择“远程站点设置”。Remote Site Settings
  2. 单击“新建远程站点”。
  3. 输入“远程站点名称”的描述性术语。
  4. 输入远程站点的 URL。
  5. (可选)输入站点的描述。
  6. 点击保存

提示

为了获得最佳性能,请验证远程 HTTPS 加密站点是否具有 OCSP (联机证书状态协议)装订已打开。

作为标注端点的命名凭据

命名凭据指定标注端点的 URL 及其所需的 URL 一个定义中的身份验证参数。Salesforce 管理 Apex 的所有身份验证 将命名凭据指定为标注终结点的标注,以便代码没有 自。您还可以跳过远程站点设置,否则,外部标注将需要这些设置 sites,用于命名凭据中定义的站点。

命名凭据还包括可用于路由的 OutboundNetworkConnection 字段 通过专用连接进行标注。通过将端点 URL 和身份验证与 标注定义、命名凭据使标注更易于维护。例如,如果 终结点 URL 更改时,仅更新命名凭据。引用命名的所有标注 凭据只是继续工作。

如果您有多个组织,则可以创建具有相同名称但具有 每个组织中不同的端点 URL。然后,您可以在所有 orgs – 一个标注定义,引用这些命名凭据的共享名称。 例如,每个组织中的命名凭据可以具有不同的终结点 URL 来容纳 开发和生产环境的差异。如果 Apex 标注指定了共享的 这些命名凭据的名称,定义标注的 Apex 类可以打包,并且 部署在所有这些组织上,而无需以编程方式检查环境。

若要从标注定义中引用命名凭据,请使用命名凭据 URL。一个 命名凭证 URL 包含方案、名称 的命名凭据和可选路径。例如:。callout:callout:My_Named_Credential/some_path

您可以将查询字符串追加到命名凭据 URL。使用问号 (?) 作为命名凭据 URL 之间的分隔符 和查询字符串。例如:。callout:My_Named_Credential/some_path?format=json

在以下 Apex 代码中,命名凭据和附加路径指定标注的 端点。

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/some_path');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());

引用的命名凭据指定终结点 URL 和外部凭据 指定身份验证设置。

命名凭据详细信息页

无论您使用哪种身份验证,Apex 代码都保持不变。这 外部凭据中的身份验证设置不同,外部凭据引用身份验证 组织中定义的提供程序。

具有 OAuth 选项的命名凭据身份验证设置

在 相比之下,让我们看看没有命名凭据的 Apex 代码是什么样子的。请注意, 代码处理身份验证变得更加复杂,即使我们坚持使用基本密码 认证。编码 OAuth 甚至更加复杂,是命名 凭据。

HttpRequest req = new HttpRequest();
req.setEndpoint('https://my_endpoint.example.com/some_path');
req.setMethod('GET');

// Because we didn't set the endpoint as a named credential, 
// our code has to specify:
// - The required username and password to access the endpoint
// - The header and header information
 
String username = 'myname';
String password = 'mypwd';
  
Blob headerValue = Blob.valueOf(username + ':' + password);
String authorizationHeader = 'BASIC ' +
EncodingUtil.base64Encode(headerValue);
req.setHeader('Authorization', authorizationHeader);
   
// Create a new http object to send the request object
// A response object is generated as a result of the request  
  
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());
  1. 使用命名凭据
    的 Apex 标注的自定义标头和正文 Salesforce 为命名凭据定义的端点的每个标注生成一个标准授权标头,但您可以禁用此选项。您的 Apex 代码还可以使用合并字段来构造每个标注的 HTTP 标头和正文。
  2. 使用命名凭证
    的 Apex 标注的合并字段 要构造 HTTP 标头并向指定为命名凭证的端点发出标注正文,请在 Apex 代码中使用这些合并字段。

使用 named 的 apex 标注的自定义标头和正文 凭据

Salesforce 为每个标注生成一个标准授权标头,以 named-credential-defined 端点,但您可以禁用此选项。您的 Apex 代码也可以使用 merge 字段来构造每个标注的 HTTP 标头和正文。

这种灵活性使您能够在特殊情况下使用命名凭据。例如,一些 远程终结点需要在请求标头中使用安全令牌或加密凭据。一些偏远的 终结点需要 XML 或 JSON 消息正文中的用户名和密码。自定义标注 标题和正文。

Salesforce 管理员必须设置命名凭据,以允许 Apex 代码构造标头 或在 HTTP 标头或正文中使用合并字段。下表描述了这些标注 命名凭据的选项。

描述
生成授权 页眉默认情况下,Salesforce 会生成一个授权标头,并将其应用于每个标注 引用命名凭据。仅当出现以下情况之一时才取消选择此选项 声明适用。远程终结点不支持授权标头。授权标头通过其他方式提供。例如,在 Apex 标注中, 开发人员可以让代码为每个 标注。如果从外部引用命名凭据,则此选项是必需的 数据源。
允许在 HTTP 中合并字段 页眉允许合并 HTTP 正文中的字段在每个 Apex 标注中,代码指定 HTTP 标头和请求正文的 构建。例如,Apex 代码可以在授权中设置 cookie 的值 页眉。这些选项使 Apex 代码能够使用合并字段来填充 HTTP 标头 并在进行标注时请求带有组织数据的正文。这些选项不是 如果从外部数据源引用命名凭据,则可用。

合并使用命名凭证的 Apex 标注的字段

构造 HTTP 标头和指向端点的标注正文 指定为命名凭据,请在 Apex 代码中使用这些合并字段。

合并字段描述
{!$Credential.Username}{!$Credential.Password}正在运行的用户的用户名和密码。仅当命名凭据使用 密码 认证。// non-standard authentication req.setHeader('X-Username', '{!$Credential.Username}'); req.setHeader('X-Password', '{!$Credential.Password}');
{!$Credential.OAuthToken}正在运行的用户的 OAuth 令牌。仅当命名凭据使用 OAuth 时才可用 认证。req.setHeader('Authorization', '{!$Credential.OAuthToken}');
{!$Credential.AuthorizationMethod}有效值取决于 命名凭据的身份验证协议。Basic—密码验证Bearer– OAuth 2.0null– 无身份验证
{!$Credential.AuthorizationHeaderValue}有效值取决于命名凭据的身份验证协议。Base-64 encoded username and password—密码验证OAuth token– OAuth 2.0null– 无身份验证
{!$Credential.OAuthConsumerKey}使用者密钥。仅当命名凭据使用 OAuth 身份验证时才可用。

注意

  • 在标注的 HTTP 请求正文中使用这些合并字段时,可以应用公式函数来转义特殊字符。 不支持其他公式函数,并且不能用于 HTTP 标头中的合并字段。以下示例对特殊字符进行转义 在 凭据。HTMLENCODEHTMLENCODEreq.setBody('Username:{!HTMLENCODE($Credential.Username)}') req.setBody('Password:{!HTMLENCODE($Credential.Password)}')
  • 在 SOAP API 调用中使用这些合并字段时,OAuth 访问令牌不会 刷新。

SOAP 服务:从 WSDL 文档定义类

可以从 WSDL 文档自动生成类,该文档具有 存储在本地硬盘驱动器或网络上。通过使用 WSDL 创建类 文档允许开发人员在其 Apex 代码中对外部 Web 服务进行标注。

注意

如果可能,请使用出站消息传递来处理集成解决方案。使用标注可以 仅在必要时才使用第三方 Web 服务。

要从 WSDL 生成 Apex 类,请执行以下操作:

  1. 在应用程序中,从“设置”中输入“快速查找”框,然后选择“Apex 类”。Apex Classes
  2. 单击“从 WSDL 生成”。
  3. 单击 Browse 导航到本地硬盘上的 WSDL 文档 驱动器或网络,或键入完整路径。此 WSDL 文档是 Apex 的基础 您正在创建的类。注意您指定的 WSDL 文档可能包含一个 SOAP 端点位置,该位置 引用出站端口。出于安全原因,Salesforce 限制了出站端口 您可以指定以下选项之一:
    • 80:此端口仅接受 HTTP 连接。
    • 443:此端口仅接受 HTTPS 连接。
    • 1024–66535(含):这些端口接受 HTTP 或 HTTPS 连接。
  4. 单击解析 WSDL 以验证 WSDL 文档内容。这 应用程序为 WSDL 文档中的每个名称空间生成一个缺省类名,并且 报告任何错误。如果 WSDL 包含以下模式类型或构造,则解析将失败 不受 Apex 类支持,或者如果生成的类超过 100 万个字符 对 Apex 类的限制。例如,无法解析 Salesforce SOAP API WSDL。
  5. 根据需要修改类名。虽然您可以保存多个 通过对每个命名空间 Apex 使用相同的类名,将 WSDL 命名空间转换为单个类 类的总长度不能超过 100 万个字符。
  6. 单击 Generate Apex。向导的最后一页显示哪个 已成功生成类,以及来自其他类的任何错误。该页面还 提供用于查看成功生成的代码的链接。

成功生成的 Apex 类包括用于调用 由 WSDL 文档表示的第三方 Web 服务。这些类允许您调用 来自 Apex 的外部 Web 服务。对于每个生成的类,将创建第二个类,并使用 同名且前缀为 .第一类是 用于同步标注。第二类用于异步标注。更多信息 关于异步标注,请参阅使用延续创建长时间运行的标注。Async

关于生成的 Apex,请注意以下几点:

  • 如果 WSDL 文档包含 Apex 保留字,则在生成 Apex 类时会追加该字。例如,在 WSDL 文档中转换为在生成的 Apex 类中。请参阅保留关键字。详情请见 处理 WSDL 中 Apex 变量不支持的元素名称中的字符 名称,请参阅使用 WSDL 的注意事项。_xlimitlimit_x
  • 如果 WSDL 中的操作具有包含多个元素的输出消息,那么 生成的 Apex 将元素包装在内部类中。表示 WSDL 操作返回内部类,而不是单个元素。
  • 由于 Apex 类中不允许使用句点 () names,则用于生成 Apex 类的 WSDL 名称中的任何句点都将替换为下划线 () 在生成的 Apex 代码中。._

从 WSDL 生成类后,可以调用外部服务 由 WSDL 引用。

注意

在使用本主题其余部分中的示例之前,必须从生成的 WSDL2Apex 代码中复制 Apex 类,并将其添加到 组织。docSampleClass

调用外部服务

在使用外部服务的 WSDL 文档后调用外部服务,以执行以下操作 生成一个 Apex 类,在 Apex 代码中创建存根的实例,然后调用 方法。例如,要从 Apex 调用 StrikeIron IP 地址查找服务,您可以 编写类似于以下内容的代码:

  // Create the stub
  strikeironIplookup.DNSSoap dns = new strikeironIplookup.DNSSoap();

  // Set up the license header
  dns.LicenseInfo = new strikeiron.LicenseInfo();
  dns.LicenseInfo.RegisteredUser = new strikeiron.RegisteredUser();
  dns.LicenseInfo.RegisteredUser.UserID = 'you@company.com';
  dns.LicenseInfo.RegisteredUser.Password = 'your-password';

  // Make the Web service call
  strikeironIplookup.DNSInfo info = dns.DNSLookup('www.myname.com');

HTTP 标头支持

可以在 Web 服务标注上设置 HTTP 标头。例如,您可以使用此功能 在授权标头中设置 Cookie 的值。要设置 HTTP 标头,请将 和 添加到存根。inputHttpHeaders_xoutputHttpHeaders_x

注意

在 API 版本 16.0 及更早版本中,HTTP 响应 标注始终使用 UTF-8 进行解码,而不考虑 Content-Type 标头。在 API 中 版本 17.0 及更高版本中,HTTP 响应使用 Content-Type 标头。

以下示例使用生成的 WSDL2Apex 代码中的示例 WSDL 文件:

在 Web 服务标注上发送 HTTP 标头

docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.inputHttpHeaders_x = new Map<String, String>();

//Setting a basic authentication header

stub.inputHttpHeaders_x.put('Authorization', 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==');

//Setting a cookie header
stub.inputHttpHeaders_x.put('Cookie', 'name=value');

//Setting a custom HTTP header
stub.inputHttpHeaders_x.put('myHeader', 'myValue');

String input = 'This is the input string';
String output = stub.EchoString(input);

如果指定了 for 的值,则 覆盖标准标头集。inputHttpHeaders_x

从 Web 服务标注响应访问 HTTP 响应标头

docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.outputHttpHeaders_x = new Map<String, String>();
String input = 'This is the input string';
String output = stub.EchoString(input);

//Getting cookie header
String cookie = stub.outputHttpHeaders_x.get('Set-Cookie');

//Getting custom header
String myHeader = stub.outputHttpHeaders_x.get('My-Header');

的值为 null 违约。您必须在之前设置 您可以访问响应中标头的内容。outputHttpHeaders_xoutputHttpHeaders_x

支持的 WSDL 功能

Apex 仅支持文档文本包装的 WSDL 样式以及以下基元和 内置数据类型:

架构类型顶点类型
xsd:anyURI字符串
xsd:boolean布尔
xsd:date日期
xsd:dateTime日期时间
xsd:double
xsd:float
xsd:int整数
xsd:integer整数
xsd:language字符串
xsd:long
xsd:Name字符串
xsd:NCName字符串
xsd:nonNegativeInteger整数
xsd:NMTOKEN字符串
xsd:NMTOKENS字符串
xsd:normalizedString字符串
xsd:NOTATION字符串
xsd:positiveInteger整数
xsd:QName字符串
xsd:short整数
xsd:string字符串
xsd:time日期时间
xsd:token字符串
xsd:unsignedInt整数
xsd:unsignedLong
xsd:unsignedShort整数

注意

Salesforce 数据类型 anyType 在用于 生成使用 API 版本 15.0 及更高版本保存的 Apex 代码。对于使用 API 保存的代码 版本 14.0 及更早版本,anyType 映射到 String。

Apex 还支持以下架构构造:

  • xsd:all,在使用 API 版本保存的 Apex 代码中 15.0 及更高版本
  • xsd:annotation,在使用 API 保存的 Apex 代码中 版本 15.0 及更高版本
  • xsd:attribute,在使用 API 保存的 Apex 代码中 版本 15.0 及更高版本
  • xsd:choice,在使用 API 保存的 Apex 代码中 版本 15.0 及更高版本
  • xsd:element.在使用 API 保存的 Apex 代码中 版本 15.0 及更高版本,该属性也是 受以下限制支持:ref
    • 您不能在不同的 命名空间。ref
    • 全局元素不能使用 。ref
    • 如果一个元素包含 ,则它不能也 contain 或 .refnametype
  • xsd:sequence

仅当用作呼入时才支持以下数据类型,即 当外部 Web 服务调用 Apex Web 服务方法时。这些数据类型不是 支持作为标注,即当 Apex Web 服务方法调用 外部 Web 服务。

  • 斑点
  • 十进制
  • 枚举

Apex 不支持任何其他 WSDL 构造、类型或服务,包括:

  • RPC/编码服务
  • 具有多个 、 多个 的 WSDL 文件 服务或多个绑定portTypes
  • 导入外部模式的 WSDL 文件。例如,以下 WSDL 片段 导入外部架构,该架构不是 支持:<wsdl:types> <xsd:schema elementFormDefault="qualified" targetNamespace="http://s3.amazonaws.com/doc/2006-03-01/"> <xsd:include schemaLocation="AmazonS3.xsd"/> </xsd:schema> </wsdl:types>但是,支持在同一架构中导入。在以下内容中 例如,外部 WSDL 被粘贴到您所在的 WSDL 中 转换:<wsdl:types> <xsd:schema xmlns:tns="http://s3.amazonaws.com/doc/2006-03-01/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://s3.amazonaws.com/doc/2006-03-01/"> <xsd:element name="CreateBucket"> <xsd:complexType> <xsd:sequence> [...] </xsd:schema> </wsdl:types>
  • 上表中未记录的任何架构类型
  • 超过大小限制的 WSDL,包括 Salesforce WSDL
  • 不使用文档文本换行样式的 WSDL。以下 WSDL 代码段不使用文档文字换行样式,并导致“无法 查找 complexType“错误时 洋。<wsdl:types> <xsd:schema targetNamespace="http://test.org/AccountPollInterface/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="SFDCPollAccountsResponse" type="tns:SFDCPollResponse"/> <xsd:simpleType name="SFDCPollResponse"> <xsd:restriction base="xsd:string" /> </xsd:simpleType> </xsd:schema> </wsdl:types>此修改后的版本将元素包装为 包含元素序列。这遵循文档文本样式,并且是 支持。simpleTypecomplexType<wsdl:types> <xsd:schema targetNamespace="http://test.org/AccountPollInterface/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="SFDCPollAccountsResponse" type="tns:SFDCPollResponse" /> <xsd:complexType name="SFDCPollResponse"> <xsd:sequence> <xsd:element name="SFDCOutput" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:schema> </wsdl:types>
  1. 生成的 WSDL2Apex 代码
    您可以使用 WSDL2Apex 工具从 WSDL 文档生成 Apex 类。WSDL2Apex 工具是开源的,可在 GitHub 上找到。
  2. 测试 Web 服务标注
    生成的代码将另存为 Apex 类,其中包含可用于调用 Web 服务的方法。若要部署或打包此 Apex 类和其他随附代码,75% 的代码必须具有测试覆盖率,包括生成的类中的方法。默认情况下,测试方法不支持 Web 服务标注,并且执行 Web 服务标注的测试会失败。为了防止测试失败并增加代码覆盖率,Apex 提供了内置接口和方法。在测试方法中使用和接收虚假响应。WebServiceMockTest.setMockWebServiceMockTest.setMock
  3. 执行 DML 操作和模拟标注
  4. 使用 WSDL 的注意事项

生成的 WSDL2Apex 代码

您可以使用 WSDL2Apex 工具从 WSDL 文档生成 Apex 类。这 WSDL2Apex 工具是开源的,可在 GitHub 上找到。

您可以在 GitHub 上的 WSDL2Apex 存储库中找到并参与 WSDL2Apex 源代码。

下面的示例演示如何从 WSDL 文档创建 Apex 类。顶点 类是在导入 WSDL 时自动生成的。下面的代码显示了一个示例 WSDL 公文。

<wsdl:definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://doc.sample.com/docSample"
targetNamespace="http://doc.sample.com/docSample"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

<!-- Above, the schema targetNamespace maps to the Apex class name. -->


<!-- Below, the type definitions for the parameters are listed. 
     Each complexType and simpleType parameteris mapped to an Apex class inside the parent class for the WSDL.  Then, each element in the complexType is mapped to a public field inside the class. -->

<wsdl:types>
<s:schema elementFormDefault="qualified"
targetNamespace="http://doc.sample.com/docSample">
<s:element name="EchoString">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="input" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="EchoStringResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="EchoStringResult"
type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</wsdl:types>

<!--The stub below defines operations. -->

<wsdl:message name="EchoStringSoapIn">
<wsdl:part name="parameters" element="tns:EchoString" />
</wsdl:message>
<wsdl:message name="EchoStringSoapOut">
<wsdl:part name="parameters" element="tns:EchoStringResponse" />
</wsdl:message>
<wsdl:portType name="DocSamplePortType">
<wsdl:operation name="EchoString">
<wsdl:input message="tns:EchoStringSoapIn" />
<wsdl:output message="tns:EchoStringSoapOut" />
</wsdl:operation>
</wsdl:portType>

<!--The code below defines how the types map to SOAP. -->

<wsdl:binding name="DocSampleBinding" type="tns:DocSamplePortType">
<wsdl:operation name="EchoString">
<soap:operation soapAction="urn:dotnet.callouttest.soap.sforce.com/EchoString"
style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<!-- Finally, the code below defines the endpoint, which maps to the endpoint in the class -->

<wsdl:service name="DocSample">
<wsdl:port name="DocSamplePort" binding="tns:DocSampleBinding">
<soap:address location="http://YourServer/YourService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

在此 WSDL 文档中,自动生成以下 Apex 类。类名是您在以下情况下指定的名称 导入 WSDL.docSample

//Generated by wsdl2apex

public class docSample {
    public class EchoStringResponse_element {
        public String EchoStringResult;
        private String[] EchoStringResult_type_info = new String[]{
                            'EchoStringResult',
                            'http://doc.sample.com/docSample',
                             null,'0','1','false'};
        private String[] apex_schema_type_info = new String[]{
                             'http://doc.sample.com/docSample',
                             'true','false'};
        private String[] field_order_type_info = new String[]{
                             'EchoStringResult'};
    }
    public class EchoString_element {
        public String input;
        private String[] input_type_info = new String[]{
                              'input',
                              'http://doc.sample.com/docSample',
                               null,'0','1','false'};
        private String[] apex_schema_type_info = new String[]{
                               'http://doc.sample.com/docSample',
                               'true','false'};
        private String[] field_order_type_info = new String[]{'input'};
    }
    public class DocSamplePort {
        public String endpoint_x = 'http://YourServer/YourService';
        public Map<String,String> inputHttpHeaders_x;
        public Map<String,String> outputHttpHeaders_x;
        public String clientCertName_x;
        public String clientCert_x;
        public String clientCertPasswd_x;
        public Integer timeout_x;
        private String[] ns_map_type_info = new String[]{
                          'http://doc.sample.com/docSample', 'docSample'};
        public String EchoString(String input) {
            docSample.EchoString_element request_x = new 
                                           docSample.EchoString_element();
            request_x.input = input;
            docSample.EchoStringResponse_element response_x;
            Map<String, docSample.EchoStringResponse_element> response_map_x = 
                       new Map<String, docSample.EchoStringResponse_element>();
            response_map_x.put('response_x', response_x);
            WebServiceCallout.invoke(
              this,
              request_x,
              response_map_x,
              new String[]{endpoint_x,
              'urn:dotnet.callouttest.soap.sforce.com/EchoString',
              'http://doc.sample.com/docSample',
              'EchoString',
              'http://doc.sample.com/docSample',
              'EchoStringResponse',
              'docSample.EchoStringResponse_element'}
            );
            response_x = response_map_x.get('response_x');
            return response_x.EchoStringResult;
        }
    }
}

请注意原始 WSDL 文档中的以下映射:

  • WSDL 目标命名空间映射到 Apex 类名。
  • 每个复杂类型都成为一个类。类型中的每个元素都是 类。
  • WSDL 端口名称映射到存根类。
  • WSDL 中的每个操作都映射到一个公共方法。

您可以使用自动生成的类来 调用外部 Web 服务。下面的代码在外部服务器上调用该方法。docSampleechoString

docSample.DocSamplePort stub = new docSample.DocSamplePort();
String input = 'This is the input string';
String output = stub.EchoString(input);

测试 Web 服务标注

生成的代码将另存为 Apex 类,其中包含您可以调用的方法 调用 Web 服务。若要部署或打包此 Apex 类和其他随附代码, 75% 的代码必须具有测试覆盖率,包括生成的类中的方法。由 默认情况下,测试方法不支持 Web 服务标注和执行 Web 的测试 服务标注失败。为了防止测试失败并增加代码覆盖率,Apex 提供内置接口和 方法。在测试方法中使用和接收虚假响应。

WebServiceMockTest.setMockWebServiceMockTest.setMock

指定用于测试 Web 服务标注的模拟响应

从 WSDL 创建 Apex 类时,自动生成的类中的方法 调用 ,其中 执行对外部服务的标注。测试这些方法时,您可以 指示 Apex 运行时在调用时生成虚假响应。为此, 实现接口和 指定要发送的 Apex 运行时的虚假响应。以下是更多步骤 细节。WebServiceCallout.invokeWebServiceCallout.invokeWebServiceMock

首先,实现接口 并在方法中指定 false 响应。WebServiceMockdoInvoke

global class YourWebServiceMockImpl implements WebServiceMock {
   global void doInvoke(
           Object stub,
           Object request,
           Map<String, Object> response,
           String endpoint,
           String soapAction,
           String requestName,
           String responseNS,
           String responseName,
           String responseType) {

        // Create response element from the autogenerated class.
        // Populate response element.
        // Add response element to the response parameter, as follows:
        response.put('response_x', responseElement); 
   }
}

注意

  • 实现接口的类可以是全局类,也可以是公共类。WebServiceMock
  • 您可以使用@isTest对此类进行批注,因为它仅在测试上下文中使用。 这样,您就可以将其从组织的代码大小限制 6 中排除 MB的。

现在,您已经指定了假响应的值,请指示 Apex 运行时通过调用测试方法发送此虚假响应。对于第一个参数,传递 ,对于第二个参数 参数,则传递 的接口实现的新实例,如下所示:Test.setMockWebServiceMock.classWebServiceMock

Test.setMock(WebServiceMock.class, new YourWebServiceMockImpl());

在此之后,如果在测试上下文中调用 Web 服务标注,则标注为 不是制造的。您会收到方法实现中指定的模拟响应。doInvoke

注意

如果执行标注的代码位于 一个托管包,从 测试方法。Test.setMock

此示例演示如何测试 Web 服务标注。列出了接口的实现 第一。此示例实现该方法,该方法返回您指定的响应。在本例中,响应元素 的自动生成的类被创建并分配一个值。接下来,响应映射 参数填充了此虚假响应。此示例基于 WSDL 在生成的 WSDL2Apex 代码中列出。导入此 WSDL 并生成 保存之前调用的类 这个班级。WebServiceMockdoInvokedocSample

@isTest
global class WebServiceMockImpl implements WebServiceMock {
   global void doInvoke(
           Object stub,
           Object request,
           Map<String, Object> response,
           String endpoint,
           String soapAction,
           String requestName,
           String responseNS,
           String responseName,
           String responseType) {
       docSample.EchoStringResponse_element respElement = 
           new docSample.EchoStringResponse_element();
       respElement.EchoStringResult = 'Mock response';
       response.put('response_x', respElement); 
   }
}

此方法生成 Web 服务标注。

public class WebSvcCallout {
    public static String callEchoString(String input) {
        docSample.DocSamplePort sample = new docSample.DocSamplePort();
        sample.endpoint_x = 'https://example.com/example/test';
        
        // This invokes the EchoString method in the generated class
        String echo = sample.EchoString(input);
        
        return echo;
    }   
}

此测试类包含设置模拟标注模式的测试方法。它调用 上一个方法 类,并验证是否收到模拟响应。callEchoString

@isTest
private class WebSvcCalloutTest {
    @isTest static void testEchoString() {              
        // This causes a fake response to be generated
        Test.setMock(WebServiceMock.class, new WebServiceMockImpl());
        
        // Call the method that invokes a callout
        String output = WebSvcCallout.callEchoString('Hello World!');
        
        // Verify that a fake result is returned
        System.assertEquals('Mock response', output); 
    }
}

执行 DML 操作和模拟标注

默认情况下,标注不是 允许在同一事务中执行 DML 操作后,因为 DML 操作 导致待处理的未提交工作,从而阻止标注执行。 有时,您可能希望在测试方法中插入测试数据 在进行标注之前使用 DML。要启用此功能,请将部分括起来 在 AND 语句中执行标注的代码。语句必须出现 在声明之前。 此外,对 DML 操作的调用不得是 / 块的一部分。Test.startTestTest.stopTestTest.startTestTest.setMockTest.startTestTest.stopTest

DML 操作 在模拟标注被允许且不需要之后发生 测试方法的任何变化。

在模拟标注之前执行 DML

此示例基于前面的示例。该示例显示 如何使用 AND 语句允许 在模拟标注之前在测试方法中执行的 DML 操作。 测试方法()首先插入一个测试帐户,调用,使用设置模拟标注模式,调用一个方法 执行标注,验证模拟响应值,最后 调用。Test.startTestTest.stopTesttestEchoStringTest.startTestTest.setMockTest.stopTest

@isTest
private class WebSvcCalloutTest {
    @isTest static void testEchoString() {              
        // Perform some DML to insert test data
        Account testAcct = new Account('Test Account');
        insert testAcct;

        // Call Test.startTest before performing callout
        // but after setting test data.
        Test.startTest();

        // Set mock callout class 
        Test.setMock(WebServiceMock.class, new WebServiceMockImpl());
        
        // Call the method that invokes a callout
        String output = WebSvcCallout.callEchoString('Hello World!');
        
        // Verify that a fake result is returned
        System.assertEquals('Mock response', output);

        Test.stopTest();
    }
}

异步顶点和模拟标注

与 DML 类似,异步 Apex 操作会导致挂起的未提交工作,从而阻止 稍后在同一事务中执行的标注。示例 异步 Apex 操作是对未来方法、批处理 Apex 或计划的调用 顶点。这些异步调用通常包含在测试方法的 and 语句中,以便 它们在 之后执行。在这个 情况下,可以在异步调用后执行模拟标注,并且不会进行任何更改 必要。但是,如果异步调用没有包含在 and 语句中,你将得到一个 异常,因为未提交的工作处于待处理状态。要防止此异常,请执行以下任一操作 其中:

Test.startTestTest.stopTestTest.stopTestTest.startTestTest.stopTest

  • 将异步调用包含在 and 语句中。Test.startTestTest.stopTestTest.startTest(); MyClass.asyncCall(); Test.stopTest(); Test.setMock(..); // Takes two arguments MyClass.mockCallout();
  • 遵循与 DML 调用相同的规则:将代码部分括起来 在 AND 语句中执行标注。语句必须出现在语句之前。此外, 异步调用不能是 / 块的一部分。Test.startTestTest.stopTestTest.startTestTest.setMockTest.startTestTest.stopTestMyClass.asyncCall(); Test.startTest(); Test.setMock(..); // Takes two arguments MyClass.mockCallout(); Test.stopTest();

模拟标注之后发生的异步调用是 允许并且不需要对测试方法进行任何更改。

使用 WSDL 的注意事项

从 WSDL 生成 Apex 类时,请注意以下事项。

映射标头

WSDL 文档中定义的标头将成为生成的类中存根上的公共字段。 这类似于 AJAX 工具包和 .NET 的工作方式。

了解运行时事件

当 Apex 代码对外部服务进行标注时,将执行以下检查。

  • 有关发出 HTTP 请求或 Web 服务调用时的超时限制的信息,请参见标注限制和局限性。
  • 不允许在 Apex 类中使用循环引用。
  • 不允许与 Salesforce 域建立多个环回连接。
  • 若要允许访问终结点,请从安装程序中输入“快速查找”框,然后选择“远程站点设置”来注册该终结点。Remote Site Settings
  • 为了防止数据库连接被阻止,没有事务 可以打开。

了解变量名称中不支持的字符

WSDL 文件可以包含 Apex 变量名称中不允许的元素名称。这 从 WSDL 生成 Apex 变量名称时,以下规则适用 文件:

  • 如果元素名称的第一个字符不是字母顺序,则会在生成的 Apex 前面附加一个字符 变量名称。x
  • 如果 Apex 变量名称中不允许使用元素名称的最后一个字符,则会在 生成的 Apex 变量名称。x
  • 如果元素名称包含 Apex 变量名称中不允许的字符,则 字符替换为下划线 () 字符。_
  • 如果元素名称在一行中包含两个 Apex 变量中不允许的字符 name,第一个字符替换为下划线 () 字符,第二个字符 替换为字符。这样可以避免生成具有两个连续变量名称的变量名称 下划线,这在 Apex 中是不允许的。_x
  • 假设您有一个操作,该操作采用两个参数和 . 生成的 Apex 有两个变量,均命名为 .该类不编译。 手动编辑 Apex 并更改其中一个变量名称。a_a_xa_x

调试从 WSDL 文件生成的类

Salesforce 使用 SOAP API、.NET 和 Axis 测试代码。如果您使用其他工具,您可能会遇到 问题。

您可以使用调试标头在请求和响应 SOAP 消息中返回 XML 以帮助 您诊断问题。有关更多信息,请参阅使用 SOAP API 部署 Apex。

调用 HTTP 标注

Apex 提供了几个内置类来处理 HTTP 服务并创建 HTTP 请求,例如 GET、POST、PUT 和 DELETE。

您可以使用这些 HTTP 类集成到基于 REST 的服务。它们还允许您 集成到基于 SOAP 的 Web 服务中,作为从 WSDL 生成 Apex 代码的替代选项。 通过使用 HTTP 类,而不是从 WSDL 开始,您可以承担更多的责任 处理请求和响应的 SOAP 消息的构造。

  1. HTTP 类
  2. 测试 HTTP 标注
    要部署或打包 Apex,75% 的代码必须具有测试覆盖率。默认情况下,测试方法不支持 HTTP 标注,因此执行标注的测试会失败。通过使用指示 Apex 在测试中生成模拟响应来启用 HTTP 标注测试。Test.setMock

HTTP 类

这些类公开 HTTP 请求和响应功能。

  • Http的 类。使用此类来启动 HTTP 请求和 响应。
  • HttpRequest 类:使用此类可以 以编程方式创建 HTTP 请求,如 GET、POST、PATCH、PUT 和 DELETE。
  • HttpResponse 类:使用此类可以 处理 返回的 HTTP 响应。HTTP

和类支持这些元素。

HttpRequestHttpResponse

  • Http请求
    • HTTP 请求类型,例如 GET、POST、PATCH、PUT、DELETE、TRACE、 连接、头部和选项
    • 请求标头(如果需要)
    • 读取和连接超时
    • 必要时重定向
    • 邮件正文的内容
  • HttpResponse
    • HTTP 状态代码
    • 响应标头(如果需要)
    • 响应正文的内容

此示例向外部服务器发出 HTTP GET 请求,该请求传递给参数中的方法。此示例还访问 返回的响应。getCalloutResponseContentsurl

public class HttpCalloutSample {

  // Pass in the endpoint to be used using the string url
  public String getCalloutResponseContents(String url) {

    // Instantiate a new Http object
    Http h = new Http();

     // Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
    HttpRequest req = new HttpRequest();
    req.setEndpoint(url);
    req.setMethod('GET');

    // Send the request, and return a response
    HttpResponse res = h.send(req);
    return res.getBody();
  }
}

前面的示例是同步运行的,这意味着没有进一步的处理 在外部 Web 服务返回响应之前发生。或者,您可以使用 @future注解,使 标注异步运行。

此示例向外部服务器发出 HTTP POST 请求,该请求传递给参数中的方法。替换为 要在标注中发送的 JSON 内容。getPostCalloutResponseContentsurlYour_JSON_Content

public class HttpPostCalloutSample {

  // Pass in the endpoint to be used using the string url
  public String getPostCalloutResponseContents(String url) {

    // Instantiate a new Http object
    Http h = new Http();

    // Instantiate a new HTTP request
    // Specify request properties such as the endpoint, the POST method, etc. 
    HttpRequest req = new HttpRequest();
    req.setEndpoint(url);
    req.setMethod('POST');
    req.setHeader('Content-Type', 'application/json');
    req.setBody('{Your_JSON_Content}');

    // Send the request, and return a response
    HttpResponse res = h.send(req);
    return res.getBody();
  }
}

若要从终结点或重定向终结点访问外部服务器,请添加远程站点 添加到授权远程站点的列表中。登录到 Salesforce,然后从“设置”的“快速”中 “查找”框,输入 ,然后选择“远程站点设置”。Remote Site Settings

使用 XML 类或 JSON 类来解析 XML 或 JSON 中的内容 由 HttpRequest 创建的请求的正文,或由 HttpResponse 访问的响应。

考虑

  • AJAX 代理处理重定向和身份验证质询 (401/407 responses) 自动。有关 AJAX 代理的详细信息,请参阅 AJAX 工具包 文档。
  • 您可以将终结点设置为命名凭据 URL。命名凭据 URL 包含方案,名称 命名凭据和可选路径。例如:。一个命名的 credential 指定标注端点的 URL 及其所需的 URL 一个定义中的身份验证参数。Salesforce 管理所有 对指定命名凭据作为标注的 Apex 标注进行身份验证 端点,这样您的代码就不必这样做了。您还可以跳过远程站点 设置,否则,向外部站点进行标注时需要这些设置,用于 命名凭据中定义的站点。请参阅作为标注的命名凭据 端点。callout:callout:My_Named_Credential/some_path
  • 在标注中设置请求正文时,请将方法设置为 。如果设置了请求正文和请求 方法为 ,则执行请求。POSTGETPOST
  • 如果您有来自 DML 的待处理未提交交易,则会阻止标注 操作、可排队作业(与 、 或将来的方法一起排队)。System.enqueueJobDatabase.executeBatch

测试 HTTP 标注

要部署或打包 Apex,75% 的代码必须具有测试覆盖率。默认情况下,test 方法不支持 HTTP 标注,因此执行标注的测试会失败。启用 HTTP 通过指示 Apex 在测试中生成模拟响应来进行标注测试,使用 .

Test.setMock通过以下方式之一指定模拟响应。

  • 通过实现 HttpCalloutMock 接口
  • 通过将静态资源与 StaticResourceCalloutMock 或 MultiStaticResourceCalloutMock 一起使用

若要在测试方法中的模拟标注之前启用运行 DML 操作,请参阅执行 DML 操作和模拟标注。

  • 通过实现 HttpCalloutMock 接口测试 HTTP 标注
  • 使用静态资源测试 HTTP 标注
  • 执行 DML 操作和模拟标注

通过实现 HttpCalloutMock 接口测试 HTTP 标注

为接口提供一个实现,以指定 Apex 运行时调用的方法中发送的响应 发送标注的响应。HttpCalloutMockrespond

global class YourHttpCalloutMockImpl implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
        // Create a fake response.
        // Set response values, and 
        // return response.
    }
}

注意

  • 实现接口的类可以是全局类,也可以是公共类。HttpCalloutMock
  • 您可以使用@isTest注释此类,因为它将仅在测试上下文中使用。 这样,就可以将其从组织的代码大小中排除 限制为 6 MB。

现在,您已经指定了假响应的值,请指示 Apex 运行时通过调用测试方法发送此虚假响应。对于第一个参数,传递 ,对于第二个参数 参数,则传递 的接口实现的新实例,如下所示:Test.setMockHttpCalloutMock.classHttpCalloutMock

Test.setMock(HttpCalloutMock.class, new YourHttpCalloutMockImpl());

在此之后,如果在测试上下文中调用 HTTP 标注,则标注不会 made 时,您会收到在方法实现中指定的模拟响应。respond

注意

如果执行标注的代码位于托管包中,则要模拟标注, 从 测试方法调用 具有相同命名空间的相同包。Test.setMock

这是一个完整的示例,演示如何测试 HTTP 标注。界面 实现 () 列在最前面。它后面是一个包含测试方法的类和另一个 包含测试调用的方法。测试方法通过在调用之前调用来设置模拟标注模式。然后,它会验证 返回的响应是已实现的方法发送的响应。单独保存每个类,并在 中运行测试。MockHttpResponseGeneratortestCalloutTest.setMockgetInfoFromExternalServicerespondCalloutClassTest

@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
    // Implement this interface method
    global HTTPResponse respond(HTTPRequest req) {
        // Optionally, only send a mock response for a specific endpoint
        // and method.
        System.assertEquals('https://example.com/example/test', req.getEndpoint());
        System.assertEquals('GET', req.getMethod());
        
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"example":"test"}');
        res.setStatusCode(200);
        return res;
    }
}
public class CalloutClass {
    public static HttpResponse getInfoFromExternalService() {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://example.com/example/test');
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        return res;
    }
}
@isTest
private class CalloutClassTest {
     @isTest static void testCallout() {
        // Set mock callout class 
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
        
        // Call method to test.
        // This causes a fake response to be sent
        // from the class that implements HttpCalloutMock. 
        HttpResponse res = CalloutClass.getInfoFromExternalService();
        
        // Verify response received contains fake values
        String contentType = res.getHeader('Content-Type');
        System.assert(contentType == 'application/json');
        String actualValue = res.getBody();
        String expectedValue = '{"example":"test"}';
        System.assertEquals(actualValue, expectedValue);
        System.assertEquals(200, res.getStatusCode());
    }
}

使用静态资源测试 HTTP 标注

您可以通过指定 您希望在静态资源中接收的响应,并使用 两个内置类之一 – StaticResourceCalloutMock 或 MultiStaticResourceCalloutMock

使用 StaticResourceCalloutMock 测试 HTTP 标注

Apex 提供了内置类,您可以通过在静态中指定响应正文来测试标注 资源。使用此类时,您不必提供自己的 接口的实现。相反,只需创建一个实例并设置静态资源以用于 响应正文以及其他响应属性,如状态代码和 内容类型。StaticResourceCalloutMockHttpCalloutMockStaticResourceCalloutMock首先,必须从要包含的文本文件创建静态资源 响应正文:

  1. 创建一个包含要返回的响应正文的文本文件。 响应正文可以是任意字符串,但它必须与 内容类型(如果指定)。例如,如果您的回复没有内容 指定类型,则该文件可以包含任意字符串。如果指定 application/json 的内容类型 对于响应,文件内容应为 JSON 字符串,例如 {“呵呵”:“骗了你”}。abc
  2. 为文本文件创建静态资源:
    1. 在“设置”中,输入“快速查找”框,然后选择“静态资源”。Static Resources
    2. 单击“新建”。
    3. 为静态资源命名。
    4. 选择要上传的文件。
    5. 点击保存

要了解有关静态资源的更多信息,请参阅 Salesforce 联机帮助。

接下来,创建一个实例并设置静态资源,然后 任何其他属性。StaticResourceCalloutMock

StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
mock.setStaticResource('myStaticResourceName');
mock.setStatusCode(200);
mock.setHeader('Content-Type', 'application/json');

在测试方法中,调用以设置模拟标注模式并将其作为第一个传递 参数,以及您为其创建的第二个变量名称 论点。Test.setMockHttpCalloutMock.classStaticResourceCalloutMock

Test.setMock(HttpCalloutMock.class, mock);

在此之后,如果您的测试方法执行标注,则不会进行标注,并且 Apex 运行时发送您在 实例中指定的模拟响应。StaticResourceCalloutMock

注意

如果执行标注的代码位于 一个托管包,从 测试方法。Test.setMock

这是一个完整的示例,包含测试方法 () 和 它所测试的方法 () 执行标注。在运行此示例之前,请创建 名为 based 的静态资源 在包含以下内容的文本文件上。单独保存每个类,并在 中运行测试。testCalloutWithStaticResourcesgetInfoFromExternalServicemockResponse{"hah":"fooled you"}CalloutStaticClassTest

public class CalloutStaticClass {
    public static HttpResponse getInfoFromExternalService(String endpoint) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        return res;
    }
}
@isTest
private class CalloutStaticClassTest {
    @isTest static void testCalloutWithStaticResources() {
        // Use StaticResourceCalloutMock built-in class to
        // specify fake response and include response body 
        // in a static resource.
        StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
        mock.setStaticResource('mockResponse');
        mock.setStatusCode(200);
        mock.setHeader('Content-Type', 'application/json');
        
        // Set the mock callout mode
        Test.setMock(HttpCalloutMock.class, mock);
        
        // Call the method that performs the callout
        HTTPResponse res = CalloutStaticClass.getInfoFromExternalService(
            'https://example.com/example/test');
        
        // Verify response received contains values returned by
        // the mock response.
        // This is the content of the static resource.
        System.assertEquals('{"hah":"fooled you"}', res.getBody());
        System.assertEquals(200,res.getStatusCode());
        System.assertEquals('application/json', res.getHeader('Content-Type'));   
    }
}

使用 MultiStaticResourceCalloutMock 测试 HTTP 标注

Apex 提供了内置类,您可以通过在静态中指定响应正文来测试标注 资源。此类类似于 除了它允许您指定 多个响应机构。使用此类时,您不必提供 您自己的接口实现。相反,只需创建一个实例并将 每个终结点要使用的静态资源。您还可以设置其他响应属性,例如 状态代码和内容类型。MultiStaticResourceCalloutMockStaticResourceCalloutMockHttpCalloutMockMultiStaticResourceCalloutMock

首先,必须从文本文件创建静态资源以包含响应正文。请参阅 使用 StaticResourceCalloutMock 测试 HTTP 标注中概述的过程。

接下来,创建 和 的实例 设置静态资源和任何其他属性。MultiStaticResourceCalloutMock

MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();
multimock.setStaticResource('https://example.com/example/test', 'mockResponse');
multimock.setStaticResource('https://example.com/example/sfdc', 'mockResponse2');
multimock.setStatusCode(200);
multimock.setHeader('Content-Type', 'application/json');

在测试方法中,调用以设置模拟标注模式并将其作为第一个传递 参数,以及您为 AS 创建的变量名称 第二个参数。Test.setMockHttpCalloutMock.classMultiStaticResourceCalloutMock

Test.setMock(HttpCalloutMock.class, multimock);

在此之后,如果测试方法对某个终结点执行 HTTP 标注 https://example.com/example/test 或 https://example.com/example/sfdc,则不会进行标注 并且 Apex 运行时会发送您在 的实例。MultiStaticResourceCalloutMock

这是一个完整的示例,其中包含测试方法 () 和它正在测试的执行标注的方法 ()。在运行此示例之前,请创建 名为 based 的静态资源 在一个文本文件上,其中包含内容,另一个基于 包含以下内容的文本文件。单独保存每个类,并在 中运行测试。testCalloutWithMultipleStaticResourcesgetInfoFromExternalServicemockResponse{"hah":"fooled you"}mockResponse2{"hah":"fooled you twice"}CalloutMultiStaticClassTest

public class CalloutMultiStaticClass {
    public static HttpResponse getInfoFromExternalService(String endpoint) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        return res;
    }
}
@isTest
private class CalloutMultiStaticClassTest {
    @isTest static void testCalloutWithMultipleStaticResources() {
        // Use MultiStaticResourceCalloutMock to
        // specify fake response for a certain endpoint and 
        // include response body in a static resource.    
        MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();
        multimock.setStaticResource(
            'https://example.com/example/test', 'mockResponse');
        multimock.setStaticResource(
            'https://example.com/example/sfdc', 'mockResponse2');
        multimock.setStatusCode(200);
        multimock.setHeader('Content-Type', 'application/json');
        
        // Set the mock callout mode
        Test.setMock(HttpCalloutMock.class, multimock);
        
        // Call the method for the first endpoint
        HTTPResponse res = CalloutMultiStaticClass.getInfoFromExternalService(
            'https://example.com/example/test');
        // Verify response received 
        System.assertEquals('{"hah":"fooled you"}', res.getBody());
        
        // Call the method for the second endpoint
        HTTPResponse res2 = CalloutMultiStaticClass.getInfoFromExternalService(
            'https://example.com/example/sfdc');
        // Verify response received 
        System.assertEquals('{"hah":"fooled you twice"}', res2.getBody());
    }
}

执行 DML 操作和模拟标注

默认情况下,标注不是 允许在同一事务中执行 DML 操作后,因为 DML 操作 导致待处理的未提交工作,从而阻止标注执行。 有时,您可能希望在测试方法中插入测试数据 在进行标注之前使用 DML。要启用此功能,请将部分括起来 在 AND 语句中执行标注的代码。语句必须出现 在声明之前。 此外,对 DML 操作的调用不得是 / 块的一部分。Test.startTestTest.stopTestTest.startTestTest.setMockTest.startTestTest.stopTest

DML 操作 在模拟标注被允许且不需要之后发生 测试方法的任何变化。

DML 操作支持适用于所有实现 使用接口和静态资源(或 )。以下示例使用已实现的接口,但您 在使用静态资源时可以应用相同的技术。HttpCalloutMockStaticResourceCalloutMockMultiStaticResourceCalloutMockHttpCalloutMock

在模拟标注之前执行 DML

此示例基于前面提供的 HttpCalloutMock 示例。该示例演示如何使用 和 语句来允许 在模拟标注之前在测试方法中执行的 DML 操作。 测试方法()首先插入一个测试帐户,调用,使用设置模拟标注模式,调用一个方法 执行标注,验证模拟响应值,最后 调用。Test.startTestTest.stopTesttestCalloutTest.startTestTest.setMockTest.stopTest

@isTest
private class CalloutClassTest {
     @isTest static void testCallout() {
        // Perform some DML to insert test data
        Account testAcct = new Account('Test Account');
        insert testAcct;

        // Call Test.startTest before performing callout
        // but after setting test data.
        Test.startTest();

        // Set mock callout class 
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
        
        // Call method to test.
        // This causes a fake response to be sent
        // from the class that implements HttpCalloutMock. 
        HttpResponse res = CalloutClass.getInfoFromExternalService();
        
        // Verify response received contains fake values
        String contentType = res.getHeader('Content-Type');
        System.assert(contentType == 'application/json');
        String actualValue = res.getBody();
        String expectedValue = '{"example":"test"}';
        System.assertEquals(actualValue, expectedValue);
        System.assertEquals(200, res.getStatusCode());

        Test.stopTest();
    }
}

异步顶点和模拟标注

与 DML 类似,异步 Apex 操作会导致挂起的未提交工作,从而阻止 稍后在同一事务中执行的标注。示例 异步 Apex 操作是对未来方法、批处理 Apex 或计划的调用 顶点。这些异步调用通常包含在测试方法的 and 语句中,以便 它们在 之后执行。在这个 情况下,可以在异步调用后执行模拟标注,并且不会进行任何更改 必要。但是,如果异步调用没有包含在 and 语句中,你将得到一个 异常,因为未提交的工作处于待处理状态。要防止此异常,请执行以下任一操作 其中:

Test.startTestTest.stopTestTest.stopTestTest.startTestTest.stopTest

  • 将异步调用包含在 and 语句中。Test.startTestTest.stopTestTest.startTest(); MyClass.asyncCall(); Test.stopTest(); Test.setMock(..); // Takes two arguments MyClass.mockCallout();
  • 遵循与 DML 调用相同的规则:将代码部分括起来 在 AND 语句中执行标注。语句必须出现在语句之前。此外, 异步调用不能是 / 块的一部分。Test.startTestTest.stopTestTest.startTestTest.setMockTest.startTestTest.stopTestMyClass.asyncCall(); Test.startTest(); Test.setMock(..); // Takes two arguments MyClass.mockCallout(); Test.stopTest();

模拟标注之后发生的异步调用是 允许并且不需要对测试方法进行任何更改。

使用证书

要使用双向 SSL 身份验证,请发送一个证书,其中包含您的标注,该证书是 在 Salesforce 中生成或由证书颁发机构 (CA) 签名。发送证书 增强了安全性,因为标注的目标接收证书并可以使用 它根据其密钥库对请求进行身份验证。

要为标注启用双向 SSL 身份验证,请执行以下操作:

  1. 生成一个 证书。
  2. 将证书与代码集成。请参阅对 SOAP 服务使用证书和对 HTTP 请求使用证书。
  3. 如果要连接到第三方并使用自签名证书,请共享 Salesforce 证书,以便他们可以将证书添加到他们的 密钥库。如果要连接到组织内的其他应用程序, 配置 Web 服务器或应用程序服务器以请求客户端证书。这 进程取决于您使用的 Web 或应用程序服务器的类型。
  4. 配置远程站点设置 标注。在任何 Apex 标注可以调用外部站点之前,该站点必须 在“远程站点设置”页中注册,否则标注失败。如果标注 指定命名凭据作为终结点,无需配置远程 网站设置。若要设置命名凭据,请参阅《定义命名凭据》中的“定义命名凭据” Salesforce 帮助。
  1. 生成证书
  2. 将证书用于 SOAP 服务
    要支持对 SOAP Web 服务的标注进行双向身份验证,请在 Salesforce 中生成证书或将密钥对从密钥库导入 Salesforce。然后将证书与您的 Apex 集成。
  3. 对 HTTP 请求使用证书

生成证书

您可以使用在 Salesforce 中生成的自签名证书或由 证书颁发机构 (CA)。要为标注生成证书,请参阅生成证书。

成功保存 Salesforce 证书后,证书和相应的 密钥是自动生成的。

创建 CA 签名证书后,必须先上传签名证书 你可以使用它。请参阅“生成由证书签名的证书 权限“在 Salesforce 在线帮助中。

将证书用于 SOAP 服务

要支持 SOAP Web Service 标注的双向身份验证,请生成 证书或将密钥对从密钥库导入 Salesforce。然后 将证书与您的 Apex 集成。

重要

我们建议存储外部的相互身份验证证书 Java 密钥库中的 Web 服务。有关详细信息,请参阅证书和密钥。

要将证书与您的 Apex 集成,请执行以下操作:

  1. 从第三方接收 Web Service 的 WSDL,或从 要连接到的应用程序。
  2. 从 Web 服务的 WSDL 生成 Apex 类。请参阅 SOAP 服务:从 WSDL 文档定义类。
  3. 生成的 Apex 类包括用于调用第三方 Web 服务的存根 由 WSDL 文档表示。编辑 Apex 类,并为实例上的变量赋值 存根类。该值必须与 在“证书和密钥管理”页面上生成的证书。clientCertName_x

此示例演示如何编辑 Apex 类,并使用生成的 WSDL2Apex 代码中的示例 WSDL 文件。示例 假定您生成的证书的唯一名称为 。DocSampleCert

docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.clientCertName_x = 'DocSampleCert';
String input = 'This is the input string';
String output = stub.EchoString(input);

对 HTTP 请求使用证书

在 Salesforce 中生成证书后,您可以使用它来支持双向 对 HTTP 请求的标注进行身份验证。

要将证书与您的 Apex 集成,请执行以下操作:

  1. 生成证书。记下证书的唯一名称。
  2. 在 Apex 中,使用 类。用于参数的值 对于此方法,必须与证书的唯一名称匹配 在上一步中生成。setClientCertificateNameHttpRequest

以下示例演示了上一步的最后一步 程序。此示例假定您之前生成了一个证书 唯一名称为 。DocSampleCert

HttpRequest req = new HttpRequest();
req.setClientCertificateName('DocSampleCert');

标注限制和局限性

当 Apex 代码对 HTTP 请求进行标注时,存在以下限制和限制 或 Web 服务调用。Web Service 调用可以是 SOAP API 调用,也可以是任何外部 Web 服务调用。

  • 单个 Apex 事务最多可以对 HTTP 请求或 API 发出 100 个标注 叫。
  • 在 Developer Edition 组织,您最多只能对外部的端点进行 20 个并发标注 您的 Salesforce 组织的域。此限制不适用于非开发人员版 组织。
  • 默认超时为 10 秒。可以为每个标注定义自定义超时。这 最小值为 1 毫秒,最大值为 120,000 毫秒。请参阅 下一节了解如何为 Web 服务或 HTTP 标注设置自定义超时。
  • 单个 Apex 事务的标注的最大累积超时为 120 秒。 此时间是 Apex 事务调用的所有标注的累加时间。
  • 每个组织对运行时间超过 5 秒的长时间运行请求都有限制(总计 执行时间)。计算此限制时,不包括 HTTP 标注处理时间。 我们暂停标注的计时器,并在标注完成后恢复。请参阅 Lightning Platform Apex 的执行调控器和限制 限制。
  • 当同一事务中有待处理的操作时,无法进行标注。 导致挂起操作的内容是 DML 语句、异步 Apex(例如 未来方法和批处理 Apex 作业)、计划的 Apex 或发送电子邮件。您可以进行标注 在执行这些类型的操作之前。
  • 挂起的操作可能在同一事务中的模拟标注之前发生。请参阅对基于 WSDL 的标注执行 DML 操作和模拟标注或对 HTTP 标注执行 DML 操作和模拟标注。
  • 当标头添加到 标注请求和响应 未由外部服务器返回,则会发生超时。Expect: 100-ContinueHTTP/1.1 100 Continue

只读模式下的 Apex 标注

在只读模式下,外部服务的 Apex 标注会执行,并且不会被 系统。通常,您会在以下情况下在同一事务中执行一些后续操作 接收来自标注的响应。例如,您可以进行 DML 调用以更新 Salesforce 记录。但 Salesforce 中的写入操作(如记录更新)被阻止 在只读模式下。只读模式下的这种行为不一致可能会破坏 程序流程和原因问题。为避免不正确的程序行为,我们建议您 防止在只读模式下进行标注。要检查组织是否处于只读模式, 叫。System.getApplicationReadWriteMode()

下面的示例检查 的返回值。如果返回值等于枚举值,则组织 在只读模式下,将跳过标注。否则 ( value),则执行标注。System.getApplicationReadWriteMode()ApplicationReadWriteMode.READ_ONLYApplicationReadWriteMode.DEFAULT

注意

此类使用 Apex HTTP 类作为示例进行标注。您还可以制作一个 通过 WSDL2Apex 使用导入的 WSDL 进行标注。检查只读的过程 无论哪种情况,模式都是相同的。

public class HttpCalloutSampleReadOnly {
    public class MyReadOnlyException extends Exception {}

    // Pass in the endpoint to be used using the string url
    public String getCalloutResponseContents(String url) {
        
        // Get Read-only mode status
        ApplicationReadWriteMode mode = System.getApplicationReadWriteMode();
        String returnValue = '';
        
        if (mode == ApplicationReadWriteMode.READ_ONLY) {
            // Prevent the callout
            throw new MyReadOnlyException('Read-only mode. Skipping callouts!');
        } else if (mode == ApplicationReadWriteMode.DEFAULT) {
            // Instantiate a new http object
            Http h = new Http();
            
            // Instantiate a new HTTP request, specify the method (GET) 
            // as well as the endpoint.
            HttpRequest req = new HttpRequest();
            req.setEndpoint(url);
            req.setMethod('GET');
            
            // Send the request, and return a response
            HttpResponse res = h.send(req);
            returnValue = res.getBody();                        
        }
        return returnValue;
    }
}

在某些 Salesforce 维护活动期间,您的 Salesforce 组织处于只读模式。 例如计划的站点切换和实例刷新。作为连续站点切换的一部分, 您的 Salesforce 组织大约每六个月切换到一次其就绪站点。为 有关站点切换的详细信息,请参阅连续站点切换。

要在沙盒中测试只读模式,请联系 Salesforce 以启用只读模式测试 选择。启用测试选项后,您可以打开只读模式并验证您的 应用程序。

设置注解超时

下面的示例为 Web 服务标注设置自定义超时。该示例有效 替换为示例 WSDL 文件和生成的类,如生成的 WSDL2Apex 代码中所述。设置超时值(以毫秒为单位) 为特殊变量赋值 在 存根。

DocSamplePorttimeout_x

docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.timeout_x = 2000; // timeout in milliseconds

以下是为 HTTP 标注设置自定义超时的示例:

HttpRequest req = new HttpRequest();
req.setTimeout(2000); // timeout in milliseconds

使用延续进行长时间的标注

使用异步标注从 Visualforce 页面或 Lightning 组件到外部 Web 服务,并在 回调方法。

异步标注是从 Visualforce 页面或 Lightning 制作的标注 通过回调方法返回响应的组件。异步 标注也称为延续

Visualforce 示例

下图显示了异步标注的执行路径。 从 Visualforce 页面开始。用户在 Visualforce 页面上调用一个操作,该操作 从 Web 服务请求信息(步骤 1)。应用服务器发出标注请求 复制到 Continuation 服务器,然后返回到 Visualforce 页面(步骤 2-3)。这 Continuation 服务器将请求发送到 Web 服务并接收响应(步骤 4–7),然后将响应交还给应用服务器(步骤 8)。最后,回应 返回到 Visualforce 页面(步骤 9)。

异步标注的执行流程

延续的执行流程图示

受益于异步标注的典型 Salesforce 应用程序 包含一个带有按钮的 Visualforce 页面。用户单击该按钮可从 外部 Web 服务。例如,获取 Web 服务中的某些产品。组织中数以千计的代理可以使用此功能 页。因此,一百个代理商可以点击同一个按钮来处理保修 同时提供产品信息。这一百个同时动作超过了 并发长时间运行请求数限制为 10。但是通过使用异步标注, 请求不受此限制的约束,可以执行。

在以下示例应用程序中,按钮操作是在 Apex 中实现的 controller 方法。action 方法创建一个并返回它。将请求发送到服务后, Visualforce 请求已暂停。用户必须等待返回响应,然后才能返回 继续使用页面并调用新操作。当外部服务返回 响应时,Visualforce 请求将恢复,页面将收到此响应。Continuation

这是我们示例应用程序的 Visualforce 页面。此页面包含一个按钮,用于 调用控制器的方法 与此页面相关联。返回延续结果并回调后 方法,按钮将再次呈现组件以显示响应的主体。startRequestoutputText

<apex:page controller="ContinuationController" showChat="false" showHeader="false">
   <apex:form >
      <!-- Invokes the action method when the user clicks this button. -->
      <apex:commandButton action="{!startRequest}" 
              value="Start Request" reRender="result"/> 
   </apex:form>

   <!-- This output text component displays the callout response body. -->
   <apex:outputText id="result" value="{!result}" />
</apex:page>

以下是与 Visualforce 页面关联的 Apex 控制器。这 controller 包含 action 和 callback 方法。

注意

在调用外部服务之前,必须将远程站点添加到 Salesforce 用户界面中授权的远程站点。在“设置”中,在“快速查找”框中输入, ,然后选择“远程站点设置”,然后单击“新建” 远程站点Remote Site Settings

如果标注将命名凭据指定为终结点, 无需配置远程站点设置。命名凭据指定 URL 一个定义中的标注端点及其所需的身份验证参数。设置 命名凭据,请参阅 Salesforce 帮助中的“定义命名凭据”。在代码中, 指定命名凭据 URL,而不是长时间运行的服务 URL。一个命名的 credential URL 包含方案、名称 的命名凭据和可选路径。例如:。callout:callout:My_Named_Credential/some_path

public with sharing class ContinuationController {
    // Unique label corresponding to the continuation
    public String requestLabel;
    // Result of callout
    public String result {get;set;}
    // Callout endpoint as a named credential URL 
    // or, as shown here, as the long-running service URL
    private static final String LONG_RUNNING_SERVICE_URL = 
        '<Insert your service URL>';
   
   // Action method
    public Object startRequest() {
      // Create continuation with a timeout
      Continuation con = new Continuation(40);
      // Set callback method
      con.continuationMethod='processResponse';
      
      // Create callout request
      HttpRequest req = new HttpRequest();
      req.setMethod('GET');
      req.setEndpoint(LONG_RUNNING_SERVICE_URL);
      
      // Add callout request to continuation
      this.requestLabel = con.addHttpRequest(req);
      
      // Return the continuation
      return con;  
    }
    
    // Callback method 
    public Object processResponse() {   
      // Get the response by using the unique label
      HttpResponse response = Continuation.getResponse(this.requestLabel);
      // Set the result variable that is displayed on the Visualforce page
      this.result = response.getBody();
      
      // Return null to re-render the original Visualforce page
      return null;
    }
}

注意

  • 在单个延续中最多可以创建三个异步标注。添加这些 使用类的方法对同一延续的标注请求。标注并行运行,用于此延续和 暂停 Visualforce 请求。只有在外部服务返回所有标注后, Visualforce 进程将恢复。addHttpRequestContinuation
  • 异步标注仅通过 Visualforce 页面受支持。制作一个 通过在 Visualforce 页面外部调用操作方法进行异步标注,例如 在开发者控制台中,不受支持。
  • 异步标注可用于保存的 Apex 控制器和 Visualforce 页面 在版本 30.0 及更高版本中。如果使用 JavaScript 远程处理,则版本 31.0 或更高版本是 必填。
  • 专用连接不支持异步标注。
  • 使用异步标注的过程
    若要使用异步标注,请在控制器的操作方法中创建一个对象,并实现回调方法。Continuation
  • 测试异步标注
    编写测试以测试控制器并满足部署或打包 Apex 的代码覆盖率要求。由于 Apex 测试不支持进行标注,因此您可以模拟标注请求和响应。模拟标注时,请求不会发送到外部服务,而是使用模拟响应。
  • 异步标注限制
    执行延续时,将应用特定于延续的限制。当延续返回并且请求恢复时,将启动新的 Apex 事务。所有 Apex 和 Visualforce 限制均适用,并在新事务中重置,包括 Apex 标注限制。
  • 创建多个异步标注
    要从 Visualforce 页面同时对长时间运行的服务进行多个标注,您最多可以向 Continuation 实例添加三个请求。例如,当您向服务发出独立请求时,例如获取两种产品的库存统计信息,何时同时进行宣传。
  • 链接异步标注
    如果标注的顺序很重要,或者当一个标注以另一个标注的响应为条件时,则可以链接标注请求。链接标注意味着只有在前一个标注的响应返回后才会进行下一个标注。例如,您可能需要在保修服务响应指示保修过期后链接标注以获取保修延期信息。您最多可以链接三个标注。
  • 从导入的 WSDL
    创建异步标注 除了基于 的标注之外,从 WSDL 生成的类进行的 Web 服务调用中还支持异步标注。从 WSDL 生成的类创建异步标注的过程与使用该类的过程类似。HttpRequestHttpRequest

使用异步标注的过程

若要使用异步标注,请在控制器的操作方法中创建一个对象,并实现回调 方法。

Continuation

在操作方法中调用异步标注

若要调用异步标注,请使用 Visualforce 操作方法中的实例调用外部服务。 创建延续时,可以指定超时值和回调的名称 方法。例如,以下代码将创建一个具有 60 秒超时和 processResponse 的回调方法名称。Continuation

Continuation cont = new Continuation(60);
cont.continuationMethod = 'processResponse';

接下来,将对象关联到 外部标注。为此,请创建 HTTP 请求,然后将此请求添加到 延续如下:Continuation

String requestLabel = cont.addHttpRequest(request);

注意

此过程基于使用 HttpRequest 类进行标注。举个例子 使用基于 WSDL 的类,请参阅从导入的 WSDL 创建异步标注。

调用标注的方法(操作方法)必须返回对象,以指示 Visualforce 挂起 系统发送标注并等待标注响应后的当前请求。该对象包含 要执行的标注。ContinuationContinuation

这是调用标注的方法的签名。Object 返回类型 表示 .Continuation

public Object calloutActionMethodName()

定义回调方法

在外部服务完成标注处理后,将返回响应。你 可以指定 callout 返回后异步执行的回调方法。这 回调方法必须在 Controller 类中定义,其中 callout 调用方法 是定义的。您可以定义一个回调方法来处理返回的响应,例如 检索响应以显示在 Visualforce 页面上。

回调方法不接受任何参数,并具有此签名。

public Object callbackMethodName()

Object 返回类型表示 、 或 。渲染原始 Visualforce 页面并完成 Visualforce request,在回调方法中返回。ContinuationPageReferencenullnull

如果操作方法使用 JavaScript 远程处理(带有 注释),则回调方法必须是静态的,并且具有 支持以下签名。@RemoteAction

public static Object callbackMethodName(List< String> labels, Object state)

艺术

public static Object callbackMethodName(Object state)

该参数由系统在调用 callback 方法,并保存与发出的标注请求关联的标签。该参数是通过在控制器中设置 Continuation.state 属性来提供的。labelsstate

下表列出了回调方法的返回值。每个返回值对应 到不同的行为。

Callback 方法返回值请求生命周期和结果
null系统完成 Visualforce 页面请求并呈现原始 Visualforce 页面(或其中的一部分)。
PageReference系统完成 Visualforce 页面请求并重定向到新的 Visualforce 页面。(使用 中的查询参数将 的结果传递到新页面。PageReferenceContinuation
Continuation系统再次暂停 Visualforce 请求,并等待 新的标注。返回新的 用于链接异步标注的回调方法。Continuation

注意

如果未为延续设置属性,则使用相同的操作方法 这使得标注在标注响应返回时再次调用。continuationMethod

测试异步标注

编写测试以测试控制器并满足部署的代码覆盖率要求 或包装 Apex。由于 Apex 测试不支持进行标注,因此您可以模拟 标注请求和响应。模拟标注时,请求不会得到 发送到外部服务,并使用模拟响应。

下面的示例演示如何在 Web 测试中调用模拟异步标注 使用 的服务调用。模拟 标注,调用类的以下方法:Test.setContinuationResponse() 和 Test.invokeContinuationMethod()。HTTPRequestTest

首先列出要测试的控制器类,然后是测试类。控制器 此处重用了 Make Long-Running Callouts with Continuations 中的类。

public with sharing class ContinuationController {
    // Unique label corresponding to the continuation request
    public String requestLabel;
    // Result of callout
    public String result {get;set;}
    // Endpoint of long-running service
    private static final String LONG_RUNNING_SERVICE_URL = 
        '<Insert your service URL>';
   
   // Action method
    public Object startRequest() {
      // Create continuation with a timeout
      Continuation con = new Continuation(40);
      // Set callback method
      con.continuationMethod='processResponse';
      
      // Create callout request
      HttpRequest req = new HttpRequest();
      req.setMethod('GET');
      req.setEndpoint(LONG_RUNNING_SERVICE_URL);
      
      // Add callout request to continuation
      this.requestLabel = con.addHttpRequest(req);
      
      // Return the continuation
      return con;  
    }
    
    // Callback method 
    public Object processResponse() {   
      // Get the response by using the unique label
      HttpResponse response = Continuation.getResponse(this.requestLabel);
      // Set the result variable that is displayed on the Visualforce page
      this.result = response.getBody();
      
      // Return null to re-render the original Visualforce page
      return null;
    }
}

此示例显示与控制器对应的测试类。此测试类 包含用于测试异步标注的测试方法。在测试方法中,设置一个模拟响应, 并导致 callback 方法。该测试确保回调 方法通过验证控制器的结果变量 设置为预期的响应。Test.setContinuationResponseTest.invokeContinuationMethod

@isTest
public class ContinuationTestingForHttpRequest {
    public static testmethod void testWebService() {
        ContinuationController controller = new ContinuationController();
        // Invoke the continuation by calling the action method
        Continuation conti = (Continuation)controller.startRequest();
        
        // Verify that the continuation has the proper requests
        Map<String, HttpRequest> requests = conti.getRequests();
        system.assert(requests.size() == 1);
        system.assert(requests.get(controller.requestLabel) != null);
        
        // Perform mock callout 
        // (i.e. skip the callout and call the callback method)
        HttpResponse response = new HttpResponse();
        response.setBody('Mock response body');   
        // Set the fake response for the continuation     
        Test.setContinuationResponse(controller.requestLabel, response);
        // Invoke callback method
        Object result = Test.invokeContinuationMethod(controller, conti);
        // result is the return value of the callback
        System.assertEquals(null, result);
        // Verify that the controller's result variable
        //   is set to the mock response.
        System.assertEquals('Mock response body', controller.result);
    }
}

异步标注限制

执行延续时,将适用特定于延续的限制。当 延续返回,请求恢复,新的 Apex 事务开始。所有 Apex 和 Visualforce 限制适用,并在新事务中重置,包括 Apex 标注 限制。

延续特定限制

以下是特定于延续的 Apex 和 Visualforce 限制。

描述限制
单个延续中并行 Apex 标注的最大数量3
链式 Apex 标注的最大数量3
单个延续的最大超时1120 秒
最大 Visualforce 控制器状态大小280 KB
最大 HTTP 响应大小1 兆字节
最大 HTTP POST 表单大小 – 所有键和值的大小 形式31 兆字节
HTTP POST 表单中的最大密钥数3500

1在自动生成的 Web 服务存根和 HttpRequest 对象将被忽略。对于延续,仅强制执行此超时限制。

2执行延续时,将对 Visualforce 控制器进行序列化。 延续完成后,控制器被反序列化,回调 调用。使用 Apex 修饰符可以 指定不序列化的变量。该框架仅使用序列化 成员,当它恢复时。控制器状态大小限制与视图状态是分开的 限制。请参见延续控制器状态和 Visualforce 视图状态。transient

3此限制适用于具有以下内容类型标头的HTTP POST表单:content-type=’application/x-www-form-urlencoded’和content-type=’multipart/form-data’

延续控制器状态和 Visualforce 视图状态

控制器状态和视图状态是不同的。延续的控制器状态包括 请求中涉及的所有控制器的序列化,而不仅仅是 调用延续的控制器。序列化控制器包括控制器 扩展,以及自定义和内部组件控制器。控制器状态大小为 作为事件记录在调试日志中。USER_DEBUG

视图状态比控制器状态保存的数据更多,并且具有更高的最大大小 (170KB)。 视图状态包含状态和组件结构。状态是所有 控制器和页面上每个组件的所有属性,包括子页面和 子组件。组件结构是组件的父子关系 在页面中。您可以在开发者控制台或页脚中监控视图状态大小 启用开发模式时的 Visualforce 页面。有关详细信息,请参阅“查看 状态选项卡“,或参考 Visualforce 开发人员的 指南。

进行多个异步标注

从 Visualforce 同时对长时间运行的服务进行多个标注 页面上,您最多可以向 Continuation 实例添加三个请求。何时 “同时标注”是指向服务发出独立请求,例如 获取两种产品的库存统计信息。

当您在同一延续中制作多个标注时,标注请求会运行 并行并挂起 Visualforce 请求。仅在返回所有标注响应后 Visualforce 进程是否恢复。

以下 Visualforce 和 Apex 示例显示了如何进行两个异步标注 同时使用单个延续。首先显示 Visualforce 页面。这 Visualforce 页面包含一个按钮,用于调用控制器中的操作方法。当 Visualforce 进程恢复,outputPanel 组件被渲染 再。此面板显示两个异步标注的响应。startRequestsInParallel

<apex:page controller="MultipleCalloutController" showChat="false" showHeader="false">
   <apex:form >
      <!-- Invokes the action method when the user clicks this button. -->
      <apex:commandButton action="{!startRequestsInParallel}" value="Start Request" reRender="panel"/>  
   </apex:form>

   <apex:outputPanel id="panel">
       <!-- Displays the response body of the initial callout. -->   
       <apex:outputText value="{!result1}" />
       
       <br/>
       <!-- Displays the response body of the chained callout. -->
       <apex:outputText value="{!result2}" />
   </apex:outputPanel> 
   
</apex:page>

此示例显示 Visualforce 页面的控制器类。该方法将两个请求添加到 延续。返回所有标注响应后,将调用回调方法 () 并处理 反应。startRequestsInParallelprocessAllResponses

public with sharing class MultipleCalloutController {

    // Unique label for the first request
    public String requestLabel1;
    // Unique label for the second request
    public String requestLabel2;
    // Result of first callout
    public String result1 {get;set;}
   // Result of second callout
    public String result2 {get;set;}
    // Endpoints of long-running service
    private static final String LONG_RUNNING_SERVICE_URL1 = 
        '<Insert your first service URL>';
    private static final String LONG_RUNNING_SERVICE_URL2 = 
        '<Insert your second service URL>';
           
    // Action method
    public Object startRequestsInParallel() {
      // Create continuation with a timeout
      Continuation con = new Continuation(60);
      // Set callback method
      con.continuationMethod='processAllResponses';
      
      // Create first callout request
      HttpRequest req1 = new HttpRequest();
      req1.setMethod('GET');
      req1.setEndpoint(LONG_RUNNING_SERVICE_URL1);
      
      // Add first callout request to continuation
      this.requestLabel1 = con.addHttpRequest(req1);     
      
      // Create second callout request
      HttpRequest req2 = new HttpRequest();
      req2.setMethod('GET');
      req2.setEndpoint(LONG_RUNNING_SERVICE_URL2);
      
      // Add second callout request to continuation
      this.requestLabel2 = con.addHttpRequest(req2);     
      
      // Return the continuation
      return con;  
    }
    
    // Callback method.
    // Invoked only when responses of all callouts are returned.
    public Object processAllResponses() {   
      // Get the response of the first request
      HttpResponse response1 = Continuation.getResponse(this.requestLabel1);
      this.result1 = response1.getBody();

      // Get the response of the second request
      HttpResponse response2 = Continuation.getResponse(this.requestLabel2);
      this.result2 = response2.getBody();
                 
      // Return null to re-render the original Visualforce page
      return null;
    }
}

链接异步标注

如果标注的顺序很重要,或者标注以响应为条件 在另一个标注中,您可以链接标注请求。链接标注意味着下一个 只有在前一个标注的响应返回后,才会进行标注。例如,您 可能需要在保修服务后链接标注以获取保修延期信息 响应表示保修已过期。您最多可以链接三个标注。

以下 Visualforce 和 Apex 示例演示如何将一个标注链接到另一个标注。 首先显示 Visualforce 页面。Visualforce 页面包含一个按钮,用于调用 操作方法 控制器。每次返回延续时,Visualforce 进程都会暂停。 Visualforce 进程在返回每个响应后恢复,并呈现每个响应 outputPanel 组件中的响应。invokeInitialRequest

<apex:page controller="ChainedContinuationController" showChat="false" showHeader="false">
   <apex:form >
      <!-- Invokes the action method when the user clicks this button. -->
      <apex:commandButton action="{!invokeInitialRequest}" value="Start Request" reRender="panel"/>  
   </apex:form>

   <apex:outputPanel id="panel">
       <!-- Displays the response body of the initial callout. -->   
       <apex:outputText value="{!result1}" />
       
       <br/>
       <!-- Displays the response body of the chained callout. -->
       <apex:outputText value="{!result2}" />
   </apex:outputPanel> 
   
</apex:page>

此示例显示 Visualforce 页面的控制器类。该方法创建第一个 延续。回调方法 () 处理第一个标注的响应。如果 此响应满足特定条件,该方法通过返回来链接另一个标注 第二个延续。返回链式延续的响应后, 第二个回调方法 () 被调用并处理第二个响应。invokeInitialRequestprocessInitialResponseprocessChainedResponse

public with sharing class ChainedContinuationController {

    // Unique label for the initial callout request
    public String requestLabel1;
    // Unique label for the chained callout request
    public String requestLabel2;
    // Result of initial callout
    public String result1 {get;set;}
    // Result of chained callout
    public String result2 {get;set;}
    // Endpoint of long-running service
    private static final String LONG_RUNNING_SERVICE_URL1 = 
        '<Insert your first service URL>';
    private static final String LONG_RUNNING_SERVICE_URL2 = 
        '<Insert your second service URL>';
           
    // Action method
    public Object invokeInitialRequest() {
      // Create continuation with a timeout
      Continuation con = new Continuation(60);
      // Set callback method
      con.continuationMethod='processInitialResponse';
      
      // Create first callout request
      HttpRequest req = new HttpRequest();
      req.setMethod('GET');
      req.setEndpoint(LONG_RUNNING_SERVICE_URL1);
      
      // Add initial callout request to continuation
      this.requestLabel1 = con.addHttpRequest(req);              
      
      // Return the continuation
      return con;  
    }
    
    // Callback method for initial request
    public Object processInitialResponse() {   
      // Get the response by using the unique label
      HttpResponse response = Continuation.getResponse(this.requestLabel1);
      // Set the result variable that is displayed on the Visualforce page
      this.result1 = response.getBody();
           
      Continuation chainedContinuation = null;
      // Chain continuation if some condition is met
      if (response.getBody().toLowerCase().contains('expired')) {
          // Create a second continuation 
          chainedContinuation = new Continuation(60);
          // Set callback method
          chainedContinuation.continuationMethod='processChainedResponse';
          
          // Create callout request
          HttpRequest req = new HttpRequest();
          req.setMethod('GET');
          req.setEndpoint(LONG_RUNNING_SERVICE_URL2);
          
          // Add callout request to continuation
          this.requestLabel2 = chainedContinuation.addHttpRequest(req); 
      }
      
      // Start another continuation 
      return chainedContinuation;  
    }    
    
    // Callback method for chained request
    public Object processChainedResponse() {   
      // Get the response for the chained request
      HttpResponse response = Continuation.getResponse(this.requestLabel2);
      // Set the result variable that is displayed on the Visualforce page
      this.result2 = response.getBody();
           
      // Return null to re-render the original Visualforce page
      return null;
    }
}

注意

在创建新的延续之前,必须检索延续的响应 以及 Visualforce 请求再次暂停之前。您无法检索旧的 来自延续链中较早延续的回应。

从导入的 WSDL 进行异步标注

除了基于 -的标注之外, 在从 WSDL 生成的 Web 服务调用中支持异步标注 类。从 WSDL 生成的类进行异步标注的过程与此类似 到使用类的过程。

HttpRequestHttpRequest

在 Salesforce 中导入 WSDL 时,Salesforce 会为每个类自动生成两个 Apex 类 命名空间。一个类是同步的服务类 服务,另一个是异步服务的修改版本。这 自动生成的异步类名以 Async 前缀开头 其格式为 AsyncServiceName。 是原始未修改的服务类的名称。 异步类在以下方面与标准类不同。ServiceName

  • 公共服务方法包含一个额外的参数作为第一个参数。Continuation
  • Web 服务操作是异步调用的,其响应是 用 response 元素。getValue
  • 和 习惯于 分别调用服务和获取响应。WebServiceCallout.beginInvokeWebServiceCallout.endInvoke

您可以在 Salesforce 用户界面中从 WSDL 生成 Apex 类。从设置中, 在“快速查找”框中输入 Apex 类, 然后选择 Apex 类

若要进行异步 Web 服务标注,请在自动生成的 异步类,方法是将实例传递给这些方法。以下示例基于假设的股票报价 服务。此示例假定组织具有一个名为 的类,该类是自动生成的 通过 WSDL 导入。该示例演示如何对服务进行异步标注 通过使用自动生成的类。首先,此示例创建一个 以 60 秒的超时时间继续,并设置回调方法。接下来,代码 示例调用方法 将 Continuation 实例传递给它。方法调用对应于异步标注 执行。ContinuationAsyncSOAPStockQuoteServiceAsyncSOAPStockQuoteServicebeginStockQuotebeginStockQuote

public Continuation startRequest() {
   Integer TIMEOUT_INT_SECS = 60;  
   Continuation cont = new Continuation(TIMEOUT_INT_SECS);
   cont.continuationMethod = 'processResponse';
   
   AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap 
      stockQuoteService = 
        new AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap();
   stockQuoteFuture = stockQuoteService.beginStockQuote(cont,'CRM');    

   return cont;   
}

当外部服务返回异步标注(方法)的响应时,此回调方法是 执行。它通过调用响应对象上的方法来获取响应。beginStockQuotegetValue

public Object processResponse() {
   result = stockQuoteFuture.getValue();
   return null; 
}

以下是带有 action 和 callback 方法的整个控制器。

public class ContinuationSOAPController {
 
    AsyncSOAPStockQuoteService.GetStockQuoteResponse_elementFuture
           stockQuoteFuture;
    public String result {get;set;}

    // Action method
    public Continuation startRequest() {    
       Integer TIMEOUT_INT_SECS = 60;  
       Continuation cont = new Continuation(TIMEOUT_INT_SECS);
       cont.continuationMethod = 'processResponse';
       
       AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap 
          stockQuoteService = 
            new AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap();
           stockQuoteFuture = stockQuoteService.beginGetStockQuote(cont,'CRM');     
       return cont;   
    }    
    
    // Callback method
    public Object processResponse() {    
       result = stockQuoteFuture.getValue();
       // Return null to re-render the original Visualforce page
       return null; 
    }
}

此示例显示了调用该方法并显示结果的相应 Visualforce 页面 田。startRequest

<apex:page controller="ContinuationSOAPController" showChat="false" showHeader="false">
   <apex:form >      
      <!-- Invokes the action method when the user clicks this button. -->
      <apex:commandButton action="{!startRequest}" 
              value="Start Request" reRender="result"/> 
   </apex:form>

   <!-- This output text component displays the callout response body. -->
   <apex:outputText value="{!result}" />
</apex:page>

测试基于 WSDL 的异步标注

测试基于 WSDL 中的 Apex 类的异步标注与此类似 添加到与基于类的标注一起使用的流程。在测试 ContinuationSOAPController.cls 之前,请创建一个 实现。本类 启用对 ContinuationTestForWSDL.cls 的安全测试,该测试 我们稍后将通过启用模拟延续并确保 测试没有实际效果。HttpRequestWebServiceMock

public class AsyncSOAPStockQuoteServiceMockImpl implements WebServiceMock {
    public void doInvoke(
        Object stub, 
        Object request, 
        Map<String, Object> response,
        String endpoint, 
        String soapAction, 
        String requestName,
        String responseNS, 
        String responseName, 
        String responseType) {
        // do nothing
    }
}

此示例是对应于控制器的测试类。类中的测试方法 设置一个假响应并调用一个模拟的延续。标注不会发送到 外部服务。为了执行模拟标注,测试调用类的以下方法:Test.setContinuationResponse() 和 Test.invokeContinuationMethod()。ContinuationSOAPControllerTest

@isTest
public class ContinuationTestingForWSDL {
    public static testmethod void testWebService() {

        ContinuationSOAPController demoWSDLClass = 
            new ContinuationSOAPController();

        // Invoke the continuation by calling the action method
        Continuation conti = demoWSDLClass.startRequest();

        // Verify that the continuation has the proper requests
        Map<String, HttpRequest> requests = conti.getRequests();
        System.assertEquals(requests.size(), 1);

        // Perform mock callout 
        // (i.e. skip the callout and call the callback method)
        HttpResponse response = new HttpResponse();
        response.setBody('<SOAP:Envelope'
            + ' xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">'
            + '<SOAP:Body>'
            + '<m:getStockQuoteResponse '
            + 'xmlns:m="http://soap.sforce.com/schemas/class/StockQuoteServiceSoap">'
            + '<m:result>Mock response body</m:result>' 
            + '</m:getStockQuoteResponse>' 
            + '</SOAP:Body>'
            + '</SOAP:Envelope>');

        // Set the fake response for the continuation
        String requestLabel = requests.keyset().iterator().next();
        Test.setContinuationResponse(requestLabel, response);

        // Invoke callback method
        Object result = Test.invokeContinuationMethod(demoWSDLClass, conti);
        System.debug(demoWSDLClass);

        // result is the return value of the callback
        System.assertEquals(null, result);

        // Verify that the controller's result variable
        //   is set to the mock response.
        System.assertEquals('Mock response body', demoWSDLClass.result);
    }
}