优化Apex与Visualforce代码编写的实用指南
在开发过程中,编写高效、安全且易于维护的代码至关重要。本文将深入探讨Apex和Visualforce代码编写的相关技巧,帮助开发者提升代码质量。
1. 让类了解测试上下文
在Apex(触发器、批处理或控制器)代码中,有时需要知道代码是在正常执行还是在测试模式下执行。这一点非常重要,因为某些操作在测试模式下是不允许的,例如Web服务调用。可以使用 Test.isRunningTest() API为测试用例提供模拟数据。更多信息可参考: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_http_testing.htm 。
2. Apex REST Web服务
Apex提供了简单的注解,如 @RestResource 、 @HttpPost 和 @HttpGet ,可将任何类作为RESTful Web服务公开。以下是一些重要的注意事项:
- 使用 with sharing 关键字 :在声明Web服务类时,通常应使用 with sharing 关键字。在特殊情况下,可以使用 without sharing 绕过安全限制(如共享规则等),但这应在极端情况下进行,因为这样做会破坏安全性。
- 强制执行CRUD/FLS设置 :使用Force.com ESAPI或Apex描述信息在Apex REST中强制执行CRUD/FLS设置,这些设置默认不会强制执行。
- 利用 RestContext 类 :该类可以让你处理入站的 RestRequest 和出站的 RestResponse 对象。
3. REST Web服务的API版本控制
Force.com的API版本控制做得很好,能够保持旧版本的向后兼容性。在Apex中也可以轻松实现相同的功能。例如,假设这是REST服务和Apex类的第一个版本:
@RestResource(urlMapping='/CoolestApexService/v1/*')
global with sharing class CoolestApexService
{
…
}
如果在API的v2版本中,Apex代码和API有重大更改,为了不破坏依赖v1版本API的客户端代码,可以创建上述Apex类的副本,并在副本中进行重大更改,同时保持向后兼容性。示例如下:
@RestResource(urlMapping='/CoolestApexService/v2/*')
global with sharing class CoolestApexServiceV2
{
…
}
请注意,这里重命名了类和REST注解的URL映射。
4. 了解限制
在某些情况下,了解给定上下文中的可用限制非常重要。这里的上下文指的是执行上下文,在Visualforce页面控制器、触发器、匿名块和测试用例中可能会有所不同。例如,堆大小限制通常为6 MB,但在电子邮件服务中则变为36 MB。
除了可用的总限制外,Apex运行时还会提供执行过程中到目前为止已消耗的限制的详细信息。可以使用 Limits 系统类来了解各种因素的总限制和已消耗的限制,例如:
- 脚本语句
- 堆大小
- SOQL查询数量
以下是一个示例代码,展示了如何使用 Limits API来了解可用的SOQL调用:
// Prints 100.
System.debug('Available Queries : ' + (Limits.getLimitQueries() -
Limits.getQueries()));
// Consume 1 Query limit out of typical quota of 100
Contact[] contacts = [Select Id from Contact];
// Consume 1 more Query limit out of typical quota of 100
Account[] accounts = [Select Id from Account];
// Prints 98 now.
System.debug('Available Queries : ' + (Limits.getLimitQueries() -
Limits.getQueries()));
要了解更多关于 Limits 系统类及其方法的信息,请参考: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_limits.htm 。
5. 跟踪资源(限制)使用情况
了解并关注Apex代码消耗的各种平台资源/限制配额是一个很好的做法。与其他语言(如Java)一样,要跟踪资源使用情况,需要检查调试日志。如果你之前从未使用过调试日志,建议先阅读以下文章:
- 理解调试日志
- 设置调试日志过滤器
调试日志可以在以下两个位置进行检查:
- 在设置区域使用调试日志 :可以通过导航到“Your Name | Setup | Monitoring | Debug Logs”来为给定用户启用日志记录。点击“New”按钮,为给定用户启用日志记录。启用日志记录后,所有代码执行(控制器、触发器、批处理、未来方法等)都可以在“Debug Logs”网格中看到。要查看资源使用的详细信息,可以点击任何日志记录的“View”链接。在日志详细信息中,最后一部分显示了各种平台资源的累积使用情况。任何接近限制的资源使用都是一个很好的提示,需要在生产崩溃之前调整应用程序。建议及时进行必要的重构或清理,以保留足够的资源限制余量。更多详细信息,请参考: 查看调试日志 。
- 使用开发者控制台的日志记录功能 :与上述调试方法类似,可以使用开发者控制台进行相同的操作。要了解更多关于使用开发者控制台查看调试日志的信息,请查看完整指南: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_debugging_system_log_console.htm 。
6. Visualforce架构
Visualforce(VF)是Force.com创建自定义屏幕的方式。来自.NET和JEE(特别是JSF)背景的开发者会发现自定义标签、视图状态和回发有很多相似之处。与Apex一样,重要的是要理解VF页面在多租户环境中执行。因此,没有人拥有服务器,也不能编写需要数分钟来处理同步作业的页面。始终有一个监管者监视页面的资源消耗,如果VF页面消耗超过限制,将会停止显示。
在开始编码之前,通常最好先了解VF架构和执行顺序。以下是一些获取更多信息的链接:
- Visualforce架构
- Visualforce页面的执行顺序
在VF开发中,最重要的是要理解缓存行为和会话管理,这与其他语言/平台(如Java、.NET和PHP等)不同。以下是与Java(J2EE/JEE)的比较:
| 比较项 | Java(J2EE/JEE) | Force.com平台 |
| ---- | ---- | ---- |
| 缓存类/页面定义 | Java服务器将所有类定义缓存在内存(RAM/堆)中。通常,在首次请求时,如果JSP页面未编译/缓存,它将被转换为Java类定义并缓存在堆中。下次请求该页面时,堆中的缓存副本将进行渲染。 | 由于平台的多租户性质,无法将定义保留在内存中。编译后的类/页面定义存储在数据库(元数据存储库)中。为了处理页面请求,编译后的定义将加载到内存中以渲染页面。 |
| 会话管理 | Java服务器提供了多种会话管理方式,并完全控制更改它。开发者也可以调整或替换服务器的会话管理。例如,绕过cookie、使用隐藏字段、URL重写等。 | 平台负责会话管理,开发者无需担心。由于平台符合行业领先的安全标准,为了保护客户数据,Salesforce不允许你调整/替换会话管理。 |
| 会话存储 | 开发者可以在请求绑定的全局变量(如Session或HttpSession)中缓存一些数据,并在每次请求时根据需要访问它。 | 由于多租户性质,开发者没有像Session这样的全局变量可用,因此无法进行请求绑定的数据缓存。Visualforce使用一种可扩展的无状态机制,称为ViewState,通常用于存储流程所需的请求数据。 |
7. 重用平台的原生外观和感觉
在创建VF页面时,有一个非常基本且重要的用户体验(UX)方面需要考虑。我见过一些开发者在样式上过于花哨,但在这个平台上这并没有帮助;VF页面的最终用户大多是现有的Salesforce客户(99%的时间)。因此,如果VF页面与原生外观和感觉不相似,就会变得不直观,并且会带来培训问题。
以下是一些使用关键组件使VF页面看起来更像原生页面的技巧:
- 使用原生标题开始页面设计 : <apex:sectionHeader> 组件常被许多Force.com开发者忽略,但它是实现原生外观和感觉的第一步。它会添加一个标题部分,包含页面标题和副标题。例如,以下代码展示了如何使用该标签为Case标准对象的页面添加原生外观和感觉:
<apex:pagestandardController="Case">
<apex:sectionHeader title="Cases" subtitle="De-dupe cases"/>
<apex:pageBlock title="Select cases">
<!-- Your code goes here -->
</apex:pageBlock>
</apex:page>
- 创建原生详细信息部分/表单 :各种组件,如
<apex:pageBlock>、<apex:pageBlockSection>、<apex:pageBlockTable>和<apex:inputField>可以帮助你快速布局原生外观的详细信息部分。在大多数页面中,这些组件将是<apex:sectionHeader>组件之后的第二个构建块。例如,以下代码片段使用几行VF代码创建了一个新的Case屏幕,其外观和感觉与原生屏幕非常相似:
<apex:pagestandardController="Case">
<apex:sectionHeader title="Cases" subtitle="New Custom Case Screen"/>
<apex:form>
<apex:pageBlock title="Create New Case" mode="edit">
<apex:pageBlockSection title="Case Basic Info">
<apex:inputField value="{!Case.AccountId}"/>
<apex:inputField value="{!Case.Status}"/>
<apex:inputField value="{!Case.ContactId}"/>
<apex:inputField value="{!Case.priority}"/>
<apex:inputField value="{!Case.Origin}"/>
</apex:pageBlockSection>
<apex:pageBlockSection title="Other Case section">
<!-- Add more fields here -->
</apex:pageBlockSection>
<apex:pageBlockButtons>
<apex:commandButton value="Save" action="{!save}"/>
<apex:commandButton value="Cancel" action="{!cancel}"/>
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
</apex:page>
使用 <apex:pageBlockSection> 组件的 columns 属性可以帮助将布局从默认的两列更改为更高或更低的值。
- 将表格样式设置为原生网格 :类似地, <apex:pageBlockTable> 等组件可用于获取具有原生外观和感觉的网格/表格。最好的部分是,这个组件与sObjects配合得很好,因此它会自动将列标题作为主题字段标签(无需额外代码)。例如:
<apex:pageBlockTable value="{!account.Contacts}" var="con">
<apex:column value="{!con.name}"/>
<apex:column value="{!con.Title}"/>
<apex:column value="{!con.Email}"/>
<apex:column value="{!con.phone}"/>
</apex:pageBlockTable>
- 以原生样式打印消息 :在VF中以原生外观和感觉显示信息、警告或错误类型的消息非常简单。开发者可以使用
<apex:pageMessage/>标签直接在页面中打印消息。例如,以下单行代码可以显示一条原生外观的信息性消息:
<apex:pageMessage summary="Your job is queued for processing, email
notification would be sent on completion" severity="info" strength="3"
/>
如果需要,可以通过Apex添加消息并在VF页面上显示:
- 控制器 :
public void onValidate() {
if (event.Registeration_Deadline__c < System.now()){
ApexPages.addMessage(new
ApexPages.Message(ApexPages.Severity.ERROR,
'Registration for this event is closed now.'));
}
...
}
- **VF页面**:需要重新渲染`<apex:pageMessages/>`组件。
始终在几乎所有VF AJAX调用中重新渲染 <apex:pageMessages> 组件。这样做有助于在很多情况下优雅地捕获用户和平台生成的消息。根据不同的情况(如确认、错误、致命、信息和警告),尝试在Apex和Visualforce中使用各种严重级别。
8. 原生(标准)与自定义控制器/扩展
标准控制器为开发者提供了丰富的平台功能。它们有两种类型:标准控制器和标准列表控制器。前者适用于处理单个记录,后者适用于处理记录列表。从高层次来看,它们提供以下关键功能:
- 自动处理安全性 :自动处理用户的FLS(字段级安全)和其他权限相关的安全性。
- 轻松访问对象字段 :标准控制器无需编写任何查询代码,即可轻松访问对象字段,包括父级和子级关系。标准控制器会检测页面中使用的字段并透明地查询它们。你可以在 这里 了解更多信息。
- 标准CRUD操作和列表操作 :提供标准的CRUD操作(如保存、快速保存、编辑、删除)和带分页的列表操作(下一页、上一页、第一页、最后一页)。以下链接来自Apex开发者指南,提供了可用方法的完整信息:
- 标准控制器
- 标准列表控制器
通常的经验法则是,尽可能充分利用标准控制器。以下是VF开发者指南中的一些代码片段,展示了标准控制器的功能:
- 标准列表控制器的分页
- 标准列表控制器的编辑数据
除了上述所有功能外,在某些情况下可能需要使用自定义控制器,例如:
- 一个复杂的页面需要同时对多个对象类型执行DML操作,例如,一个页面在点击“保存”按钮时创建一个账户和多个关联的联系人。
- 页面流程中需要进行Web服务调用。
- 需要通过Apex发送电子邮件。
即使在上述情况下,Force.com开发者也应该首先尝试使用自定义控制器扩展来解决问题,因为扩展保留了大部分标准功能,并根据需要添加新功能。例如,以下代码片段展示了如何使用扩展查询股票价格,同时保留原生功能:
<apex:pagestandardController="Account" extensions="DynamicBindingExt">
<apex:pageMessage summary="Stock price for {!Account.Name} is
{!stockPrice}" severity="info" strength="3" />
<apex:detail subject="{!account.id}" />
</apex:page>
public with sharing class DynamicBindingExt {
public Decimal stockPrice{get;set;}
public DynamicBindingExt(ApexPages.StandardController controller) {
Account acc = (Account)controller.getRecord();
String recordName = acc.Name;
// stockPrice = result from HTTP callout based on account name
}
}
关键要点是,除非确实需要,否则避免使用自定义控制器。这并不意味着自定义控制器没有用处,但关键是要尽可能利用标准平台的优势。
9. 重用VF代码
在VF中重用代码非常重要,特别是在中型和大型项目中,多个开发者可能会通过以下方式重复造轮子:
- 反复编写相同的标记和逻辑。
- 在不同页面之间复制粘贴VF代码。
通过遵循上述关于Apex和Visualforce代码编写的技巧,开发者可以提高代码的质量、安全性和可维护性,从而在AppExchange列表或生产部署中更加轻松。同时,合理利用标准功能和重用代码可以提高开发效率,减少错误。
优化Apex与Visualforce代码编写的实用指南
10. 避免过多复制粘贴,重用代码
为了避免在VF开发中进行过多的复制粘贴,实现代码重用,可以采用以下几种方式:
- 组件化开发 :将常用的页面元素封装成组件,例如一个包含特定样式和功能的表单组件。在不同页面中可以直接引用这些组件,减少重复代码。以下是一个简单的组件示例:
<!-- 定义组件 -->
<apex:component>
<apex:pageBlock title="Common Form">
<apex:inputField value="{!someField}" />
<apex:commandButton value="Submit" action="{!submitAction}" />
</apex:pageBlock>
</apex:component>
<!-- 在页面中引用组件 -->
<apex:page>
<c:CommonForm />
</apex:page>
- 使用模板 :创建通用的页面模板,包含页面的基本结构和样式,如页眉、页脚等。不同的页面可以基于这些模板进行扩展,只需要关注页面的具体内容。例如:
<!-- 定义模板 -->
<apex:page>
<apex:sectionHeader title="Template Page" />
<apex:insert name="body" />
<apex:sectionFooter />
</apex:page>
<!-- 使用模板的页面 -->
<apex:page template="c:MyTemplate">
<apex:define name="body">
<!-- 页面具体内容 -->
<apex:pageBlock title="Content">
<apex:inputField value="{!anotherField}" />
</apex:pageBlock>
</apex:define>
</apex:page>
11. 限制视图状态
视图状态(ViewState)是Visualforce用于在请求之间保存页面数据的机制。过多的视图状态会导致页面性能下降,因此需要对其进行限制。以下是一些限制视图状态的方法:
- 减少不必要的数据存储 :只在视图状态中存储必要的数据,避免存储大量的临时数据或不需要跨请求保留的数据。例如,在控制器中使用瞬态(transient)关键字标记不需要存储在视图状态中的变量:
public class MyController {
public transient List<SomeObject> tempList; // 该列表不会存储在视图状态中
public List<SomeObject> importantList; // 重要数据会存储在视图状态中
public MyController() {
tempList = new List<SomeObject>();
importantList = new List<SomeObject>();
}
}
- 使用AJAX进行局部更新 :通过AJAX调用只更新页面的部分内容,而不是整个页面。这样可以减少视图状态的传输和存储。例如:
<apex:page>
<apex:form>
<apex:outputPanel id="panel">
<apex:inputField value="{!someField}" />
<apex:commandButton value="Update" action="{!updateAction}" reRender="panel" />
</apex:outputPanel>
</apex:form>
</apex:page>
12. 通过字段集编写灵活页面
字段集(FieldSets)是一种在Salesforce中定义一组字段的方式。通过使用字段集,可以使VF页面更加灵活,能够根据不同的需求动态显示字段。以下是使用字段集的步骤:
1. 创建字段集 :在对象设置中创建字段集,选择要包含的字段。
2. 在VF页面中引用字段集 :
<apex:page standardController="Account">
<apex:form>
<apex:pageBlock title="Account Details">
<apex:pageBlockSection title="Field Set Section">
<apex:repeat value="{!$ObjectType.Account.FieldSets.MyFieldSet}" var="field">
<apex:inputField value="{!Account[field]}" />
</apex:repeat>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
通过这种方式,当需要更改显示的字段时,只需要在字段集中进行修改,而不需要修改页面代码。
13. 加速AJAX调用
为了提高AJAX调用的速度,可以采取以下措施:
- 优化查询 :在AJAX调用涉及的数据查询中,确保查询语句高效。避免不必要的字段查询和复杂的条件。例如:
public class MyAjaxController {
public List<Contact> getContacts() {
// 只查询必要的字段
return [SELECT Id, Name, Email FROM Contact LIMIT 10];
}
}
- 减少数据传输量 :只返回AJAX调用所需的数据,避免返回过多的冗余数据。可以在控制器中对数据进行处理和筛选。
- 使用缓存 :对于一些不经常变化的数据,可以使用缓存机制,减少重复查询。例如:
public class MyAjaxController {
private static Map<Id, Contact> contactCache;
public List<Contact> getContacts() {
if (contactCache == null) {
contactCache = new Map<Id, Contact>([SELECT Id, Name, Email FROM Contact LIMIT 10]);
}
return contactCache.values();
}
}
14. 充分利用全局变量
Salesforce提供了一些全局变量,如 $User 、 $Label 、 $ObjectType 等。合理利用这些全局变量可以简化代码,提高开发效率。以下是一些常见全局变量的使用示例:
- $User :获取当前用户的信息,如用户名、用户ID等。
<apex:page>
<p>Welcome, {!$User.Name}</p>
</apex:page>
-
$Label:用于国际化和本地化,获取自定义标签的值。
<apex:page>
<p>{!$Label.MyCustomLabel}</p>
</apex:page>
-
$ObjectType:获取对象的元数据信息,如字段列表、记录类型等。
<apex:page>
<apex:repeat value="{!$ObjectType.Account.FieldSets.MyFieldSet}" var="field">
<apex:inputField value="{!Account[field]}" />
</apex:repeat>
</apex:page>
15. 避免JavaScript远程调用的常见错误
JavaScript远程调用(JavaScript Remoting)是一种在Visualforce页面中调用Apex方法的技术。在使用时需要注意以下常见错误:
- 方法签名问题 :确保Apex方法的签名与JavaScript调用中的参数和返回类型一致。例如:
// Apex方法
global with sharing class MyRemoteClass {
@RemoteAction
public static String getMessage(String param) {
return 'Hello, ' + param;
}
}
// JavaScript调用
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.MyRemoteClass.getMessage}',
'World',
function(result, event) {
if (event.status) {
alert(result);
} else {
alert('Error: ' + event.message);
}
},
{escape: true}
);
- 安全性问题 :对远程调用的方法进行适当的权限检查,避免未经授权的访问。可以在Apex方法中添加权限验证逻辑。
- 错误处理 :在JavaScript中对远程调用的结果进行充分的错误处理,确保在出现错误时能够给用户友好的提示。
16. 注意Visualforce页面的安全性
在开发Visualforce页面时,安全性至关重要。以下是一些安全方面的注意事项:
- 输入验证 :对用户输入进行严格的验证,防止SQL注入、XSS攻击等。可以使用Apex的验证规则或JavaScript进行前端验证。
- 访问控制 :确保只有授权用户能够访问页面和执行相关操作。可以使用标准控制器的安全功能或自定义的访问控制逻辑。
- 数据加密 :对于敏感数据,如密码、信用卡信息等,进行加密存储和传输。
17. 其他杂项技巧
除了上述内容,还有一些关于图表、动态VF组件、PDF渲染等方面的技巧:
- 图表 :使用 <apex:chart> 组件可以轻松创建各种图表,如柱状图、折线图等。例如:
<apex:page>
<apex:chart data="{!chartData}" width="400" height="300">
<apex:axis type="Category" position="bottom" fields="label" title="Labels" />
<apex:axis type="Numeric" position="left" fields="value" title="Values" />
<apex:barSeries orientation="vertical" xField="label" yField="value" />
</apex:chart>
</apex:page>
- 动态VF组件 :可以在运行时动态创建和添加VF组件。例如,根据用户的选择动态显示不同的表单。
public class DynamicComponentController {
public Component.Apex.OutputPanel getDynamicPanel() {
Component.Apex.OutputPanel panel = new Component.Apex.OutputPanel();
panel.id = 'dynamicPanel';
if (someCondition) {
Component.Apex.InputField input = new Component.Apex.InputField();
input.value = '{!someField}';
panel.childComponents.add(input);
}
return panel;
}
}
<apex:page controller="DynamicComponentController">
<apex:dynamicComponent componentValue="{!dynamicPanel}" />
</apex:page>
- PDF渲染 :使用
<apex:page renderAs="pdf">可以将页面渲染为PDF文件。可以设置页面的样式和布局,使其适合PDF格式。
<apex:page renderAs="pdf">
<h1>PDF Report</h1>
<p>Some report content here...</p>
</apex:page>
综上所述,通过掌握这些关于Apex和Visualforce代码编写的技巧,开发者能够编写出高质量、安全且易于维护的代码,提高开发效率,同时在应用部署和使用过程中避免许多潜在的问题。无论是在小型项目还是大型项目中,这些技巧都能发挥重要作用,帮助开发者更好地完成工作。
下面是一个简单的mermaid流程图,展示了从代码编写到部署的基本流程:
graph LR
A[编写Apex和VF代码] --> B[代码审查]
B --> C[测试]
C --> D[修复问题]
D --> E{是否通过测试}
E -- 是 --> F[部署到生产环境]
E -- 否 --> A
同时,为了更清晰地展示不同代码编写技巧的适用场景,我们可以列出以下表格:
| 技巧 | 适用场景 |
| ---- | ---- |
| 重用平台原生外观和感觉 | 创建面向Salesforce现有客户的页面 |
| 重用VF代码 | 中型和大型项目,多个开发者参与开发 |
| 限制视图状态 | 页面性能优化,减少数据传输 |
| 通过字段集编写灵活页面 | 需要动态显示字段的页面 |
| 加速AJAX调用 | 页面交互频繁,需要快速响应的场景 |
| 充分利用全局变量 | 简化代码,提高开发效率 |
| 避免JavaScript远程调用的常见错误 | 使用JavaScript Remoting技术的页面 |
| 注意Visualforce页面的安全性 | 涉及用户数据和敏感操作的页面 |
| 杂项技巧(图表、动态组件、PDF渲染) | 需要展示数据图表、动态内容或生成PDF报告的页面 |
超级会员免费看
66

被折叠的 条评论
为什么被折叠?



