Salesforce Apex 类深度解析:开发者实践指南
背景与应用场景
作为一名 Salesforce 开发人员,我们日常工作中接触最频繁、使用最核心的技术无疑是 Apex。Apex 是 Salesforce 平台提供的一种强类型、面向对象的编程语言,它允许开发者在服务器端执行流程和事务控制语句,为复杂的业务需求提供了强大的定制能力。虽然 Salesforce 的声明式工具(如 Flow、Validation Rules)功能日益强大,但在处理复杂业务逻辑、与外部系统集成、或构建高度定制化的用户体验时,Apex 依然是不可或缺的。
而 Apex Class (Apex 类) 则是 Apex 编程的基本构建单元。它就像一个蓝图或模板,定义了对象的属性(变量)和行为(方法)。在 Salesforce 平台上,几乎所有的自定义逻辑都是通过 Apex 类来实现的。
常见的应用场景包括:
- 自定义业务逻辑: 实现标准功能无法覆盖的复杂验证规则、记录创建/更新的自动化处理、精确的计算逻辑等。例如,在创建订单时,自动根据客户等级、产品类型和历史购买记录计算一个复杂的折扣。
- Web 服务: 创建自定义的 REST 或 SOAP API,使外部系统能够安全地与 Salesforce 数据进行交互。例如,为移动应用提供一个查询客户信息的接口。
- Lightning 组件控制器: 作为 Lightning Web Components (LWC) 或 Aura 组件的后端控制器,负责数据查询、处理和持久化,为前端提供强大的数据支持。
- 异步处理: 使用 Batch Apex 处理海量数据(如每晚清洗和更新数十万条联系人数据),使用 Queueable Apex 执行需要较长时间的调用(如复杂的外部系统调用),或使用 Scheduled Apex 定期执行任务(如每月生成汇总报告)。
- Email 服务: 创建可以处理入站邮件的 Apex 类,实现根据收到的邮件内容自动创建 Case 或 Lead 的功能。
理解并精通 Apex 类的结构、原理和最佳实践,是每一位 Salesforce 开发人员从入门到精通的必经之路。本文将从开发者的视角,深入探讨 Apex 类的构成、关键概念以及在实际开发中需要注意的事项。
原理说明
一个 Apex Class 的定义包含了多个关键部分,理解这些部分的作用是编写高质量代码的基础。其基本语法结构如下:
[private | public | global] [with sharing | without sharing | inherited sharing] class ClassName [implements InterfaceName] [extends ClassName] { // 成员变量、常量、方法、构造函数等 }
1. 访问修饰符 (Access Modifier)
访问修饰符决定了 Apex 类、方法或变量的可见性范围。
- private: 默认的访问修饰符。仅在当前的 Apex 类内部可见。这是最严格的访问级别,有助于实现良好的封装。
- public: 在您的应用程序或命名空间 (Namespace) 内的任何地方都可以访问。例如,一个 LWC 的控制器方法通常需要声明为
public
。 - global: 这是最宽松的访问级别,它使得类或方法不仅在您的 Salesforce 组织中可见,而且可以被任何命名空间或应用程序访问。通常只在开发托管包 (Managed Package) 或定义需要被外部系统调用的 Web 服务时使用。
2. 共享关键字 (Sharing Keyword)
这是 Salesforce 平台特有的一个至关重要的安全概念,它决定了 Apex 代码在执行时是否遵循当前用户的共享规则 (Sharing Rules) 和记录级访问权限。
- with sharing: 强制执行当前用户的共享规则。如果一个用户没有权限查看某条记录,那么使用
with sharing
的类在执行 SOQL 查询时也无法查到这条记录。这是保证数据安全的默认和推荐选项。 - without sharing: 忽略共享规则。代码在系统模式 (System Mode) 下运行,可以访问组织内的所有数据,无论当前执行用户是否有权限。这在需要处理汇总数据或执行特权操作时非常有用,但必须谨慎使用,以防数据泄露。
- inherited sharing: 继承其调用者的共享模式。如果一个
inherited sharing
的类被一个with sharing
的类调用,它将以with sharing
模式运行。如果调用者是without sharing
,它则以without sharing
模式运行。这为构建可复用的工具类提供了极大的灵活性。 - 如果未指定共享关键字,类的共享模式默认为
without sharing
,但如果该类被另一个类调用,它会继承调用者的共享模式。这是一个容易混淆的点,因此最佳实践是显式声明共享模式。
3. 类的成员
一个 Apex 类可以包含以下类型的成员:
- 变量 (Variables): 用于存储对象的状态。可以是基本数据类型(如
Integer
,String
,Boolean
),也可以是 sObject(如Account
,Contact
)或其他 Apex 对象。 - 方法 (Methods): 定义了类的行为和逻辑。方法可以接收参数并返回值。
- 构造函数 (Constructors): 一种特殊的方法,用于创建和初始化类的实例(对象)。构造函数没有返回值,并且其名称必须与类名完全相同。
- 属性 (Properties): 一种特殊的语法,结合了私有变量和公共的 get/set 访问器方法,用于控制对类成员变量的读写。
- 内部类 (Inner Classes): 在一个 Apex 类内部定义的另一个类。
4. 静态 (Static) vs. 实例 (Instance)
类成员(变量和方法)可以是静态的或实例的。
- 实例成员: 属于类的特定实例(对象)。必须先使用
new
关键字创建一个类的实例,然后才能通过该实例访问其成员。每个实例都有自己的一份实例变量副本。 - 静态成员: 使用
static
关键字声明,它属于类本身,而不是任何特定实例。可以直接通过类名进行访问,无需创建对象。所有实例共享同一份静态变量。静态方法通常用于创建工具类或工厂方法。
示例代码
以下示例均来自 Salesforce 官方文档,展示了 Apex 类的不同方面。
示例 1: 一个基础的 Apex 类
这是一个名为 EmailManager
的类,它包含一个公共方法,用于处理入站邮件并根据邮件内容创建联系人和任务。这个例子很好地展示了类的基本结构和业务逻辑的实现。
// 来自 Salesforce Apex Developer Guide global class EmailManager { // 全局方法,用于处理 InboundEmail 对象 global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) { // 创建一个 InboundEmailResult 对象以返回结果 Messaging.InboundEmailResult result = new Messaging.InboundEmailresult(); String myPlainText = ''; myPlainText = email.plainTextBody; // 获取邮件的纯文本正文 try { // 创建一个新的 Contact 对象 Contact c = new Contact(); c.FirstName = 'Test'; c.LastName = email.fromName; c.Email = envelope.fromAddress; insert c; // 插入 Contact 记录 System.debug('New contact created with ID: ' + c.Id); // 创建一个关联到新 Contact 的 Task Task t = new Task(); t.WhoId = c.Id; t.Subject = email.subject; t.Status = 'New'; t.Priority = 'Normal'; insert t; // 插入 Task 记录 } catch (QueryException e) { System.debug('Query Issue: ' + e); } // 设置结果为成功 result.success = true; // 返回结果 return result; } }
示例 2: 带有构造函数和属性的类
这个例子展示了一个名为 MyClass
的类,它有一个构造函数用于初始化,以及一个公共属性。
// 来自 Salesforce Apex Developer Guide - "Classes" public class MyClass { // 公共整型属性,带有 get 和 set 访问器 public Integer myInteger { get; set; } // 构造函数,接收一个整型参数 public MyClass(Integer i) { // 在对象创建时,使用传入的参数初始化属性 this.myInteger = i; } // 一个实例方法,返回属性值的平方 public Integer getSquare() { return this.myInteger * this.myInteger; } // 一个静态方法,不依赖任何实例 public static String getGlobalMessage() { return 'This is a static message.'; } } // 如何使用这个类: // MyClass instance = new MyClass(5); // 调用构造函数创建实例 // Integer square = instance.getSquare(); // 调用实例方法, square 的值为 25 // String msg = MyClass.getGlobalMessage(); // 直接通过类名调用静态方法
示例 3: 使用 `with sharing` 保证数据安全
这个例子展示了如何显式声明 with sharing
来确保代码执行时遵循用户的记录访问权限。
// 来自 Salesforce Apex Developer Guide - "Using the with sharing, without sharing, and inherited sharing Keywords" public with sharing class ContactReader { public List<Contact> getContacts() { // 这个 SOQL 查询只会返回当前执行用户有权查看的 Contact 记录。 // 如果一个用户因为共享规则或角色层级无法看到某个 Contact, // 即使它满足查询条件,也不会被包含在返回结果中。 return [SELECT Id, Name, Email FROM Contact]; } }
注意事项
在编写 Apex 类时,必须时刻牢记 Salesforce 作为一个多租户平台的特性,这带来了一些关键的限制和需要注意的事项。
- Governor Limits (执行调控器和限制): Salesforce 平台为了保证所有租户的资源公平使用,对代码执行施加了严格的限制。作为开发者,必须时刻关注:
- SOQL 查询限制: 在一个同步事务中,最多只能执行 100 条 SOQL 查询。
- DML 语句限制: 在一个同步事务中,最多只能执行 150 次 DML 操作(如 insert, update, delete)。
- CPU 时间限制: 同步事务的 CPU 执行时间上限为 10,000 毫秒。
- 堆大小限制 (Heap Size): 同步事务的内存使用上限为 6MB。
违反这些限制会导致代码执行失败并抛出异常。因此,Bulkification (批量化) 是最重要的编码原则,即永远不要在循环中执行 SOQL 查询或 DML 操作。
- 安全性和数据访问:
- 务必显式声明类的共享模式 (
with sharing
,without sharing
, orinherited sharing
),以明确代码的数据访问上下文。 - 在处理 DML 操作之前,应检查用户是否具有对象的创建、读取、更新、删除 (CRUD) 权限和字段级的访问权限 (Field-Level Security, FLS)。可以使用
Security.stripInaccessible
方法来安全地移除用户无权访问的字段,防止 DML 失败。
- 务必显式声明类的共享模式 (
- 测试覆盖率 (Test Coverage):
为了保证代码质量和平台稳定性,Salesforce 要求部署到生产环境的 Apex 代码必须有至少 75% 的代码覆盖率。这意味着您必须编写单元测试来验证您的代码逻辑。一个高质量的测试类不仅是为了满足覆盖率,更重要的是要能够验证代码在各种场景下的正确性,包括正面用例、负面用例和边界情况。
- API 版本 (API Version):
每个 Apex 类都与一个特定的 Salesforce API 版本相关联。这决定了该类在执行时所遵循的行为和语法规则。当 Salesforce 发布新版本时,旧版本的类行为保持不变,这保证了向后兼容性。在维护旧代码或使用新功能时,需要注意类的 API 版本。
总结与最佳实践
Apex 类是 Salesforce 开发的基石。编写出高效、可维护且安全的代码,不仅能满足复杂的业务需求,还能确保系统的长期稳定和可扩展性。作为开发者,我们应遵循以下最佳实践:
- 单一职责原则 (Single Responsibility Principle): 让每个类只负责一件事情。例如,一个类负责处理客户相关的 DML 操作,另一个类负责调用外部系统的 API。这使得代码更易于理解、测试和维护。
- 代码批量化: 始终以处理记录集合(如
List<sObject>
)为前提来设计你的方法,而不是处理单个记录。这是避免触及 Governor Limits 的核心策略。 - 安全第一: 默认使用
with sharing
。仅在确实需要提升权限时才谨慎使用without sharing
,并严格审查其安全性。在 DML 操作前进行 FLS/CRUD 检查。 - 编写有意义的测试: 不要只为了 75% 的覆盖率而写测试。测试用例应该覆盖核心业务逻辑,并使用
System.assert()
来验证结果的正确性。 - 避免硬编码 ID: 切勿在代码中硬编码记录 ID、URL 或其他环境特定的值。使用自定义元数据类型 (Custom Metadata Types)、自定义设置 (Custom Settings) 或标签 (Labels) 来存储这些配置,以便在不同环境间轻松迁移。
- 善用工具类: 对于通用的、可复用的功能(如日期格式化、字符串处理),可以创建静态方法的工具类,方便在整个代码库中调用。
- 代码注释和命名规范: 编写清晰的注释,并采用统一的命名规范(如驼峰命名法)。良好的代码本身就是最好的文档。
通过遵循这些原则,您将能够构建出健壮、高效且安全的 Salesforce 应用程序,为您的客户或企业创造更大的价值。
评论
发表评论