简介:《Domino开发指南精华》是一本面向企业级Java开发者的技术指南,聚焦Lotus Domino平台的Java集成开发实践,解决邮件处理、富文本操作、URL头信息获取、数据库交互、文档与视图管理及代理自动化等核心协作系统构建难题。资源为单文件PDF格式,共1个文件,大小3.82MB,内容完整覆盖lotus.domino包的典型API调用与工程化应用,适合具备Java基础、需快速落地Domino定制开发的中高级开发者。已有1930人学习下载,表明其在遗留系统维护与协同办公平台二次开发领域具有持续参考价值。书中提供大量可直接复用的代码示例,强调企业场景下的实操路径——如通过Notes API实现邮件自动归档、富文本字段动态渲染、视图数据分页加载及定时代理触发业务流程,同时附有版权与使用规范说明,便于合规引入生产环境。
1. Domino开发指南精华:不是Java框架,而是Notes/Domino平台上的原生开发实战手册
你手头有一套老系统——IBM Notes客户端+Domino服务器架构的内部审批、文档协同或知识库系统,现在要加一个新功能:自动归档邮件到指定数据库、按关键词触发工作流、把Notes表单数据导出成Excel并带格式。这时候翻遍Maven仓库、Spring Boot Starter列表、甚至GitHub Trending,都找不到“Domino Java API”的starter依赖——因为Domino开发根本不在现代Java生态里跑。它用的是自己的一套运行时:Notes.jar + NCSO.jar + lotus.domino.* 包体系,运行在Domino服务器JVM里,调用的是C层Notes C API封装。这份《Domino开发指南精华》不是教你怎么用Spring整合Domino,而是告诉你:怎么在Notes Designer里写Java代理、怎么用LotusScript做前端逻辑、怎么用DXL导出/导入设计元素、怎么绕过Session对象线程安全陷阱、怎么让Java代理在服务器端真正拿到FullAdmin权限——全是血泪经验攒出来的硬核操作。适合正在维护/升级存量Domino应用的后端工程师、企业IT运维、以及被临时拉来救火的Java开发者。它不讲理论,只讲“改完哪几行代码,重启服务器后能立刻看到效果”。
2. Domino开发环境搭建与核心API选型:为什么必须用Notes 9.0.1 FP10 + Domino 10.0.1而非最新版
2.1 开发机环境:Notes客户端 ≠ IDE,Designer才是真编辑器
Domino开发不能靠IntelliJ或Eclipse写完Java再部署——所有Java代理(Agent)、Servlet、Web服务都必须在Notes Designer中编写、编译、保存到NSF数据库内。这意味着你的开发机必须装完整Notes客户端(含Designer组件),而非仅装Domino Administrator。常见错误是只装Admin Client,结果打开.nsf文件时看不到“Agents”视图,也点不了“Edit Agent”按钮。
安装顺序严格如下:
- 先装IBM Notes 9.0.1 Fix Pack 10(官方支持Java 8u181,兼容性最稳);
- 再装Domino 10.0.1 Server(注意:不是11.x或12.x!11+版本对旧Java代理兼容性差,且NCSO.jar签名机制变更导致Classloader报错);
- 最后配置Notes客户端连接本地Domino服务器(Server Document中启用“Allow HTTP clients”和“Allow Java agents”)。
提示:不要试图用Notes 12.x + Domino 12.x组合。实测Java代理中调用
session.getDatabase("", "log.nsf")会返回null,原因在于12.x默认禁用Legacy Java Security Manager,而大量老代码依赖其权限校验逻辑。
2.2 核心Jar包定位与classpath陷阱
Domino Java开发依赖三个关键jar,它们不在CLASSPATH环境变量里,也不在项目lib目录下,而是由Notes JVM在启动时动态加载:
| Jar包名 | 来源路径 | 关键用途 | 版本敏感点 |
|---|---|---|---|
notes.jar | C:\Program Files\IBM\Notes\jvm\lib\ext\notes.jar | 提供lotus.domino.*主类,如Session、Database、Document | 必须与Notes客户端版本严格一致,混用9.0.1客户端+10.0.1服务器时,优先取Notes客户端路径下的jar |
ncso.jar | C:\Program Files\IBM\Domino\jvm\lib\ext\ncso.jar | 提供com.ibm.domino.napi.*底层C API封装,用于跨平台文件操作、DXL解析 | Domino服务器版本决定,若代理需调用NAPI(如读取NSF文件头),必须用服务器对应版本 |
domino.jar | C:\Program Files\IBM\Domino\jvm\lib\ext\domino.jar | Domino内置Servlet容器支持类,用于开发HTTP Servlet | 仅在开发Web服务时需要,但必须与httpstack配置匹配(见4.2节) |
验证是否加载成功:在Java代理中写一行System.out.println("Loaded: " + Session.class.getPackage().getImplementationVersion());,运行后查看Domino Console输出。若报NoClassDefFoundError: lotus/domino/Session,90%是jar路径错了或Notes客户端未以管理员身份运行(Windows UAC拦截了jvm.dll加载)。
2.3 Java vs LotusScript:什么场景必须用Java,什么场景死守LotusScript
这不是语言优劣问题,而是执行上下文决定的硬约束:
必须用Java的场景:
- 需调用外部HTTP API(如调用企业微信机器人接口)→ LotusScript的
XMLHTTP对象不支持HTTPS证书校验,Java可配SSLContext; - 需处理大附件(>10MB)→ LotusScript内存模型易OOM,Java可用
InputStream分块读取; - 需集成JDBC(如查SQL Server审计日志)→ LotusScript无原生JDBC驱动,Java可直接
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver")。
- 需调用外部HTTP API(如调用企业微信机器人接口)→ LotusScript的
必须用LotusScript的场景:
- 操作RichText字段(如插入图片、设置段落样式)→ Java API对
RichTextItem的格式控制极弱,appendText()后无法设字体,而LotusScript的AppendRTItem()支持完整RTF指令; - 前端表单事件(如
QuerySave、PostRecalc)→ Notes客户端只识别LotusScript,Java无法挂载到表单事件链; - 调用COM组件(如Word转PDF)→ Windows平台下LotusScript可
CreateObject("Word.Application"),Java需额外JNI桥接,稳定性差。
- 操作RichText字段(如插入图片、设置段落样式)→ Java API对
实操建议:混合开发——前端表单用LotusScript做UI交互,后台批处理用Java代理,通过@Command([ToolsRunMacro])或session.evaluate("@Command(...)触发。
3. Java代理开发全流程:从创建到调试的六步闭环
3.1 创建Java代理:不是新建Java Class,而是Designer里的特殊节点
在Notes Designer中打开目标NSF → 左侧导航栏右键“Agents” → “New Agent” → 在弹窗中:
- Name:
ArchiveMailToDB(命名规范:动词+名词,避免空格和中文); - Shared: ✅ 勾选(否则仅当前用户可见);
- Target: 选“None”(纯后台运行,不绑定文档);
- Trigger: 选“On event” → “After new mail has arrived”(邮件归档场景);
- Runtime: 选“Java”(关键!不是LotusScript);
- 点击OK后,Designer自动打开Java编辑器,初始模板已含
import lotus.domino.*;和public class JavaAgent implements AgentBase结构——这是Domino强制约定,不可删改。
3.2 核心代码骨架:Session复用、Database缓存、异常兜底三原则
以下是最小可行代理代码(已去业务逻辑,保留Domino开发铁律):
import lotus.domino.*; public class ArchiveMailToDB extends AgentBase { private Session session; private Database targetDB; public void NotesMain() { try { // 【原则1】Session必须从AgentBase.getSession()获取,禁止new Session() session = getSession(); // 【原则2】Database对象必须缓存,避免循环中反复openDatabase() targetDB = session.getDatabase("", "archive.nsf"); if (!targetDB.isOpen()) { throw new RuntimeException("Target DB archive.nsf not found or closed"); } // 主逻辑入口(此处省略邮件扫描与归档代码) processIncomingMail(); } catch (Exception e) { // 【原则3】所有异常必须捕获并记录到Domino日志,不能抛出 System.out.println("ERROR in ArchiveMailToDB: " + e.getMessage()); e.printStackTrace(); // 此行输出到Domino Console,非Notes客户端日志 } } private void processIncomingMail() throws Exception { // 示例:获取收件箱未读邮件 Database mailDB = session.getDatabase("", "mail\\username.nsf"); View inbox = mailDB.getView("($Inbox)"); DocumentCollection unreads = inbox.getAllUnreadEntries(); Document doc = unreads.getFirstDocument(); while (doc != null) { // 归档逻辑... doc = unreads.getNextDocument(doc); } } }参数说明与逻辑说明:
getSession()返回的是当前代理执行上下文的Session对象,它已预置了FullAdmin权限(前提是代理属性中勾选“Run as Web user”并配置了有效ID),比手动Session s = new Session()安全得多;session.getDatabase("", "archive.nsf")中第一个参数为空字符串表示本地服务器,若需远程库,填服务器名如"CN=Domino01/O=Org";unreads.getFirstDocument()返回的是Document对象,但该对象生命周期绑定于DocumentCollection,循环中必须用getNextDocument(doc)推进,不能用for(Document d : collection)——后者在Domino Java API中不支持增强for循环;System.out.println()输出到Domino服务器Console(console.log命令可见),而session.writeLog()才写入log.nsf,后者性能开销大,仅用于关键事务日志。
3.3 调试Java代理:Console日志 + Domino Debug模式 + 远程JPDA三重验证
Domino Java代理无法像普通Java程序那样F5调试,必须用组合手段:
Console日志法(最快):
- 在代理中插入
System.out.println("STEP 1: Start processing");; - 启动Domino服务器后,在命令行输入
tell http restart(若代理为Web触发)或load domino(若为邮件触发); - 实时监控Console:
show log或tell amgr run "ArchiveMailToDB"手动触发,观察输出。
- 在代理中插入
Domino Debug模式(准确定位):
- 修改Domino服务器
notes.ini,添加:JavaDebugPort=8000 JavaDebug=true - 重启Domino,此时JVM启动时会监听8000端口;
- 在IntelliJ中配置Remote JVM Debug,Host=localhost,Port=8000,无需附加任何jar,直接Attach;
- 在Java代理代码中打断点(如
processIncomingMail()第一行),触发代理后IDE会自动停住。
- 修改Domino服务器
远程JPDA验证(生产环境兜底):
- 若服务器在Linux且无法直连,用
ssh -L 8000:localhost:8000 user@domino-server建立端口映射; - IntelliJ Debug配置中Host填
localhost,Port填8000,即可远程调试。
- 若服务器在Linux且无法直连,用
注意:Debug模式下代理执行会变慢(JVM字节码插桩),切勿在生产环境长期开启JavaDebug=true,仅用于问题定位。
4. 常见问题排查:Java代理不执行、Session权限不足、DXL导入失败三大黑匣子
4.1 现象:Java代理在Designer中点击“Run”无反应,Console无任何输出
原因:代理属性中未启用“Enabled”或“Shared Agent”权限未授权。
- 排查步骤:
- 右键代理 → “Properties” → “Security”页签 → 确认“Enabled”复选框✅;
- 在“Runtime”页签 → 检查“Run on behalf of”是否设为“Server”(非“User”);
- 打开Domino Administrator → 服务器文档 → “Security”页签 → “Sign and run unrestricted methods and operations”中,确认代理签名者(如
CN=Dev/O=Org)在“Signers”列表中。
解决:用服务器ID(如server.id)重新签名代理:Designer中右键代理 → “Sign” → 选择服务器ID文件。
4.2 现象:session.getDatabase("", "xxx.nsf")返回null,但NSF明明存在
原因:NSF路径错误或数据库未在Domino目录注册。
- 排查步骤:
- 在Domino Console执行
show database,确认xxx.nsf是否在列表中; - 若不在,检查NSF是否放在
data\目录下(非data\subdir\),Domino默认只扫描data\一级; - 若在子目录,需在服务器文档“Basics”页签中添加路径到“Database directories”。
解决:将NSF移至data\根目录,或修改服务器配置使其识别子目录。
- 在Domino Console执行
4.3 现象:DXL导入时抛DOMException: Invalid character,但XML文件用浏览器打开正常
原因:DXL文件含BOM(Byte Order Mark)或编码声明与实际不符。
- 排查步骤:
- 用Notepad++打开DXL文件 → “编码”菜单 → 查看是否为UTF-8 with BOM;
- 检查DXL首行
<?xml version="1.0" encoding="UTF-8"?>中的encoding值是否与文件真实编码一致。
解决:
- Notepad++中“编码” → “转为UTF-8无BOM格式”;
- 或用Java代码预处理:
String dxl = Files.readString(Paths.get("form.dxl"), StandardCharsets.UTF_8); dxl = dxl.replaceFirst("^\\ufeff", ""); // 移除BOM Database db = session.getDatabase("", "target.nsf"); db.importDxl(dxl); // 此时不再报Invalid character
4.4 现象:Java代理中调用session.evaluate("@DbLookup(...)")返回空数组,但Formula在Notes客户端中正常
原因:@DbLookup在Java代理中运行于服务器上下文,@ThisDatabase指向代理所在NSF,而非调用者NSF。
解决:显式指定数据库路径:
String[] result = session.evaluate( "@DbLookup(\"\"; \"server1!!app\\lookup.nsf\"; \"ViewName\"; \"key\"; \"Column\")" );其中"server1!!app\\lookup.nsf"格式为"服务器名!!数据库路径",双反斜杠是Java字符串转义要求。
5. Domino Web服务开发:从Agent到Servlet的迁移路径与HTTP Header陷阱
5.1 为什么Java Agent不够用?当需要RESTful接口时
Java Agent本质是定时/事件触发的批处理,无法响应HTTP GET/POST请求。若需提供API给前端Vue应用调用,必须转向Domino Servlet。但注意:Domino 10.0.1默认Servlet容器是httpstack(非Tomcat),其Servlet API版本为2.5,不支持@WebServlet注解,必须手动注册。
5.2 创建Servlet:web.xml + Java类 + Domino服务器配置三件套
步骤1:编写Servlet类(必须继承javax.servlet.http.HttpServlet):
package com.example.domino; import javax.servlet.http.*; import javax.servlet.*; import java.io.*; public class MailApiServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.print("{\"status\":\"ok\",\"count\":123}"); out.flush(); } }步骤2:配置web.xml(放在NSF的WebContent\WEB-INF\web.xml):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <servlet> <servlet-name>MailApi</servlet-name> <servlet-class>com.example.domino.MailApiServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MailApi</servlet-name> <url-pattern>/api/mail</url-pattern> </servlet-mapping> </web-app>步骤3:Domino服务器启用HTTP Stack:
- 服务器文档 → “Internet Protocols” → “HTTP”页签 → 勾选“Enable HTTP protocol”;
- 在“Ports” → “Internet Ports”中确认HTTP端口(默认80);
- 关键:在
notes.ini中添加HTTPEnableServlets=1,否则Servlet不加载。
5.3 HTTP Header与CORS:Domino默认禁用跨域,前端拿不到响应
Domino Servlet默认不返回Access-Control-Allow-Origin头,Chrome会拦截响应。不能在Servlet中resp.setHeader("Access-Control-Allow-Origin", "*")——httpstack会忽略。
正确解法:在NSF的WebConfig文档中配置:
- 创建一个Design Element → “Web Configuration”;
- 在“HTTP Response Headers”区域添加:
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,POST,OPTIONS Access-Control-Allow-Headers: Content-Type - 保存后,所有该NSF下的Servlet自动获得这些Header。
提示:生产环境请将
*替换为具体域名(如https://vue-app.example.com),避免安全风险。
6. 生产环境避坑清单:权限、日志、升级三道生死线
6.1 权限链断裂:从代理签名到服务器ACL的七层校验
Domino Java代理执行失败,80%源于权限链断裂。这条链共7环,缺一不可:
| 层级 | 校验点 | 失败现象 | 检查命令/路径 |
|---|---|---|---|
| 1. 代理签名 | 代理属性中“Signer”字段是否为有效ID | Console报“Security exception” | Designer中右键代理 → Properties → Security |
| 2. 签名者ACL | 签名者在目标NSF的ACL中权限≥Manager | session.getDatabase()返回null | NSF右键 → Properties → Access Control → 查签名者权限 |
| 3. 服务器ACL | 签名者在names.nsf中被授予“Server Access” | 代理完全不触发 | names.nsf→ “People”视图 → 找签名者 → 检查“Server Access”字段 |
| 4. 服务器文档 | 服务器文档中“Security”页签启用“Run unrestricted agents” | Console报“Operation not allowed” | Domino Administrator → 服务器文档 → Security页签 |
| 5. notes.ini | notes.ini中JAVA_POLICY_ENABLED=1且JAVA_SECURITY_POLICY_FILE指向有效policy文件 | Java安全异常 | show config JAVA_POLICY_ENABLED |
| 6. JVM参数 | -Djava.security.manager未被意外启用 | 代理启动即退出 | show config JavaOptions,确认无-Djava.security.manager |
| 7. 操作系统 | Windows服务以“Local System”而非普通用户运行 | 文件IO失败 | Windows服务管理器 → Domino服务 → “Log On”页签 → 确认为“Local System” |
血泪经验:每次升级Domino后,必须重走这7步。曾因第5步JAVA_POLICY_ENABLED被自动设为0,导致所有Java代理静默失败,排查耗时3天。
6.2 日志黑洞:Console、log.nsf、stdout三地日志的取舍策略
Domino日志分散在三处,新手常混淆:
- Console日志(
show log):实时、轻量、仅存内存,重启即清空。只用于调试阶段快速验证逻辑通路; - log.nsf日志(
session.writeLog()):持久化、带时间戳、可归档,但I/O开销大。仅用于记录关键事务(如“归档完成123封邮件”); - stdout重定向文件(
notes.ini中CONSOLE_LOG_FILE=path\console.log):全量、无过滤、占磁盘。仅在疑难问题时开启,日常关闭。
我的习惯:
- 开发期:
System.out.println()打点 →show log盯屏; - 上线前:删掉所有
System.out,改用session.writeLog("INFO: Archive started"); - 线上告警:配置
log.nsf的“Log Events”视图,设置“Error”级别邮件通知。
6.3 升级雷区:Domino 10.0.1 → 11.0.1的四个不兼容点
2023年有客户强行升级到11.0.1,结果所有Java代理崩溃。实测四大不兼容:
| 问题点 | Domino 10.0.1行为 | Domino 11.0.1变化 | 应对方案 |
|---|---|---|---|
Session.createDateTime() | 返回DateTime对象,timeValue方法正常 | 返回DateTime但timeValue抛NullPointerException | 改用toJavaDate().getTime() |
Document.getItemValueString("Field") | 字段不存在时返回空字符串 | 字段不存在时抛NoSuchElementException | 调用前先doc.hasItem("Field") |
Database.openByReplicaID() | 支持""作为服务器名参数 | 必须传真实服务器名,""被拒绝 | 用session.getServerName()动态获取 |
AgentBase.getSession() | 返回FullAdmin Session | 返回受限Session,需显式session.asSigner() | 在代理开头加session = session.asSigner() |
从那以后我每次升级Domino前,都强制走一遍这四条兼容性测试用例,哪怕只是小版本号变动。
希望帮到你。
本文还有配套的精品资源,点击获取