APEXlang 是 Oracle APEX 26.1 引入的应用文本表示形式。它把 App Builder 中的声明式应用定义整理成可读、可验证、可导入的 .apx 文件,让浏览器内的可视化开发、VS Code 编辑、SQLcl 命令行验证、Git 审查和 AI 编码智能体协作使用同一份应用元数据。

24 使用 APEXlang#

APEXlang 不是另一套运行时框架,而是同一个 APEX 应用定义的文本化视图。你仍然可以在 App Builder 中用向导、Page Designer 和 Shared Components 完成主要开发;当任务更适合外部工具时,再把应用导出为 APEXlang,用编辑器、SQLcl、版本控制系统或 AI 编码智能体处理。

本章的学习路线是:先理解 APEXlang 解决的问题,再创建一个很小的 Employees 示例应用;随后从 App Builder 导出和导入,阅读导出的语法结构,最后分别练习 VS Code、SQLcl、工作副本比较和 App Builder 内置 APEXlang 视图。

学习前提:有一个 APEX 26.1 工作区;示例使用基于 EMP 表的 Employees 应用;如果要练习外部编辑,还需要 SQLcl 26.1 或 SQL Developer for VS Code 扩展,并能连接到工作区解析方案。

验证目标:你应能导出一个 APEXlang 应用,识别 application.apxpagesshared-components.apex/apexlang.json 的作用;修改后能通过 apex validate 或 VS Code Problems 面板发现错误,并只在验证通过后导入回 App Builder。

24.1 APEXlang 的价值#

在 APEXlang 之前,APEX 应用可以用 SQL 导出,适合安装、迁移和源码留存,但文件里包含内部 API 调用和大量数值 ID,不适合作为日常编辑格式。可读 YAML 曾经让审查和差异比较更容易,但它不是可编辑后再导入的格式。

APEXlang 补上的是“可读、可改、可验证、可导入”这一环。App Builder 继续适合声明式配置和可视化设计;SQLcl 适合自动化导出、导入和验证;VS Code 适合搜索、批量修改、补全和 Problems 面板;Git 适合代码审查和变更历史;AI 编码智能体则适合在明确规格与验证命令约束下辅助生成和修复。

操作检查:找一个已有应用,先用 App Builder 查看页面结构,再导出 APEXlang 并在编辑器里搜索同一页面、区域和页面项。确认你能把浏览器里的组件对应到 .apx 文件里的组件定义。

24.2 与 AI 编码智能体协作#

APEXlang 的结构稳定、语义清晰,适合 AI 编码智能体读取和修改。Oracle 在 GitHub 上提供 APEXlang skills,配合 SQLcl 的 apex validate 命令,可以让智能体在“修改文件、运行验证、根据错误修正、再次验证”的循环中工作。

前提:外部项目目录中已有 APEXlang 导出;SQLcl 可运行;智能体已加载 Oracle APEXlang 相关技能或提示;团队已经写清楚功能规格、数据模型、页面目标和验收条件。

推荐流程:先让智能体阅读当前 APEXlang 文件和蓝图或规格,提出最小改动计划;开发者确认边界后再让它修改;每次修改后运行 apex validate;只有验证无错误且人工审查通过,才导入到 App Builder。

验证检查:智能体输出不能只看“生成完成”。必须检查 SQLcl 验证结果、Git 差异、涉及页面的运行效果,以及安全、授权、数据源和页面项绑定是否符合业务规则。

APEX blueprints 也可以与 AI 协作结合使用。团队先把功能规格和数据模型写清楚,AI 根据 Oracle 的 blueprint 生成提示产出结构化 Markdown 计划;App Builder 再把蓝图确定性地生成应用脚手架。这样,利益相关者可以尽早试用真实页面,在签署方向后再进入细节开发。

24.3 从 App Builder 导出 APEXlang#

为了学习 APEXlang,官方示例先用 Create App Wizard 创建一个很小的 Employees 应用。这个应用基于熟悉的 EMP 表,包含一个 Interactive Report 页面和一个模态 Form 页面,足够展示页面、区域、页面项、LOV、模板、按钮和动作在 APEXlang 中的表示方式。

图 24-1 使用 Create App Wizard 创建 Employees 示例应用,后续导出结果用于阅读 APEXlang 结构。

操作路径:进入 App Builder,打开目标应用,使用 Export Application 页面。在导出格式中选择 APEXlang,然后下载导出文件。官方示例下载的是 employees.zip

图 24-2 在 App Builder 中选择 APEXlang 作为应用导出格式。

解压后可以看到应用级文件、页面文件、共享组件文件、部署设置和 APEXlang 元数据。页面文件名同时包含页面号和页面别名,便于审查;编译器也会检查文件名中的页面号和别名是否与文件内容一致。

employees.zip
├── application.apx
├── page-groups.apx
├── deployments
│   └── default.json
├── pages
│   ├── p00000-global-page.apx
│   ├── p00001-home.apx
│   ├── p00002-employees.apx
│   ├── p00003-employee.apx
│   └── p09999-login.apx
├── shared-components
│   ├── static-files
│   │   └── icons
│   ├── themes
│   │   └── universal-theme
│   │       └── theme.apx
│   ├── authentications.apx
│   ├── authorizations.apx
│   ├── breadcrumbs.apx
│   ├── build-options.apx
│   ├── component-settings.apx
│   ├── lists.apx
│   ├── lovs.apx
│   └── static-files.apx
└── .apex
    └── apexlang.json

应用 ID 不直接写在应用主体文件中,而是放在 deployments/default.json。这让导出的 zip 可以使用应用别名作为更友好的文件名,也让不同环境保留自己的部署设置。

{
  "app" : {
    "id" : 104,
    "runtime" : {
      "debugging" : true
    }
  }
}

验证检查:确认 zip 中至少有 application.apxpages/shared-components/deployments/default.json.apex/apexlang.json;再打开 pages/p00003-employee.apx,确认它能对应到 App Builder 中的 Employee 表单页。

24.4 在 App Builder 中导入 APEXlang#

App Builder 可以直接导入 APEXlang 格式的应用。进入 Import 向导,把 APEXlang zip 文件拖入或选择上传,然后按向导继续。

图 24-3 在 App Builder 中导入 APEXlang 格式应用。

导入向导会识别类型为 APEXlang,并允许选择目标 Application ID。点击 Import Application 后,App Builder 会验证并编译 APEXlang 源文件;没有错误时才真正完成导入。

图 24-4 预览待导入应用,并为导入结果选择 Application ID。

APEXlang 编译通过一个带 Builder 认证的 ORDS 端点完成。因此,在某个工作区第一次导入 APEXlang 应用时,可能会提示把工作区解析方案启用为 ORDS REST 服务。此时点击 REST Enable Schema,向导即可继续。

图 24-5 如果工作区解析方案尚未启用 ORDS REST 服务,导入向导会提示启用。

验证检查:导入成功后,回到 App Builder 应用列表确认应用存在;运行应用,检查 Home、Employees 报表页和 Employee 表单页是否可打开。若导入报错,先修复 APEXlang 文件并重新验证,不要绕过验证结果。

24.5 阅读 APEXlang 语法#

APEXlang 的语法目标是让开发者和 AI 编码助手都容易阅读。组件类型后面跟组件标识符,再用括号包住属性列表;属性按功能分组;属性值通常不需要引号;多值属性使用数组;默认值通常可以省略。

pages/p00003-employee.apx 为例,页面 3 的别名是 EMPLOYEE,页面类型是模态对话框,外观组里能直接看到它使用 drawer 对话框模板。

page 3 (
    name: Employee
    alias: EMPLOYEE
    title: Employee
    appearance {
        pageMode: modalDialog
        dialogTemplate: @/drawer
        templateOptions: [
            #DEFAULT#
            js-dialog-class-t-Drawer--pullOutEnd
        ]
    }
    dialog {
        chained: false
    }
  ⋮
)

你可以把嵌套属性读作路径。例如 appearance.pageMode 的值是 modalDialogappearance.templateOptions 是一个数组,其中 #DEFAULT# 表示保留模板定义的默认类。

每类组件都有一个标识属性,多数情况下是 staticId,也可能是 namealias。APEXlang 用人类可读的标识符引用组件,避免直接引用内部数值 ID。编译器会在应用、页面或父组件作用域内检查唯一性。

page 3 (
 ⋮
   region employee (
     ⋮
    )
 ⋮
)

@ 开头的值表示对其他组件标识符的引用。下面的表单区域使用 @/blank-with-attributes 引用 Universal Theme 模板。

region employee (
    name: Employee
    type: form
    source {
        location: localDatabase
        tableName: EMP
    }
    layout {
        sequence: 10
        slot: contentBody
    }
    appearance {
        template: @/blank-with-attributes
        templateOptions: #DEFAULT#
    }
    edit {
        enabled: true
        allowedOperations: [
            add
            update
            delete
        ]
    }
)

同一页面内的页面项引用 employee 区域时使用 @employee。例如隐藏主键项 P3_EMPNO 同时在布局和来源中引用该表单区域。

pageItem P3_EMPNO (
    type: hidden
    layout {
        sequence: 10
        region: @employee
        slot: regionBody
    }
    source {
        formRegion: @employee
        column: EMPNO
        dataType: number
        queryOnly: true
        primaryKey: true
    }
    security {
        sessionStateProtection: checksumRequiredSessionLevel
    }
)

如果页面项引用的是共享组件 LOV,也使用 @。例如 P3_DEPTNO 是一个 selectList,通过 @dept-dname 引用共享 LOV。

pageItem P3_DEPTNO (
    type: selectList
    label {
        label: Deptno
        alignment: left
    }
    lov {
        type: sharedComponent
        lov: @dept-dname
    }
    layout {
        sequence: 80
        region: @employee
        slot: regionBody
        alignment: left
    }
    appearance {
        template: @/optional-floating
        templateOptions: #DEFAULT#
    }
    source {
        formRegion: @employee
        column: DEPTNO
        dataType: number
    }
)

@/ 前缀有两个常见场景:引用 Global Page 上的组件,或引用标准 Universal Theme 里的组件。普通页面引用全局页区域可写成 @/my-global-page-header;引用标准主题模板可写成 @/drawer@/blank-with-attributes@/optional-floating。如果模板是在当前应用中自定义的,则用普通 @my-slideshow 形式。

官方示例还在 Employees 页面增加一些特性:把 Interactive Report 区域切换成 Content Row,改用 SQL 查询,增加 HTML 帮助文本,定义内联 CSS,把 Raise Salary 按钮绑定到 PL/SQL 业务逻辑,并在 Page Load 动态动作里运行浏览器端 JavaScript。

图 24-6 修改后的 Employees 页面,用于观察 SQL、PL/SQL、JavaScript、HTML 和 CSS 在 APEXlang 中的表示方式。

多行代码用带语言标识的 fenced code block 表示。SQL 查询示例:

region employees (
    name: Employees
    type: themeTemplateComponent/contentRow
    source {
        location: localDatabase
        type: sqlQuery
        sqlQuery:
            ```sql
            select EMPNO,
                   ENAME,
                   DNAME
              from EMP_DEPT_V
            ```
    }
)

PL/SQL 业务逻辑使用 plsql 语言标识;页面加载时运行的浏览器端 JavaScript 使用 javascript-browser,服务器端 JavaScript 则会使用 javascript-mle

settings {
    plsqlCode:
        ```plsql
        update emp
           set sal = nvl(sal,0) + 100
         where empno = :EMPNO
         returning sal into :P2_NEW_SALARY;
        ```
    itemsToSubmit: EMPNO
    itemsToReturn: P2_NEW_SALARY
}
settings {
    jsCode:
        ```javascript-browser
        if ( !sessionStorage.getItem( "p2Welcomed" ) ) {
            const username = apex.item( "P2_APP_USER" ).getValue();
            apex.message.showPageSuccess( `Welcome back, ${username}!` );
            sessionStorage.setItem( "p2Welcomed", "true" );
        }
        ```
}

HTML 与 CSS 也使用同样的 fenced block 模式。

css {
    inline:
        ```css
        body {
            font-size: 1.2rem;
        }
        ```
}

help {
    helpText:
        ```html
        <p>
          Select an employee to edit it,
          or click (Raise Salary) to increase
          an employee's salary by 100.
        </p>
        ```
}

验证检查:在编辑器中打开 p00003-employee.apxp00002-employees.apx,分别找到页面、区域、页面项、LOV、按钮动作和代码块。每找到一个 APEXlang 片段,都回到 Page Designer 中定位对应组件,建立文本文件和可视化配置之间的映射。

24.6 结合 App Builder 与外部工具#

APEXlang 的导出和导入单位是整个应用。也就是说,如果你在外部工具中修改了 APEXlang,再导入回 App Builder,就会用外部版本替换 App Builder 当前版本;反过来,如果你在 App Builder 中继续修改,外部工具只有在下一次导出后才能看到最新变化。

团队规则:每次决定导入前,确认 App Builder 里的最新修改已经导出并合并到外部目录。不要让浏览器里的改动和 Git 里的改动长期分叉,否则导入时很容易覆盖别人的工作。

APEXlang 文件适合放入 Git。它们比 SQL 安装脚本更利于审查,因为页面、区域和组件的变更更接近开发者理解的结构。如果发现外部 APEXlang 和 App Builder 版本不同步,可以先从 App Builder 导出当前版本到临时目录,再用 diff/merge 工具与外部版本合并,最后验证并导入合并结果。

工作副本也可以参与这套流程。为某个功能或修复创建 working copy 后,可以导出该 working copy 的 APEXlang,在外部工具中修改,再导入回 working copy。APEX 会保留 working copy 与主应用的关系;准备合并时,流程与纯 App Builder 开发一致。

验证检查:每次导入都应先验证通过;导入后在 App Builder 中打开受影响页面,确认 working copy 或主应用的目标版本被更新;最后运行应用验证页面行为,而不仅仅确认导入成功消息。

24.7 在 Visual Studio Code 中编辑 APEXlang#

SQL Developer for VS Code 扩展可以在连接的 APEX 文件夹下显示应用。除了从 App Builder 导出,也可以在 VS Code 中对应用选择 Export...,指定父目录并点击 Apply。如果需要,扩展会创建以应用别名命名的子目录,例如 employees,并把 APEXlang 文件导出进去。

图 24-7 从 SQL Developer for VS Code 导出 Employees 应用的 APEXlang 文件。

导出面板的 SQL 标签页会显示底层 SQLcl 命令。-dir 参数指定父目录;如果省略,SQLcl 会把应用导出到当前目录下以应用别名命名的文件夹。

apex export -applicationid 104
            -dir '/Users/smuench/Downloads'
            -exptype APEXLANG
图 24-8 SQL 标签页展示对应的 SQLcl APEXlang 导出命令。

SQL Developer 扩展会为 .apx 文件提供上下文补全。例如光标停在页面 3 的 appearance.dialogTemplate 值中时,按 Ctrl + Space 可以看到当前页面模式允许使用的模板。示例中可把抽屉模板切换为 @/modal-dialog

图 24-9 在 .apx 文件中使用 APEXlang 代码补全。

切换模板后,原有 templateOptions 可能不再适用。此时 VS Code 的 Problems 面板会主动显示验证错误,例如提示某个模板选项值无效。

图 24-10 Problems 面板主动显示 APEXlang 验证错误,帮助在导入前修正。

修正 templateOptions 后,可以点击编辑器右上角工具栏中的“play”按钮,把当前 APEXlang 应用导入回 App Builder。导入需要连接到工作区解析方案;如果扩展还不知道该用哪个连接,会提示选择。

图 24-11 点击编辑器工具栏中的“play”按钮,把 APEXlang 应用导入 App Builder。

验证和导入成功后,VS Code 会显示确认消息。

图 24-12 VS Code 中的 APEXlang 应用导入成功确认。

最后回到浏览器刷新应用页面,确认 Employee 页面已经从模态抽屉变成模态对话框。这个运行时检查比“导入成功”消息更重要,因为它验证了变更确实到达目标页面。

图 24-13 刷新应用并验证 VS Code 导入后的页面结果。

24.8 使用 SQLcl 处理 APEXlang#

SQLcl 可以在命令行导出、导入和验证 APEXlang 应用。要导出,连接到工作区解析方案后运行 apex export,指定应用 ID 和导出类型。

SQL> apex export -applicationid 104 -exptype apexlang

要导入 APEXlang 应用,同样连接到解析方案,使用 apex import,并把 -input 指向目录或 zip 文件。

SQL> apex import -input /Users/smuench/Downloads/employees

导入会先验证。如果没有错误,SQLcl 才继续导入,并显示成功消息。

SQL> apex import -input /Users/smuench/Downloads/employees

Importing application ID: 104 into workspace: DIVEINTOAPEX

Import successful.

如果只想验证而不导入,使用 apex validate。输入同样可以是 APEXlang 目录或 zip 文件。

SQL> apex validate -input /Users/smuench/Downloads/employees

验证成功时会看到 Validation successful;否则 SQLcl 会列出错误和警告。官方示例故意在 pageItem P3_EMPNO 中制造三处拼写错误:@employee 改成 @employenumber 改成 numbeprimaryKey 改成 primaryKe

pageItem P3_EMPNO (
    type: hidden
    layout {
        sequence: 10
        region: @employe      <-- 1 --
        slot: regionBody
    }
    source {
        formRegion: @employee
        column: EMPNO
        dataType: numbe       <-- 2 --
        queryOnly: true
        primaryKe: true       <-- 3 --
    }
)

SQLcl 会报告文件、行、列、错误类型和错误说明,例如 REFERENCE_NOT_FOUNDINVALID_PROPERTY,以及合法值列表。需要保留错误输出时,可用 spool 写入日志文件。

SQL> spool employees_errors.log

SQL> apex validate -input /Users/smuench/Downloads/employees

APEXLang Compile Errors:

File: pages/p00003-employee.apx
Line: 117
Column: 12
Type: REFERENCE_NOT_FOUND
Error: Reference not found: @employe
 ⋮
SQL> spool off

APEXlang 的验证是元数据驱动的。编译器知道每类 APEX 组件允许哪些属性、子组件和取值约束。例如 Interactive Report 区域可以有列定义,Form 区域不一定有同样的子结构;Chart 区域可能有 zoomAndScroll,Classic Report 则没有。Page Designer 长期依赖同一套元数据动态显示属性,如今 APEXlang 编译器也使用这些信息严格验证文件。

.apex/apexlang.json 记录导出时使用的 APEX meta-metadata(MMD)版本。这个值影响编译器如何理解组件与属性,也影响 VS Code 的补全准确性。不要手工修改。

{
  "mmdVersion" : "26.1.0+3102"
}

导入 APEXlang 需要访问工作区解析方案,但验证可以不登录数据库连接。使用 sql /nolog 启动 SQLcl 后,仍可在 APEXlang 应用根目录运行 apex validate -input .。这对没有 SQLNet 连接的环境、CI 检查或 AI 智能体自检尤其有用。

$ sql /nolog
SQL> apex validate -input .
APEXLang Compile Errors:
File: pages/p10020-dashboard.apx
Line: 499
Column: 4
Type: DUPLICATED_COMPONENT
Error: Duplicated component within parent scope.

验证检查:apex validate 当成导入前的强制门禁。无论修改来自人、脚本还是 AI 智能体,都先验证;错误日志要定位到文件、行列和组件,再回到 App Builder 或编辑器中修正。

24.9 App Builder 内的 APEXlang#

即使团队主要仍在 App Builder 内开发,APEXlang 也能提升可读性。它会替代原先用于查看变更的可读 YAML 格式,使页面审查和 working copy 差异比较更贴近可导入的真实结构。

在 Page Designer 中处理任意页面时,可以通过新的 APEXlang View 菜单选项查看当前页面的 APEXlang 表示。

图 24-14 在 Page Designer 中打开当前页面的 APEXlang View。

这个视图适合快速搜索页面定义、理解组件层级,或把 App Builder 中看到的配置映射到 APEXlang 语法。

图 24-15 在 App Builder 中查看当前页面的 APEXlang 表示。

比较 working copy 和主应用时,也可以直接查看 APEXlang 差异。示例中,P3_MGR 页面项从 selectList 改成 textField,同时丢失了相关 lov 配置;这种差异用 APEXlang 很容易定位。

图 24-16 比较 working copy 与主应用时查看 APEXlang 差异,能直接看到组件属性变化。

验证检查:在 Page Designer 中打开 APEXlang View,搜索当前页面的一个区域和一个页面项;再创建或打开一个 working copy,比较它与主应用的差异,确认差异视图能帮助你判断变更是否符合预期。