简介WADE开发文档面向使用WADE框架进行企业级Java Web开发的中初级开发者系统梳理框架的基本功能与开发步骤帮助读者快速上手项目搭建与版本改造。文档围绕新建项目与开发改造两条主线展开新建项目部分讲解如何借助quickstart工具生成空项目并说明build.properties中项目名称、项目目录、WADELIB包路径、编译省份等配置项的含义开发改造部分按WADE2.52至2.59各版本列出建议改造点涉及JDK版本配置、输入框回车动作控制、集群缓存共享、客户端DataMap与DatasetList数据格式统一、页面命名调整等内容便于读者在框架升级时对照排查。资源包为1个doc文件大小约3.24MB结构紧凑适合作为日常开发查阅手册。目前已有974人学习可供需要理解WADE框架配置体系与升级改造思路的开发者参考。1. 拿到 WADE 开发文档先别急着敲代码这套老框架的落地路径第一次接触 WADE 开发文档的人十有八九会被ant newproject和一堆build.properties配置劝退。这不是一套现代脚手架它更像一份带着版本演进痕迹的工程手册从新建项目、增删改查、批量操作到 Excel 导入每一步都绑着 quickstart 模板和 wadelib 依赖。文档真正值钱的地方是它把「一个企业级 Java Web 项目从零搭起来」的完整动作拆成了可复制的步骤而不是停留在概念层。适合谁手上维护着 WADE 老系统、需要按框架规范补功能或做版本升级改造的开发者也适合想理解早期 JSP/Wicket 风格组件化开发的人。下面按「搭项目 → 写业务 → 避坑 → 进阶」的顺序把这份文档拆成能照着复现的实操笔记。2. 用 quickstart 起一个空项目build.properties 的六个关键参数WADE 的新项目不是靠 IDE 向导生成的而是靠 quickstart 提供的ant newproject任务在 build 目录下产出一个空壳。这个设计的好处是项目结构、依赖路径、编译脚本全部由模板统一坏处是任何一个路径参数写错编译阶段就会以「找不到 wadelib」或「类不存在」的形式翻车。所以第一步不是写业务而是把build/build.properties吃透。2.1 先理解目录约定再改配置文档里给的配置片段核心是让 build.xml 知道四件事项目叫什么、源码在哪、wadelib 在哪、编译成什么 Java 版本。我一般先把整个 quickstart 解压到一个工作目录确认wadelib和应用目录处于同一级这样WADELIB_HOME ../../../wadelib/wadelib这类相对路径才成立。如果两者不在同一级就必须去改build/build.xml里引入build_wadelib.xml的那行路径否则后面所有 ant 任务都会连锁失败。# build/build.properties 关键项 # 编译时的省代码开发用 common打包部署时换成对应省份 PROVINCE_NAME common # 项目名称最终 jar/war 都以它命名 PROJECT_NAME wade-quickstart # 项目根目录.. 表示当前 build 目录的上级 PROJECT_HOME .. # wadelib 包路径决定依赖能否被找到 WADELIB_HOME ../../../wadelib/wadelib # 各目录映射 build${PROJECT_HOME}/build config${PROJECT_HOME}/etc source${PROJECT_HOME}/src classes${PROJECT_HOME}/web/WEB-INF/classes lib${WADELIB_HOME}/web/WEB-INF/lib web${PROJECT_HOME}/web APPLANGGB2312这段配置里PROVINCE_NAME是最容易被忽略的一个。开发阶段用common走公共代码等到测试打包发布时才切到具体省份这意味着同一份源码在不同阶段编译出的产物可能不同。APPLANGGB2312也提醒我们这是偏早期的编码约定新写页面时如果混入 UTF-8 资源容易出现中文乱码排查时先看这里。2.2 改名三组变量别只改一组新建项目时文档要求配置OLD_NAME1/2/3和NEW_NAME1/2/3分别对应小写、首字母大写、全大写三种形态。很多人只改了小写结果生成的项目里包名、类名、常量名大小写不一致编译能过但运行时报类找不到。我的做法是先把新项目名定下来比如custmgr然后严格按三种形态填# 原系统名称quickstart 模板 OLD_NAME1 quickstart OLD_NAME2 Quickstart OLD_NAME3 QUICKSTART # 新系统名称三种大小写形态必须一致对应 NEW_NAME1 custmgr NEW_NAME2 Custmgr NEW_NAME3 CUSTMGR填完后执行build/ant newproject它会在 build 目录下生成新项目骨架。接着按文档第 3 步删掉page.application和sidebar.html里 quickstart 遗留的页面名与菜单配置——这一步不做新项目启动后侧边栏会挂着一堆示例菜单点进去全是模板页面很容易误以为项目没生成成功。2.3 编译命令分两种场景开发期用ant all它走默认目标编译快、不打包测试和发布用ant allwar它需要额外引入ant-contrib.jar否则任务定义会报错。我一般把这两个命令写进自己的备忘日常改代码只跑ant all要出 war 包前先确认ant-contrib.jar已放进 ant 的 lib 目录再跑ant allwar。如果 allwar 报「taskdef class net.sf.antcontrib... not found」基本就是缺这个 jar不用去翻业务代码。提示PROJECT_HOME ..是相对 build 目录的如果你在别的目录执行 ant相对路径会解析错位建议始终在 build 目录下执行命令。3. 增删改查四件套从 HTML 组件到 Bean 的完整链路WADE 的业务写法是「HTML 组件声明 → 页面 Java 事件 → Bean 数据方法」三层。文档用 VIP 客户资料这个示例贯穿了新增、修改、删除、查看四个动作这套链路吃透后批量操作和导入都是它的变体。下面按动作拆重点讲参数和容易写错的地方。3.1 新增PageRedirect 跳转与序列值获取新增的入口是一个wade:PageRedirect组件点击后跳到编辑页。页面事件里拿到PageData调用 Bean 的createVipcust再把生成的VIP_ID通过setTransfer传给下一页最后用redirectToMsg给出操作提示。// 页面事件新增 VIP 客户资料 public void createVipcust(IRequestCycle cycle) throws Exception { PageData pd getPageData(); ExamplesBean bean new ExamplesBean(); // 把页面数据交给 Bean 处理返回新生成的 VIP_ID String vip_id bean.createVipcust(pd, pd.getData()); // 传递参数到下页键名 VIP_ID pd.setTransfer(VIP_ID, vip_id); // 操作提示两个按钮分别跳回新增页和详情页 redirectToMsg(VIP 客户资料新增成功, new String[] { getPageName(), getPageName() }, new String[] { null, queryVipcust }, new String[] { 继续新增, 查看详情 }); }Bean 里做的是补全字段再入库dao.getSequence(SEQ_VIP_ID)取序列值pd.getContext()拿登录员工、部门、业务区、地州这些上下文common.getSysTime()设时间最后dao.insert写入TF_F_CUST_VIP。这里有个细节文档特别注明所有数据以文本方式进数据集时间也直接写成yyyy-MM-dd HH:mm:ss字符串不要自作聪明转成 Date 对象否则和框架的数据转换格式对不上。3.2 修改save 和 update 的区别是血泪经验修改动作最容易翻车的地方不是跳转而是save与update的语义差异。文档写得很直白update会把 data 里所有字段更新表里原有但 data 没带的字段会被清空save会先查出原记录再覆盖。所以只改部分字段时必须用 save除非你确认 data 已包含全部字段才用 update 换效率。// Bean按主键和注销标记判断能否修改 public boolean updateVipcust(PageData pd, IData data, String[] values) throws Exception { ExamplesDAO dao new ExamplesDAO(pd); // 指定 VIP_ID 和 REMOVE_TAG 作为更新条件避免并发覆盖 return dao.save(TF_F_CUST_VIP, data, new String[] { VIP_ID, REMOVE_TAG }, values); }页面事件里先取VIP_ID和REMOVE_TAG调用 Bean 返回 false 时用common.error提示「信息已被操作过」。这个判断解决的是并发场景同一页面被多人打开或有人在你更新前已删除/改状态靠关键列匹配就能拦住脏写。save后面那组new String[]{VIP_ID,REMOVE_TAG}是自定义更新条件表没有主键或有复合主键时都靠它指定。3.3 删除与查看绑定列查询的两种写法删除用wade:PageSubmit组件listener指向页面事件onclick里queryBox做二次确认。Bean 里构造DataMap放VIP_ID和REMOVE_TAG再dao.delete并指定绑定列。查看则是dao.queryByPK默认按主键查单条需要按自定义列查时同样追加new String[]{VIP_ID,REMOVE_TAG}。// Bean删除指定列作为删除条件 public boolean deleteVipcust(PageData pd, String vip_id, String remove_tag) throws Exception { ExamplesDAO dao new ExamplesDAO(pd); IData data new DataMap(); data.put(VIP_ID, vip_id); data.put(REMOVE_TAG, remove_tag); return dao.delete(TF_F_CUST_VIP, data, new String[] { VIP_ID, REMOVE_TAG }); }查看的页面事件里有个防御写法值得抄queryVipcust返回 null 时common.error(您要查看的记录已不存在)避免详情页拿到空对象后渲染报错。这套「先判空再 setInfo」的习惯在批量场景里同样适用。3.4 批量操作DatasetList 与 executeBatch 的取舍批量新增把单条逻辑包进for循环逐条补字段后统一dao.insert(TF_F_CUST_VIP, dataset)批量删除则把选中的VIP_ID数组拼成DatasetList再dao.delete。这两类能覆盖大部分场景但遇到「通用 insert/update/delete 做不了」的复杂批量就得走executeBatch手写 SQL 和Parameter绑定。// 自定义批量按数组构造参数绑定逐条执行 public void batchUpdateVipcusts(IData param) throws Exception { String[] vip_ids common.getValues(param.get(VIP_ID)); String[] cust_names common.getValues(param.get(CUST_NAME)); String[] cust_types common.getValues(param.get(CUST_TYPE)); String sql update TF_F_VIP_CUST set CUST_NAME ?, CUST_TYPE ? where VIP_ID ?; Parameter[] params new Parameter[vip_ids.length]; for (int i 0; i params.length; i) { params[i] new Parameter(); params[i].add(cust_names[i]); params[i].add(cust_types[i]); params[i].add(vip_ids[i]); } executeBatch(sql, params); }参数绑定顺序必须和 SQL 里?的顺序严格一致这是executeBatch最容易错的地方。另外文档示例里那条 SQL 写成了set CUST_NAME ? and CUST_TYPE ?实际执行会语法报错正确写法是逗号分隔抄的时候要顺手改掉。4. Excel 导入配置、模板与失败数据回吐导入是文档里步骤最多的一块也是最容易「配了但跑不通」的地方。它依赖三份东西导入配置import/xxx.xml定义列格式与校验失败导出配置export/xxximport.xml保留原始数据并加IMPORT_ERROR列页面表单负责上传文件流。任何一环缺失后台都拿不到数据。4.1 导入配置nullable、maxsize、type 三个字段定校验导入 XML 的header下每个cell对应一列name是字段名desc是表头文字type决定类型1 字符串、2 数字、3 时间nullable控制必填maxsize限长度。这些配置直接决定导入时的格式校验配错就会出现「明明填了却提示必填」或「长度超了没拦住」。!-- import/examples/basic/VipcustList.xml -- sheet descVIP 客户资料 header isshowtrue height300 cell nameUSECUST_ID desc客户标识 type2 align1 width4000 nullableno maxsize16/ cell nameUSECUST_NAME desc客户名称 type1 align1 width6000 nullableno maxsize100/ /header /sheet失败导出配置则相反所有列建议统一设type1并额外加一列IMPORT_ERROR。原因是失败数据要和用户原始输入保持一致如果设了type3加format导出时会把2007-03-01转成2007-03-01 00:00原始形式就丢了用户拿着失败文件改都无从下手。4.2 页面与事件enctype 和标准校验方法缺一不可页面表单必须设enctypemultipart/form-data文件域用input typefile nameFILE_PATH/否则服务器取不到文件流。文档还强调必须加上verifyAll、confirmForm、confirmInfo这类标准验证方法不加的话后台拿不到数据——这是框架的约定不是可选优化。// 页面事件导入并回吐失败数据 public void importVipcusts(IRequestCycle cycle) throws Exception { PageData pd getPageData(); // 解析上传的 Excel返回数据集数组取第一个 IDataset dataset ExcelParser.importExcel(pd, examples/basic/VipcustList.xml, pd.getUploadFile(FILE_PATH).getStream())[0]; ExamplesBean bean new ExamplesBean(); IDataset failds bean.importVipcusts(pd, dataset); if (failds.size() ! 0) { // 失败数据生成 Excel 供下载 String file_id ExcelParser.writeExcelByImport(pd, examples/basic/VipcustImport.xml, new IDataset[] { failds }); redirectToMsgByPopup(共导入 dataset.size() 条成功 (dataset.size() - failds.size()) 条失败 failds.size() 条); } else { redirectToMsgByPopup(批量导入成功); } }Bean 里用成功集和失败集分开收集失败记录补IMPORT_RESULTfalse和IMPORT_ERROR说明原因最后只把失败集写回 Excel。模板文件放templet/xxx/xxx.xls页面用attach?file_name...file_path...提供下载file_name是下载显示名file_path是模板真实路径两者别写反。5. 版本升级改造与避坑那些文档里一句话带过的坑WADE 的「开发改造」章节按版本列了建议改造点读起来像变更日志但每一条背后都是实际会踩的坑。下面挑几条高频的按「现象 → 原因 → 解决」整理再补两个通用排查经验。5.1 常见问题排查现象底部页面报错控制台提示找不到 Buttom。原因WADE2.52 起要求把web/Buttom.html、view/Buttom.java改为Bottom.html、Bottom.java拼写从 Buttom 纠正为 Bottom但page.application里的配置没同步改。解决三处一起改页面文件、Java 类、page.application配置缺一处就报错。现象输入框回车触发了意料之外的动作。原因旧写法靠全局脚本拦截回车组件多了就互相干扰。解决按 WADE2.56 的建议给输入框组件加enterActionalert(xxx)参数把回车行为收敛到组件自身别再写全局拦截。现象集群部署后缓存各节点不一致。原因旧缓存无法跨节点共享。解决按 WADE2.55 升级 quickstart 到 2.35 及以上参照etc/common/ehcache.xml改造通常只需把 quickstart 的 ehcache 增量合并进自定义 cache 配置不用整份替换。现象页面里document.getElementById(name).disabled true对日期组件无效。原因日期组件由文本框和图标两个 DOM 组成直接操作只隐藏了文本框。解决按 WADE2.5 规范改用getElement(name)、hidden(name|obj)、disabled(name|obj)这些框架方法它们知道组件的真实结构。现象linkSubmit在某些浏览器跳转失败。原因该方法存在浏览器兼容问题。解决改用wade:PageSubmit组件替代这也是 WADE2.5 明确建议的方向。5.2 两个通用排查习惯第一编译报「找不到 wadelib」时先看WADELIB_HOME的相对路径是否和实际目录层级一致再看build.xml里build_wadelib.xml的引入路径这两处任一错位都会连锁报错。第二JDK 版本问题按 WADE2.59 的建议在build.properties里显式配JDK_VERSION1.4|1.5这样既控制编译目标也能让应用部署到 1.4 运行环境别指望默认值。注意page.application和sidebar.html里的模板残留配置新建项目后一定要清干净否则示例菜单会混进正式系统排查业务问题时容易被误导。6. 进阶用 initScript 和客户端 DatasetList 收掉历史包袱文档里 WADE2.5 和 WADE2.53 两条改造点放到今天看依然有实用价值因为它们解决的是「老代码越写越乱」的根问题。WADE2.5 去掉了scriptinit();/script这种初始脚本调用改为页面载入时自动调用按页面名生成的默认方法比如页面examples.basic.VipcustEdit对应initVipcustEidt()也可以用wade:Head initScript参数自定义方法名没写初始脚本就自动忽略。这个机制的好处是初始逻辑和页面绑定不再散落在 HTML 里。我一般会在页面类里显式写一个空的 init 方法占位需要初始化下拉框、默认值时再往里填避免「以为会自动调用结果没写」的玄学问题。WADE2.53 则统一了客户端和服务端的数据格式客户端直接用DataMap、DatasetList服务端通过setAjaxDataset把数据传到前端前端拿到DatasetList直接操作。相比过去零散的 ajax 局部刷新这种方式在数据量和灵活性上都更稳。改造时的判断标准很简单单一或少量局部刷新优先换成setAjaxDataset整页跳转仍走PageRedirect。下面这张表是我改造时用来对照的场景旧写法建议写法原因页面初始逻辑scriptinit();/script页面名生成的 init 方法或initScript写法规范减少代码量DOM 取值/禁用document.getElementByIdgetElement/disabled兼容复合组件局部刷新零散 ajaxsetAjaxDatasetDatasetList数据格式统一性能更好提交跳转linkSubmitwade:PageSubmit规避浏览器兼容问题验证改造是否生效我的习惯是每次改完先跑ant all编译再启动应用点一遍对应页面重点看控制台有没有「方法未定义」或「组件找不到」的告警。从那以后我每次接手 WADE 老项目都会先把build.properties的路径、page.application的残留配置、以及版本改造清单这三样过一遍再动业务代码能省掉大半返工。希望帮到你。本文还有配套的精品资源点击获取