精通 Salesforce Apex 类:开发者综合指南
背景与应用场景
在 Salesforce 平台中,管理员和开发者可以使用一系列强大的声明式工具(如 Flow、校验规则)来快速实现业务需求。然而,当业务逻辑变得复杂、需要与外部系统进行深度集成,或者需要处理大量数据时,声明式工具往往会遇到瓶颈。这时,Apex 就成为了不可或缺的编程解决方案。
Apex 是 Salesforce 平台提供的一种强类型、面向对象的编程语言,其语法与 Java 类似。通过编写 Apex Classes (Apex 类),开发者可以构建稳健、可扩展的应用程序,以满足最复杂的业务需求。
Apex 类的主要应用场景包括:
- 复杂的业务逻辑实现: 当业务规则包含多步计算、复杂的条件判断或跨多个对象的操作时,Apex 类可以提供声明式工具无法比拟的灵活性。
- 自定义 Web 服务: 创建 REST 或 SOAP API,使外部系统能够安全地与 Salesforce 数据进行交互。
- Visualforce 控制器: 为 Visualforce 页面提供后端逻辑支持,控制页面行为和数据展示。
- Aura 和 Lightning Web Components (LWC) 后端控制器: 作为 Lightning 组件的数据服务层,执行服务器端调用(例如,查询数据、执行 DML 操作)。
- 批处理作业 (Batch Apex): 异步处理数百万条记录,而不会超出平台的 Governor Limits (管控限制)。
- 计划任务 (Scheduled Apex): 在指定时间自动执行 Apex 代码,用于日常维护、数据同步等任务。
- 自定义邮件服务: 处理入站电子邮件,并根据邮件内容自动创建或更新 Salesforce 记录。
理解和掌握 Apex 类的编写是每一位 Salesforce 开发者的核心技能,也是构建企业级应用的基础。
原理说明
Apex 类是创建对象的蓝图或模板。一个类封装了数据(以变量形式)和行为(以方法形式)。所有 Apex 代码都在 Salesforce 的多租户环境中执行,这意味着平台会强制实施严格的资源限制(即 Governor Limits),以确保没有单个客户的代码会垄断共享资源。
面向对象编程 (Object-Oriented Programming, OOP)
Apex 遵循 OOP 的核心原则,这使得代码更加模块化、可重用和易于维护:
- 封装 (Encapsulation): 将数据(属性)和操作数据的代码(方法)捆绑在一个单元(类)中。通过访问修饰符(`private`, `protected`, `public`, `global`)控制对内部成员的访问。
- 继承 (Inheritance): 一个类可以从另一个类(父类)继承属性和方法,从而实现代码重用。使用 `extends` 关键字实现继承。
- 多态 (Polymorphism): 允许使用父类类型的变量来引用子类的对象,并通过 `virtual` 和 `override` 关键字实现方法的重写,使得程序可以根据对象的实际类型执行不同的行为。
类的关键组成部分
- 访问修饰符 (Access Modifiers):
- `private`:仅在当前类内部可见。这是最严格的访问级别。
- `public`:对命名空间内的所有 Apex 代码可见。
- `global`:对所有 Apex 代码可见,无论其命名空间如何。通常用于构建可供外部调用的 Web 服务或在受管包中暴露 API。
- 共享关键字 (Sharing Keywords):
- `with sharing`:强制执行当前用户的共享规则。这是默认的(如果类是从 Aura/LWC 调用的)。
- `without sharing`:不强制执行共享规则,代码可以访问和操作组织内的所有数据(但仍受用户权限和许可证限制)。
- `inherited sharing`:继承调用它的类的共享模式。这是推荐的最佳实践,因为它使类的行为更具可预测性。
- 变量 (Variables): 用于存储数据的成员。可以是基本数据类型(如 `Integer`, `String`, `Boolean`)或复杂类型(如 sObjects, List, Map)。
- 方法 (Methods): 定义类的行为。方法可以接受参数并返回值。
- 构造函数 (Constructor): 一种特殊的方法,用于在创建类的实例时进行初始化。
- 静态方法 (Static Methods): 属于类本身而不是类的实例,可以直接通过类名调用,无需创建对象。
示例代码
以下示例均来自 Salesforce 官方开发者文档,展示了 Apex 类的不同方面。
示例 1: 一个基础的 Apex 类
这个例子定义了一个简单的 `EmailManager` 类,它包含一个 `public` 方法,用于向一组联系人发送邮件。这个类展示了基本的类结构、方法定义和 DML 操作。
// public 访问修饰符意味着这个类可以被组织内的任何 Apex 代码访问。 public class EmailManager { // 这是一个 public 方法,接受一个字符串列表作为收件人地址。 // 它不返回值 (void)。 public void sendMail(String[] addresses, String subject, String body) { // 创建一个 Messaging.SingleEmailMessage 对象列表来存储要发送的邮件。 // 在循环外创建集合是 "Bulkification" 的第一步。 List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>(); for (String address : addresses) { // 创建一个邮件实例。 Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); // 设置收件人地址。 String[] toAddresses = new String[] {address}; mail.setToAddresses(toAddresses); // 设置邮件主题。 mail.setSubject(subject); // 设置纯文本邮件正文。 mail.setPlainTextBody(body); // 将准备好的邮件添加到列表中。 messages.add(mail); } // 使用 Messaging.sendEmail() 方法一次性发送列表中的所有邮件。 // 这是一个高效的 DML 操作,有助于避免超出 Governor Limits。 Messaging.SendEmailResult[] results = Messaging.sendEmail(messages); // 检查发送结果。 if (results[0].isSuccess()) { System.debug('The email was sent successfully.'); } else { System.debug('The email failed to send: ' + results[0].getErrors()[0].getMessage()); } } }
示例 2: 带有 SOQL 查询和共享关键字的类
这个例子展示了一个带有 `with sharing` 关键字的类,它会遵守当前用户的共享规则。它执行一个 SOQL (Salesforce Object Query Language) 查询来获取与特定客户关联的联系人。
// 'with sharing' 关键字确保该类的代码在当前用户的共享权限下运行。 // 这意味着用户只能查询到他们有权访问的客户和联系人记录。 public with sharing class ContactSearch { // 这是一个 public static 方法,可以直接通过类名调用,例如:ContactSearch.searchForContacts(...) // 它返回一个 Contact 对象的列表。 public static List<Contact> searchForContacts(String accountName, String mailingPostalCode) { // 执行 SOQL 查询。 // 这个查询会查找与给定客户名称和邮政编码匹配的联系人。 // 这是一个安全、高效的查询,因为它使用了绑定变量(冒号前缀的变量)来防止 SOQL 注入。 List<Contact> contacts = [SELECT Id, Name FROM Contact WHERE Account.Name = :accountName AND MailingPostalCode = :mailingPostalCode]; return contacts; } }
示例 3: 带有构造函数的类
这个例子展示了一个 `AccountHelper` 类,它包含一个构造函数,在创建类的实例时接收一个 `Account` 记录。这是一种常见的封装 sObject 记录并为其提供附加业务逻辑的模式。
public class AccountHelper { // 声明一个 private 成员变量来存储 Account 记录。 // 'private' 意味着它只能在此类内部访问。 // 'final' 意味着一旦在构造函数中赋值,就不能再更改。 private final Account acct; // 这是一个构造函数。它的名称与类名相同,没有返回值。 // 当使用 'new AccountHelper(someAccount)' 创建实例时,此构造函数被调用。 public AccountHelper(Account a) { // 将传入的 Account 记录赋值给成员变量。 this.acct = a; } // 这是一个 public 方法,用于验证客户的年度收入字段。 public Boolean validate() { if (this.acct.AnnualRevenue == null || this.acct.AnnualRevenue <= 0) { // 如果年度收入为空或小于等于零,则验证失败。 return false; } return true; } } // 如何使用这个类: // Account myAccount = [SELECT Id, Name, AnnualRevenue FROM Account WHERE Name = 'Test Corp' LIMIT 1]; // AccountHelper helper = new AccountHelper(myAccount); // Boolean isValid = helper.validate(); // System.debug('Is the account valid? ' + isValid);
注意事项
在编写 Apex 类时,必须时刻牢记 Salesforce 平台的特性和限制。
Governor Limits (管控限制)
Salesforce 在多租户架构上运行,为防止任何单一代码事务消耗过多共享资源,平台设置了严格的限制。常见的限制包括:
- SOQL 查询: 每个事务中最多执行 100 个同步 SOQL 查询。
- DML 语句: 每个事务中最多执行 150 次 DML 操作(如 `insert`, `update`, `delete`)。
- 总记录数: DML 语句处理的总记录数不能超过 10,000 条。
- CPU 时间: 每个事务的 CPU 执行时间有限制(例如,同步为 10,000 毫秒)。
- 堆大小 (Heap Size): 同步事务中为 6MB,异步为 12MB。
解决方案: 始终遵循 Bulkification (批量化) 模式。即,设计代码来处理记录集合(`List`, `Set`, `Map`),而不是在循环中逐条处理记录并执行 SOQL 或 DML。
错误处理
健壮的代码必须包含完善的错误处理机制。使用 `try-catch` 块来捕获和处理潜在的异常,如 `QueryException` 或 `DmlException`。对于 DML 操作,可以考虑使用 `Database` 类的方法(如 `Database.insert(records, allOrNone)`),它允许部分记录成功并返回失败记录的详细错误信息,而不是抛出异常。
安全与权限
始终考虑数据安全。默认情况下,Apex 在系统模式下运行,这意味着它会忽略对象级和字段级安全性(FLS)。
- 使用 `with sharing` 关键字来强制执行用户的共享规则。
- 在动态 SOQL 或处理敏感数据时,使用 `Schema` 方法(如 `isAccessible()`, `isUpdateable()`)来编程检查用户是否具有对特定对象或字段的访问权限。
测试覆盖率
Salesforce 强制要求 Apex 代码(类和触发器)必须有至少 75% 的测试覆盖率才能部署到生产环境。测试类不仅是满足部署要求的工具,更是确保代码质量、功能正确性和防止未来更新破坏现有功能的关键保障。应编写能够覆盖所有业务逻辑分支、正面和负面场景的测试用例。使用 `Test.startTest()` 和 `Test.stopTest()` 来隔离代码并获取一组新的 Governor Limits。
总结与最佳实践
Apex 类是 Salesforce 平台开发的核心。一个设计良好的 Apex 类应该是高效、可读、可维护且安全的。作为技术架构师,我推荐遵循以下最佳实践:
- 单一职责原则: 每个类应该只负责一项功能。例如,一个类负责处理业务逻辑,另一个类负责数据查询。这使得代码更易于理解和测试。
- 代码批量化 (Bulkify Your Code): 永远不要在循环中放置 SOQL 查询或 DML 语句。始终使用集合来处理数据。
- 善用 Map 优化查询: 当需要基于一组 ID 查询相关记录时,先将查询结果放入一个 `Map
` 中,这样可以在后续逻辑中通过 ID 高效地获取记录,而无需再次查询。 - 明确共享模式: 为每个类显式声明共享关键字(`with sharing`, `without sharing`, `inherited sharing`)。`inherited sharing` 是最安全和最灵活的选择,因为它会适应其调用上下文。
- 编写有意义的测试: 不要只为了达到 75% 的覆盖率而测试。使用 `System.assertEquals()` 来断言代码的行为是否符合预期,并测试各种边界条件。
- 代码注释和命名规范: 使用清晰的变量名和方法名,并为复杂的逻辑块添加注释。这对于团队协作和长期维护至关重要。
- 避免硬编码 ID: 永远不要在 Apex 代码中硬编码记录 ID。应该使用动态查询、自定义元数据类型 (Custom Metadata Types) 或自定义设置 (Custom Settings) 来存储和检索这些值。
通过遵循这些原则和最佳实践,您可以构建出不仅能满足当前需求,而且能够随着业务发展而轻松扩展的高质量 Salesforce 应用程序。
评论
发表评论