我实际接触 ColdFusion 项目之后,最开始最容易困惑的并不是语法。
真正让我困惑的是:
Application.cfc到底什么时候执行?.cfm和.cfc有什么区别?- 为什么浏览器访问的是
.cfm,但实际业务逻辑却在.cfc? session、request、application到底有什么区别?- 一个请求从浏览器进入 ColdFusion 后,到底经历了什么?
如果这些基础关系没有弄清楚,即使能够照着已有代码修改功能,也很容易变成“代码能改,但是不知道为什么这样写”。
所以这一篇先不急着做复杂功能,而是从 ColdFusion Web 应用的整体结构开始。
1. ColdFusion 到底是什么?
ColdFusion 是一种运行在服务器端的 Web 应用开发平台。
我们写的:
index.cfm
login.cfm
user.cfc
Application.cfc
并不是直接在浏览器里运行。
浏览器访问:
https://example.com/index.cfm
实际过程大致是:
浏览器
↓
Web Server
↓
ColdFusion Server
↓
解析 CFML
↓
访问数据库 / Session / 文件等
↓
生成 HTML / JSON
↓
返回浏览器
和 PHP 很像。
例如:
<cfset name = "Li">
<h1>Hello, #name#</h1>
ColdFusion 会先在服务器上执行:
<cfset name = "Li">
然后最终给浏览器返回:
<h1>Hello, Li</h1>
浏览器根本看不到原来的 CFML。
2. .cfm 是什么?
.cfm 可以理解成:
ColdFusion 的页面文件。
例如:
login.cfm
index.cfm
user_list.cfm
report.cfm
通常负责:
- 页面显示
- HTML
- 表单
- JavaScript
- 调用 CFC
- 接收 URL/Form 参数
- 输出最终页面
例如:
<cfparam name="url.id" default="">
<cfquery name="qUser" datasource="mydb">
SELECT
id,
user_name
FROM
users
WHERE
id = <cfqueryparam value="#url.id#" cfsqltype="cf_sql_integer">
</cfquery>
<h1>
<cfoutput>#EncodeForHTML(qUser.user_name)#</cfoutput>
</h1>
这里的 .cfm 同时包含:
输入
↓
数据库
↓
HTML
这种写法在老 ColdFusion 项目里非常常见。
但是随着系统越来越复杂,通常会把数据库和业务逻辑逐渐拆到 .cfc。
3. .cfc 是什么?
.cfc 全称:
ColdFusion Component
可以把它理解成:
ColdFusion 的“类 / Service / API 组件”。
例如:
User.cfc
Auth.cfc
Report.cfc
Common.cfc
Student.cfc
基本结构:
<cfcomponent>
<cffunction name="getUser" access="public" returntype="struct">
<cfargument
name="userId"
type="numeric"
required="true"
>
<cfset var result = {}>
<cfset result.id = arguments.userId>
<cfset result.name = "Li">
<cfreturn result>
</cffunction>
</cfcomponent>
这里:
<cfcomponent>
代表定义一个组件。
而:
<cffunction>
代表组件中的方法。
这和 Java:
class UserService {
public User getUser(int userId) {
}
}
概念上比较接近。
4. CFM 和 CFC 最大的区别
可以先用一句非常简单的话理解:
CFM = 页面
CFC = 功能
例如:
user_list.cfm
负责显示:
用户列表页面
而:
UserService.cfc
负责:
查询用户
新增用户
更新用户
删除用户
结构可以是:
user_list.cfm
↓
UserService.cfc
↓
Database
例如:
<cfset userService = createObject(
"component",
"components.UserService"
)>
<cfset users = userService.getUsers()>
然后:
<cfcomponent>
<cffunction name="getUsers" access="public" returntype="query">
<cfquery name="qUsers" datasource="mydb">
SELECT
id,
user_name,
email
FROM
users
ORDER BY
id
</cfquery>
<cfreturn qUsers>
</cffunction>
</cfcomponent>
这样页面和业务逻辑就分开了。
5. CFC 不只是“后台文件”
这里有一个很重要的地方。
CFC 的函数可以有不同的 access。
例如:
<cffunction
name="getUser"
access="public"
>
常见值包括:
private
package
public
remote
其中实际 Web 开发里特别需要理解的是:
public
remote
6. public 和 remote 的区别
public
<cffunction
name="getUser"
access="public"
>
表示这个方法可以被 ColdFusion 内部其他组件或页面调用。
例如:
<cfset obj = createObject(
"component",
"UserService"
)>
<cfset result = obj.getUser(1)>
remote
<cffunction
name="getUser"
access="remote"
>
表示:
可以通过 HTTP 从浏览器外部调用。
例如:
/user/UserService.cfc?method=getUser&id=1
或者通过 JavaScript Ajax 调用。
因此:
remote
相当于暴露了一个接口。
这也是为什么在处理安全问题时,access="remote" 必须特别注意。
因为:
public
只是内部方法。
而:
remote
有可能直接成为 Web API。
7. Application.cfc 是什么?
这是整个 ColdFusion 项目里非常重要的文件。
通常位于项目根目录:
/project
Application.cfc
index.cfm
login.cfm
components/
ColdFusion 收到请求时,会自动寻找:
Application.cfc
它相当于整个 Web 应用的生命周期控制器。
可以控制:
- 应用初始化
- Session
- 请求处理
- 错误处理
- 登录检查
- Datasource
- 全局配置
- URL 重写相关逻辑
- 安全处理
8. 一个最基础的 Application.cfc
例如:
component {
this.name = "MyApplication";
this.sessionManagement = true;
this.sessionTimeout = createTimeSpan(
0,
0,
30,
0
);
}
这里:
this.name
定义应用名称。
例如:
this.name = "MyApplication";
ColdFusion 会根据这个名字区分 Application Scope。
9. 开启 Session
例如:
this.sessionManagement = true;
这样才能使用:
session.userId
session.userName
session.login
例如登录成功之后:
session.userId = qUser.id;
session.userName = qUser.user_name;
之后其他页面:
<cfoutput>
#session.userName#
</cfoutput>
仍然可以取得。
10. Application.cfc 的生命周期
这是理解 ColdFusion 项目非常关键的一部分。
常见方法包括:
onApplicationStart()
onApplicationEnd()
onSessionStart()
onSessionEnd()
onRequestStart()
onRequest()
onRequestEnd()
onError()
可以理解成:
ColdFusion Application
│
├─ Application 启动
│ └─ onApplicationStart()
│
├─ 用户 Session 开始
│ └─ onSessionStart()
│
├─ Request
│ ├─ onRequestStart()
│ ├─ xxx.cfm
│ └─ onRequestEnd()
│
└─ Error
└─ onError()
11. onApplicationStart()
Application 第一次启动时执行。
例如:
function onApplicationStart() {
application.appName = "My System";
application.version = "1.0.0";
return true;
}
可以存储:
整个应用共享的数据
比如:
application.systemName
application.config
application.masterData
12. onSessionStart()
某个用户第一次建立 Session 时执行。
例如:
function onSessionStart() {
session.login = false;
session.userId = "";
}
这样每个新 Session 都会默认:
未登录
13. onRequestStart()
这个方法非常重要。
几乎:
每一次请求都会执行。
例如:
function onRequestStart(targetPage) {
if (
!structKeyExists(session, "login")
||
!session.login
) {
if (
!findNoCase(
"login.cfm",
arguments.targetPage
)
) {
location(
url = "/login.cfm",
addToken = false
);
}
}
return true;
}
意思就是:
访问任意页面
↓
检查 Session
↓
没有登录
↓
跳转 login.cfm
所以有时候你看到:
明明只是访问某个 cfm
结果却发生:
302
302
302
很有可能问题并不在那个页面本身,而是在:
Application.cfc
onRequestStart()
这里。
14. 为什么 Application.cfc 会造成 302 循环?
假设:
function onRequestStart(targetPage) {
if (!session.login) {
location("/login.cfm");
}
}
那么访问:
/user.cfm
会:
user.cfm
↓
session.login = false
↓
302 login.cfm
然后:
login.cfm
↓
又执行 onRequestStart()
↓
session.login = false
↓
再次 302 login.cfm
于是形成:
login.cfm
↓
login.cfm
↓
login.cfm
↓
login.cfm
也就是:
Too Many Redirects
所以通常必须排除登录页面:
if (!findNoCase("login.cfm", targetPage)) {
}
15. Application Scope
Application Scope:
application.xxx
是:
所有用户共同使用。
例如:
application.systemName = "SmartFORCE";
所有用户都能读:
application.systemName
适合保存:
系统名称
共通配置
缓存
Master 数据
系统版本
16. Session Scope
Session:
session.xxx
是:
每个用户自己的会话数据。
例如:
session.userId
session.userName
session.role
用户 A:
session.userId = 100
用户 B:
session.userId = 200
互不影响。
17. Request Scope
Request:
request.xxx
生命周期非常短。
只在:
当前 HTTP Request
中有效。
例如:
request.startTime = getTickCount();
请求结束之后就消失。
18. Variables Scope
普通页面里经常看到:
variables.xxx
在 CFC 里面:
variables
通常属于:
Component Instance
例如:
component {
variables.datasource = "mydb";
}
函数:
function getUser() {
queryExecute(
"
SELECT *
FROM users
",
{},
{
datasource = variables.datasource
}
);
}
19. Arguments Scope
函数参数:
function getUser(required numeric userId) {
}
里面应该使用:
arguments.userId
例如:
if (arguments.userId <= 0) {
}
这样比直接写:
userId
更加明确。
20. Local Scope
函数内部变量最好使用:
local
例如:
function getUser(required numeric userId) {
local.result = {};
local.query = queryExecute(
"
SELECT
id,
user_name
FROM
users
WHERE
id = :id
",
{
id = {
value = arguments.userId,
cfsqltype = "cf_sql_integer"
}
}
);
return local.query;
}
旧式 CFML 里也经常看到:
<cfset var result = "">
这是为了避免变量进入 variables Scope。
21. 一个请求到底是怎么执行的?
例如浏览器访问:
https://example.com/user/list.cfm
ColdFusion 大致会:
① 找 Application.cfc
② 如果 Application 未启动
onApplicationStart()
③ 如果是新 Session
onSessionStart()
④ onRequestStart()
⑤ 执行 user/list.cfm
⑥ list.cfm 可能调用 UserService.cfc
⑦ UserService.cfc 查询 DB
⑧ list.cfm 生成 HTML
⑨ onRequestEnd()
⑩ 返回浏览器
画成结构:
Browser
│
▼
Application.cfc
│
├─ onRequestStart()
│
▼
list.cfm
│
▼
UserService.cfc
│
▼
Database
│
▼
list.cfm
│
▼
HTML
│
▼
Browser
22. 我现在对 CFM / CFC / Application.cfc 的理解
经过实际项目开发,我目前会把它们理解成:
Application.cfc
=
整个应用的入口和生命周期管理
CFM
=
页面层
CFC
=
业务逻辑 / Component / API
例如:
Application.cfc
│
▼
student_list.cfm
│
▼
StudentService.cfc
│
▼
Database
当理解了这一层之后,再去看大型 ColdFusion 项目,就不会只是看到:
几百个 cfm
几百个 cfc
而是能够开始判断:
这个是页面
这个是业务逻辑
这个是 API
这个是全局处理
这个是 Session
这个是 Request
这也是我觉得学习 ColdFusion 时非常重要的第一步。

