2008年02月15日

一、概述

javascript函数劫持,也就是老外提到的javascript hijacking技术。最早还是和剑心同学讨论问题时偶然看到的一段代码,大概这样写的:

window.alert = function(s) {};

觉得这种用法很巧妙新颖,和API Hook异曲同工,索性称之为javascript function hook,也就是函数劫持。通过替换js函数的实现来达到劫持这个函数调用的目的,一个完整的hook alert函数例子如下:

<!–1.htm–>
<script type="text/javascript">
<!–
var _alert = alert;
window.alert = function(s) {
    if (confirm("是否要弹框框,内容是\"" + s + "\"?")) {
        _alert(s);
    }
}
//–>
</script>
<html>
<body>
<input type="button" onclick="javascript: alert(‘Hello World!’)" value="test" />
</body>
</html>

搞过API Hook的同学们看到这个代码一定会心的一笑,先保存原函数实现,然后替换为我们自己的函数实现,添加我们自己的处理逻辑后最终再调用原来的函数实现,这样这个alert函数就被我们劫持了。原理非常简单,下面举些典型的应用来看看我们能利用它来做些什么。

二、应用举例

1. 实现一个简易的javascript debugger,这里说是debugger比较标题党,其实只是有点类似于debugger的功能,主要利用js函数劫持来实现函数的break point,来看看个简单的例子:

<script type="text/javascript">
<!–
var _eval = eval;
eval = function(s) {
    if (confirm("eval被调用\n\n调用函数\n" + eval.caller + "\n\n调用参数\n" + s)) {
        _eval(s);
    }
}
//–>
</script>
<html>
<head>
</head>
<body>
<script type="text/javascript">
<!–
function test() {
    var a = "alert(1)";
    eval(a);
}

function t() {
    test();
}

t();
//–>
</script>
</body>
</html>

通过js函数劫持中断函数执行,并显示参数和函数调用者代码,来看一个完整例子的效果:

>help
debug commands:

bp <function name> – set a breakpoint on a function, e.g. "bp window.alert".
bl – list all breakpoints.
bc <breakpoint number> – remove a breakpoint by specified number, e.g. "bc 0".
help – help information.

>bp window.alert
* breakpoint on function "window.alert" added successfully.

>bl
* 1 breakpoints:
0 – window.alert

>bc 0
* breakpoint on function "window.alert" deleted successfully.

这里演示设置断点,察看断点和删除断点,完整代码在本文附录[1]给出。

2. 设置陷阱实时捕捉跨站测试者,搞跨站的人总习惯用alert来确认是否存在跨站,如果你要监控是否有人在测试你的网站xss的话,可以在你要监控的页面里hook alert函数,记录alert调用情况:

<script type="text/javascript">
<!–
function log(s) {
    var img = new Image();
    img.style.width = img.style.height = 0;
    img.src = "http://yousite.com/log.php?caller=" + encodeURIComponent(s);
}

var _alert = alert;
window.alert = function(s) {
    log(alert.caller);
    _alert(s);
}
//–>
</script>

当然,你这个函数要加到页面的最开始,而且还要比较隐蔽一些,赫赫,你甚至可以使alert不弹框或者弹个警告框,让测试者抓狂一把。

3. 实现DOM XSS自动化扫描,目前一般的XSS自动化扫描的方法是从http返回结果中搜索特征来确定是否存在漏洞,但是这种方法不适用于扫描DOM XSS,因为DOM XSS是由客户端脚本造成的,比如前段时间剑心发现的google的跨站(见附录[2])原理如下:

document.write(document.location.hash);

这样的跨站无法反映在http response里,所以传统扫描方法没法扫描出来。但是如果你从上个例子里受到启发的话,一定会想到设置陷阱的办法,DOM XSS最终导致alert被执行,所以我们hook alert函数设置陷阱,如果XSS成功则会去调用alert函数,触发我们的陷阱记录结果,这样就可以实现DOM XSS的自动化扫描,陷阱代码类似于上面。

4. 灵活的使用js劫持辅助你的页面代码分析工作,比如分析网页木马时,经常会有通过eval或者document.write来进行加密的情况,于是我们编写段hook eval和document.write的小工具,辅助解密:

<script type="text/javascript">
<!–
var _eval = eval;
eval = window.execScript = window.document.write = window.document.writeln = function(s) {
    document.getElementById("output").value = s;
}
//–>
</script>
<html>
<body>
input:
<textarea id="input" cols="80" rows="10"></textarea>
<input type="button" onclick="javascript: _eval(document.getElementById(‘input’).value);" value="decode" />
<br />
output:
<textarea id="output" cols="80" rows="10"></textarea>
</body>
</html>

在input框里输入加密的代码:

eval(unescape("%61%6C%65%72%74%28%31%29%3B"));

在output框里输出解码后的代码:

alert(1);

当然你还能想到更多的灵活应用:)

三、反劫持

谈到劫持也就必然要谈谈反劫持的话题,假设你要写一个健壮的xss playload,就需要考虑反劫持,有两个问题要解决:

如何判断是否被劫持了?
如果发现被劫持了,如何反劫持?

1. 判断某个函数是否被劫持,这个好办,写个小程序对比一下一个函数被hook前后,有什么差别:

<textarea id="tb1" cols="80" rows="8"></textarea>
<script type="text/javascript">
<!–
document.getElementById("tb1").value = eval + "\n";
var _eval = eval;
eval = function(s) {
    alert(s);
    _eval(s);
}
document.getElementById("tb1").value += eval;
//–>
</script>

结果:

function eval() {
    [native code]
}

function(s) {
    alert(s);
    _eval(s);
}

我们发现那些内置函数是[native code],而自定义的则是具体的函数定义,用这个特征就可以简单的检测函数是否被劫持:

function checkHook(proc) {
    if (proc.toString().indexOf("[native code]") > 0) {
        return false;
    } else {
        return true;
    }
}

2. 如何反劫持,第一个想法就是恢复被劫持的函数,如果劫持的人把原函数保存在某个变量里那还好办,直接调用原函数就可以了,但是劫持者自己也没保存副本怎么 办,只能自己创建个新的环境,然后用新环境里的干净的函数来恢复我们这里被hook了的,怎么创建新环境?整个新的iframe好了,里面就是个全新的环 境。ok,动手吧:

function unHook(proc) {
    var f = document.createElement("iframe");
    f.style.border = "0";
    f.style.width = "0";
    f.style.height = "0";
    document.body.appendChild(f);

    var d = f.contentWindow.document;
    d.write("<script type=\"text/javascript\">window.parent.escape = escape;<\/script>");
    d.close();
}

综合1、2节,整个测试代码如下:

<!–antihook.htm–>
<script type="text/javascript">
<!–
escape = function(s) {
    return s;
}
//–>
</script>
<html>
<body>
<input type="button" onclick="javascript: test();" value="test" />
<script type="text/javascript">
<!–
function test() {
    alert(escape("s y"));
    
    if (checkHook(escape)) {
        unHook(escape);
    }

    alert(escape("s y"));
}

function checkHook(proc) {
    if (proc.toString().indexOf("[native code]") > 0) {
        return false;
    } else {
        return true;
    }
}

function unHook(proc) {
    var f = document.createElement("iframe");
    f.style.border = "0";
    f.style.width = "0";
    f.style.height = "0";
    document.body.appendChild(f);

    var d = f.contentWindow.document;
    d.write("<script type=\"text/javascript\">window.parent.escape = escape;<\/script>");
    d.close();
}
//–>
</script>
</body>
</html>

3. 不是上面两个问题都解决了么,为什么要有第3节?因为那不是个最好的解决办法,既然我们可以创建全新的iframe,何不把代码直接放到全新iframe 里执行呢,这样做的话绿色环保,既不用考虑当前context里的hook问题,也不用改动当前context,不会影响本身的程序执行。给出两个比较通 用点的函数:

function createIframe(w) {
    var d = w.document;
    var newIframe = d.createElement("iframe");
    newIframe.style.width = 0;
    newIframe.style.height = 0;
    d.body.appendChild(newIframe);
    newIframe.contentWindow.document.write("<html><body></body></html>");
    return newIframe;
}

function injectScriptIntoIframe(f, proc) {
    var d = f.contentWindow.document;
    var s = "<script>\n(" + proc.toString() + ")();\n</script>";
    d.write(s);
}

把你的payload封装进一个函数,然后调用这两个方法来在iframe里执行:

function payload() {
    // your code goes here
}

var f = createIframe(top);
injectScriptIntoIframe(f, payload);

四、最后

由于国内很少有见文章提及这个东西,所以才草成这篇,希望能够抛砖引玉。由于本人水平有限,难免有错误或者疏漏之处请谅解,没有说清楚的地方,欢迎和我交流。

还有就是一些不得不感谢的人,感谢剑心一直以来毫无保留的交流,感谢黑锅多次鼓励我把自己的心得体会写成文字,感谢幻影所有的朋友们、B.C.T的朋友们以及群里那帮经常一起扯淡的朋友们。

广告一下,没法幻影blog的朋友,可以添加hosts来突破:
72.14.219.190 pstgroup.blogspot.com

五、附录

[1] 简易的javascript inline debugger代码

<!–test.htm–>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Javascript Inline Debugger</title></head>
<body>
<script language="javascript" type="text/javascript" src="js_inline_debugger.js"></script>
<input type="button" value="hahaha" onclick="javascript: alert(this.value);" style="margin-left: 300px;" />
</body>
</html>

/*
    FileName:        js_inline_debugger.js
    Author:            luoluo
    Contact:        luoluonet_at_yahoo.cn
    Date:            2007-6-27
    Version:        0.1
    Usage:    
                    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                    <html xmlns="http://www.w3.org/1999/xhtml">
                    <head>
                    </head>
                    <body>
                    <script language="javascript" type="text/javascript" src="js_inline_debugger.js"></script>
                    </body>
                    </html>
    Instruction:    
                    It is a simple javascript inline debugger. You must add xhtml1-transitional dtd to your
                    html document if you wanna to use the script.
*/

//————————————————————————–//
// 公用的函数
//————————————————————————–//
// 判断是否是IE
function isIE() {
    return document.all && window.external;
}

// 去除字符串两边的空格
String.prototype.trim = function() {
    var re = /(^\s*)|(\s*)$/g;
    return this.replace(re, "");
}

// 删除数组中某个元素
Array.prototype.remove = function(i) {
    var o = this[i];
    for (var j = i; j < this.length – 1; j ++) {
        this[j] = this[j + 1];
    }
    – this.length;
    return o;
}

// 判断一个数组中是否存在相同的元素
Array.prototype.search = function(o) {
    for (var i = 0; i < this.length; i ++) {
        if (this[i] == o) {
            return i;
        }
    }

    return -1;
}

// html编码
function htmlEncode(s) {
    s = s.replace(/&/g, "&amp;");
    s = s.replace(/</g, "&lt;");
    s = s.replace(/>/g, "&gt;");
    s = s.replace(/\"/g, "&quot;");
    s = s.replace(/\’/g, "&#34;");

    return s;
}

// js编码
function jsEncode(s) {
    s = s.replace(/\\/g, "\\\\");
    s = s.replace(/\n/g, "\\n");
    s = s.replace(/\"/g, "\\\"");
    s = s.replace(/\’/g, "\\\’");
    return s;
}

//————————————————————–//
// 命令行窗口工具
//————————————————————–//
function Console(parentNode, processInputProc) {
    // 窗口
    var panel = document.createElement("div");
    panel.style.width = "250px";
    panel.style.height = "250px";
    panel.style.borderColor = "#666666";
    panel.style.borderWidth = "1px";
    panel.style.backgroundColor = "#ffffff";
    panel.style.borderStyle = "solid";
    panel.style.position = "absolute";
    panel.style.zIndex = 100;
    parentNode.appendChild(panel);

    // 标题栏
    var title = document.createElement("div");
    title.style.width = "250px";
    title.style.height = "15px";
    title.style.backgroundColor = "#dddddd";
    title.style.fontSize = "12px";
    title.style.fontFamily = "verdana,tahoma";
    panel.appendChild(title);

    // 标题栏拖动窗口功能
    var isDragging = false;
    var startPos = new Position(panel.offsetLeft, panel.offsetTop);
    var startMousePos = new Position(0, 0);

    title.onmouseover = function(evt) {
        this.style.cursor = "pointer";
    }

    title.onmousedown = function(evt) {
        if (isDragging == true) {
            return;
        }

        var event = evt || window.event;
        startMousePos.x = event.clientX;
        startMousePos.y = event.clientY;
        isDragging = true;
    }

    title.onmousemove = function(evt) {
        if (isDragging == false) {
            return;
        }
        
        activateWindow();
        var event = evt || window.event;
        panel.style.left = (event.clientX – startMousePos.x) + startPos.x + "px";
        panel.style.top = (event.clientY – startMousePos.y) + startPos.y + "px";
    }

    title.onmouseup = function(evt) {
        if (isDragging == false) {
            return;
        }

        var event = evt || window.event;
        startPos.x =  (event.clientX – startMousePos.x) + startPos.x;
        panel.style.left = startPos.x + "px";
        startPos.y = (event.clientY – startMousePos.y) + startPos.y;
        panel.style.top = startPos.y + "px";
        isDragging = false;
    }

    // 关闭窗口功能
    var closeButton = document.createElement("div");
    closeButton.style.width = "13px";
    closeButton.style.height = "13px";
    closeButton.style.backgroundColor = "#ffffff";
    closeButton.style.styleFloat = closeButton.style.cssFloat = "left";
    closeButton.style.fontSize = "12px";
    closeButton.style.borderWidth = "1px";
    closeButton.style.borderColor = "#999999";
    closeButton.style.borderStyle = "solid";
    closeButton.style.paddingButton = "2px";
    closeButton.innerHTML = "<div style=\"margin-top: -2px;margin-left: 3px;\">x</div>";
    title.appendChild(closeButton);

    var isVisible = true;

    // 窗口关闭
    closeButton.onclick = function(evt) {
        isVisible = false;
        panel.style.display = "none";
    }

    // 标题栏文字
    var titleLabel = document.createElement("div");
    titleLabel.style.height = "14px";
    titleLabel.style.width = "200px";
    titleLabel.style.fontSize = "11px";
    titleLabel.style.color = "#ffffff";
    titleLabel.style.styleFloat = titleLabel.style.cssFloat = "left";
    titleLabel.style.fontFamily = "verdana,tahoma";
    titleLabel.innerHTML = "Javascript Inline Debugger";
    //titleLabel.style.marginTop = isIE() ? -14 : "-14px";
    titleLabel.style.marginLeft = isIE() ? 18 : "18px";
    title.appendChild(titleLabel);

    // 中间的显示部分
    var showPanel = document.createElement("div");
    showPanel.style.width = "250px";
    showPanel.style.height = isIE() ? "221px" : "219px";
    showPanel.style.fontSize = "11px";
    showPanel.style.padding = "0";
    showPanel.style.margin = "0";
    showPanel.style.overflow = "auto";
    showPanel.style.marginTop = isIE() ? -1 : "0";
    panel.appendChild(showPanel);

    // 命令输入框
    var cmdArea = document.createElement("div");
    panel.appendChild(cmdArea);

    var cmdTextbox = document.createElement("input");
    cmdTextbox.type = "text";
    cmdTextbox.style.width = "250px";
    cmdTextbox.style.height = "12px";
    cmdTextbox.style.fontSize = "12px";
    cmdTextbox.style.padding = "0";
    cmdTextbox.style.margin = "0";
    cmdTextbox.style.marginTop = isIE() ? -2 : "0";
    cmdTextbox.style.borderWidth = "0";
    cmdTextbox.style.borderTopWidth = "1px";
    cmdTextbox.style.paddingTop = "2px";
    cmdArea.appendChild(cmdTextbox);

    // 窗口激活或者不激活
    var isActive = false;

    // 激活窗口
    var activateWindow = function() {
        if (! isVisible) {
            return;
        }

        if (isActive) {
            return;
        }

        cmdTextbox.focus();
        title.style.backgroundColor = "#015DF4";
        isActive = true;
    }

    panel.onclick = activateWindow;

    // 不激活窗口
    var deactivateWindow = function() {
        if (! isVisible) {
            return;
        }

        if (! isActive) {
            return;
        }

        title.style.backgroundColor = "#cccccc";
        isActive = false;
    }

    cmdTextbox.onfocus = activateWindow;
    cmdTextbox.onblur = deactivateWindow;

    // 输出函数
    var dbgPrint = function(s) {
        showPanel.innerHTML += htmlEncode(s).replace(/\n|(\r\n)/g, "<br />");
        
        if (parseInt(showPanel.scrollHeight) > parseInt(showPanel.style.height)) {
            showPanel.scrollTop += parseInt(showPanel.scrollHeight) – parseInt(showPanel.style.height);
        }
    }

    // 调用输入处理回调函数
    cmdTextbox.onkeydown = function(evt) {
        var event = evt || window.event;

        if (event.keyCode == 0×0d) {
            processInputProc(this.value, dbgPrint);
            this.value = "";
        }
    }
}

// 坐标类
function Position(x, y) {
    this.x = x;
    this.y = y;
}

//————————————————————————–//
// 内联调试器类
//————————————————————————–//
function InlineDebugger() {
    var bpList = new Array();
    var id_eval;

    // 设置断点
    var bp = function(funcName) {
        // 检查eval是否被hook
        if ((new String(eval)).indexOf("[native code]") < 0) {
            return "error: eval function was hooked by other codes in the front.\n";    
        }

        // 保存未被hooked的eval
        id_eval = eval;

        var re = /^[a-zA-Z0-9_\.]+$/i;
        if (! re.test(funcName)) {
            return "error: bad argument of command bp \"" + funcName + "\".\n";
        }

        try {
            id_eval("if (typeof(" + funcName + ") == \"object\" || typeof(" + funcName + ") == \"function\") {var obj = " + funcName + ";}");
        } catch (e) {
            return "error: " + e + ", invalid function name \"" + funcName + "\".\n";
        }

        if (obj == undefined) {
            return "error: the argument of command bp \"" + funcName + "\" is not a function object.\n";
        }

        if ((new String(obj)).indexOf("function") < 0) {
            return "error: the argument of command bp \"" + funcName + "\" is a property, a function is required.\n";
        }

        if (bpList.search(funcName) >= 0) {
            return "error: there is a breakpoint on function \"" + funcName + "\"\n";
        }

        try {
            var sc = "window." + funcName.replace(/\./g, "_") + "_bak = " + funcName + ";\n" +
                     funcName + " = " +
                     "function() {\n" +
                     "    var args = \"\";\n" +
                     "    for (var i = 0; i < arguments.length; i ++) {\n" +
                     "        args += arguments[i] + (i == (arguments.length – 1) ? \"\" : \",\");\n" +
                     "    }\n" +
                     "    if (confirm(\"function \\\"" + funcName + "\\\" was called, execute it?\\n\\narguments:\\n\" + args + \"\\n\\ncaller:\\n\" + " + funcName + ".caller)) {\n" +
                     "        id_eval(\"" + funcName.replace(/\./g, "_") + "_bak(\\\"\" + jsEncode(args) + \"\\\")\");\n" +
                     "    }\n" +
                     "};" +
                     "\n";
            id_eval(sc);
            bpList.push(funcName);
            return "* breakpoint on function \"" + funcName + "\" added successfully.\n";
        } catch (e) {
            return "unkown error: " + e + ".\n";
        }
    }

    // 枚举所有的断点
    var bl = function() {
        if (bpList.length == 0) {
            return "* no breakpoint.\n";
        }

        var bps = "* " + bpList.length + " breakpoints: \n";

        for (var i = 0; i < bpList.length; i ++) {
            bps += i + " – " + bpList[i] + "\n";
        }

        return bps;
    }

    // 清除某个断点
    var bc = function(n) {
        try {
            n = parseInt(n);
        } catch (e) {
            return "error: bc command requires a numeric argument.\n";
        }

        if (bpList.length == 0) {
            return "error: no breakpoint.\n";
        }

        var funcName = bpList.remove(n);
        
        try {
            eval(funcName + " = window." + funcName.replace(/\./g, "_") + "_bak;");
            return "* breakpoint on function \"" + funcName + "\" deleted successfully.\n";
        } catch (e) {
            return "error: " + e + ".\n";
        }
    }

    // 帮助
    var help = function() {
        var s = "debug commands:\n\n" +
                "bp <function name> – set a breakpoint on a function, e.g. \"bp window.alert\".\n" +
                "bl – list all breakpoints.\n" +
                "bc <breakpoint number> – remove a breakpoint by specified number, e.g. \"bc 0\".\n" +
                "help – help information.\n"
                "\n";
        return s;
    }

    // 处理命令
    this.exeCmd = function(cmd) {
        cmd = cmd.trim();
        var cmdParts = cmd.split(/\s+/g);
        var cmdName;
        var cmdArg;

        if (cmdParts.length == 1) {
            cmdName = cmd;
        } else {
            cmdName = cmdParts[0];
            cmdArg = cmdParts[1];
        }

        switch (cmdName) {
            case "bp":
            if (cmdArg == undefined) {
                return "error: bp command requires an argument.\n";
            } else {
                return bp(cmdArg);
            }
            break;

            case "bl":
            return bl();
            break;

            case "bc":
            if (cmdArg == undefined) {
                return "error: bc command requires an argument \"number of breakpoint\".\n";
            } else {
                return bc(cmdArg);
            }
            break;

            case "help":
            return help();
            break;

            default: return "error: command \"" + cmdName + "\" not found, you can get information by \"help\" command.\n";
            break;
        }
    }
}

//—————————————————————————–//
// 主过程
//—————————————————————————–//
/*try {
    debugger;
} catch (e) {}*/
var id = new InlineDebugger();
var console = new Console(document.body, function(s, printProc){printProc(id.exeCmd(s));});

2007年05月26日

远在老家农村的老妈,知道明天下午4点儿子开完会,而自家的菜园子也可以在下午3点半前浇完水。这叫日程管理和工作计划安排;
下午3:30,小侄女提前30分钟到菜地找到老妈,提醒要去打电话。这叫工作提醒;
老妈提出让大侄子骑自行车带他去乡里电话亭打电话。这叫共享资源申请;
大侄子说那辆新自行车被他老爸骑走了,只有委曲坐那辆旧自行车去。这叫共享资源计算和领用;
到了乡政府门口的电话亭,老妈带上老花镜,翻开随身带的小本本,那里写了儿子的电话。这叫通讯录管理;
老妈给儿子说你都快30岁了,得找个媳妇了。这叫工作任务下达;
儿子很不好意思,开玩笑地小声问:我找不到怎么办?这叫即时通讯;
老妈壮大胆子开导儿子,说你爸当年怎么跟我相亲怎么好上的。这叫知识管理;
老妈说你可以找人介绍相亲,也可以像你大哥一样自己找到相好的,这叫多流程可选项提供;
儿子说俺们公司做软件的,偶尔有几个女的不是已经有了相好的就是比较丑,我还是相亲吧,这叫流程选定;
老妈说你本月就要找到媒人,媳妇得在明年春节期间定下来,明年元旦这三天你就要将侯选对象带来家里给大家看一下。这叫流程节点设置;
电话打完,大侄子骑车将老妈带回,嫂子发现自行车后架散了,将大侄子说了一通。这叫共享资源的使用归还和入库检查;
快到月底了,儿子的日历牌上写着老妈交办的任务,开始心里急了。这叫流程提醒;
一过月底,老妈就打来电话问媒人找到没有。这叫流程催办;
总算找到媒人了,她有一个在城市长大的同事的女儿和从乡下来的远房的亲戚可以介绍,问儿子要约哪一个见面。这叫流程分支;
儿子说要看对方各自是什么条件,谁长得漂亮,你就帮我约见谁。这叫分支条件自动判断;
媒人说都很漂亮,城市女男朋友多点个子矮点脾气大点,乡下妹具有朴实、文静等若干好处。这叫复杂条件的逻辑判断;
儿子说这样吧你给我照片我再看约见谁。这叫人工判断和自由选择;
拿到照片后儿子发现这年头照像技术太高,谁都像明星,跟本分不出谁好谁坏、是不是本人,就寄给老妈由她决定。这叫工作委托;
儿子想想除了寄照片,还要给老妈谈谈心,有些话不好说就用笔来表达。这叫电子邮件;
老妈说两人都看看吧。儿子打算看完城市女后再见见乡下妹。这叫任务串行。
巧在媒人的远房亲戚与自家嫂子同在一个村,在儿子急不可奈约会城市女的时候嫂子已经见过乡下妹了。这叫任务并行;
如果家里5人同意乡下妹并且儿子不反对,就选择乡下妹。如果儿子同意城市女并且老妈也同意就选择城市女。这叫节点的“与或”逻辑关系;
老妈急匆匆将儿子召回,说大家要碰个头谈一下,商讨该选择城市女还是乡下妹。这是会议起草;
儿子说我下周5赶回家一趟。这叫参会确认;
老妈问要到家里住几天,要吃老妈炒的哪些菜。这叫会议食宿安排;
如期回家商讨,大家争执不下,老妈采用丢豆子的作法,两个碗中哪个碗里的豆子多就选择对应的人。这叫电子投票;
最后依据统计结果,再形成一致意见,决定还是选择乡下妹。这叫会议精神;
儿子给媒人说我们家里开过会了,还是选择乡下妹。这叫会议精神传达;
与乡下妹的关系进展很顺利,家量商量干脆元旦订婚,并且要详细规定了订婚的主要事项和各人应负的责任。这叫公文起草;
老妈找来发黄的当年大哥订婚的安排纸片,让小侄女直接照这个格式来将起草内容。这叫公文模版管理;
订婚的安排分别让大姑大嫂小舅小妹都看过一遍,并作若干修改。这叫公文改稿;
最后老妈、老爸、儿子都对各项内容满意了,并同意照办。这叫公文会签;
老妈安排小侄女在村里广播站将订婚消息播出,按老家规矩全村人都要来喝喜酒。这叫公文转公告;
老妈说这个订婚安排纸头保留在她那,大家有什么不清楚的地方再来问她要这个订婚安排。这叫档案管理;
嫂子记性就是不好,问老妈要订婚安排内容,老妈说你不能改啊。这叫档案借阅及权限控制;
可是,天有不测风云,听说乡下妹早已有了相好的并且都准备结婚了,大家认为要重新选择媳妇人选。这叫流程退回;
也有的说还是找那个媒人再帮助介绍几个乡下妹吧。这叫流程跳转;
可是儿子心里比较烦,打算这事先放一边再说。这叫流程中止;
又传来消息,说乡下小妹早就与老相好吹了,并且邻居都说乡下妹人品不错,老妈说订婚可以继续下去。这叫流程恢复;
可儿子心里总堵得慌,本以为乡下小妹人单纯哪知道还有老相好,就打算不再相亲娶媳妇了。这叫流程否决;
经不住老妈老爸大姑大嫂小舅小妹同学朋友的左说右劝,最后还是重新找个媒人来说亲吧。这叫流程重启;
某日不小心进入网上同学录,大家都在交流相亲心得,儿子也发了几个帖谈谈这段时间的相亲感想。这叫工作讨论;
想来想去,还是找找同学帮忙介绍,于是儿子将自己的要求列成一个表格,让同学将合适人选填在表格里交给他。这叫表单定制;
想想都是新时代的新新人类了,找媳妇的事还是随遇而安吧,不要那么严格的程序。这叫自由流转;
若干年后儿子仍然单身,见过的女孩已不计其数,其中年龄25岁以下的N个、25岁以上的N+8个、长发的N-5个、短发的N+13个。这叫表单内容统计分析;
儿子想想很伤心,与自己一起“压马路”的女孩不足30%,每次“压马路”时间不超过15分钟。这叫流程效率分析……

2007年03月30日

一位优秀的商人杰克,有一天告诉他的儿子  

杰克:我已经决定好了一个女孩子,我要你娶她
儿子:我自己要娶的新娘我自己会决定  
杰克:但我说的这女孩可是比尔·盖茨的女儿喔  
儿子:哇!那这样的话……   

在一个聚会中,杰克走向比尔·盖茨  

杰克:我来帮你女儿介绍个好丈夫  
比尔:我女儿还没想嫁人呢  
杰克:但我说的这年轻人可是世界银行的副总裁喔  
比尔:哇!那这样的话……   

接着,杰克去见世界银行总裁  

杰克:我想介绍一位年轻人来当贵行的副总裁  

总裁:我们已经有很多位副总裁,够多了  

杰克:但我说的这年轻人可是比尔·盖茨的女婿喔  
总裁:哇!那这样的话……   

最后,杰克的儿子娶了比尔-盖茨的女儿,又当上世界银行的副总裁  知道吗,生意通常都是这样谈成的

2006年02月18日
 


有时候我们需要定时地处理大量的数据,这里我们称之为Batch处理。

Batch处理就是定时地执行某个程序。其实可以很明显的看到,这里我们需要做到两个东东:定时执行程序。下面描述一下这两个东东。

定时:

我们这边使用的是Linux系统,如果我们需要按一定的周期执行程序,我们可以使用crontab来实现。crontab C)指令从指定文件或标准输入拷贝或编辑成用户的 crontab 文件,该文件规定了在指定日期和时间调度运行一些指令。

可以使用如下的方法来使用:

$ crontab -e

编辑方式与vi相同。

编辑结束然后保存退出,系统会自动的安装crontab。修改crontab时可以使用相同的方法。虽然有别的办法可以实现相同的效果,但是这里建议使用这种方法,安全第一嘛!

Example:

$ crontab -e

 

#Created by Yaogao 2005-12-30 12:43

30 11 * * * (echo "Crontab start to run ….";

. /etc/profile;/home/ebid/bin/bash.sh;echo "Crontab stopped!")

00 17 * * * (echo "Crontab start to run ….";

. /etc/profile;/home/ebid/bin/bash.sh;echo "Crontab stopped!")

解释一下:

第一:"#"注释开始

第二:30 11 * * * 标示各种时间。(30 11 * * *的意思是:每天1130)

所以这5个数分别代表minutes  hours  day_of_month  month  day_of_week

  minutes      分钟   0-59

  hours       小时   0-23

  day_of_month   日    1-31

  month       月    1-12

  day_of_week    星期     0-60表示星期日)

还有就是"*"代表该字段所有的可能值。

下面是比较有意思的一些时间设定:

#每天早上6

0 6 * * *

 

 

#每两个小时

0 */2 * * *

 

#晚上11点到早上8点之间每两个小时,早上8

0 23-7/28 * * *

 

#每个月的4号和每个星期的星期一到星期三的早上11

0 11 4 * mon-wed

 

#1月份1日早上4

0 4 1 jan *

第三:Command

对于command,如果只有单个命令,可以这样:

#每天早上6点执行date命令

0 6 * * * date

如果是多个命令:0 6 * * * (echo "Crontab start to run ….";

. /etc/profile;/home/ebid/bin/bash.sh;echo "Crontab stopped!")那么命令之间用";"分隔。执行顺序由先至后。

其实这里还有一个比较重要的东西,就是 . /etc/profile,这个命令必须得有,虽然这个是全局变量,而且系统启动之后就应该设定好了,但是这里仍然需要重新设定。

 

这样我们就实现了在固定的某个时间执行我们的程序。

 

执行程序:

执行程序就相对来说比较简单了,只要我们设置好需要的环境变量,那么程序就可以执行了。一般的做法都是写一个脚本,比如说上面的bash.sh

Example:

#!/bin/csh -f

#setenv hello 

setenv  CLASSPATH  ‘.’

set batch_home=/home/oracle/project/

set flist=`ls $batch_home/lib/*.jar`

foreach name ($flist)

        setenv CLASSPATH ${CLASSPATH}:${name}

end

 

echo "Shukei Start  ………."

cd   $batch_home/bin

java divinemind.onlyfun.Helloworld HelloYG

echo "Shukei End ………"

解释一下:

#!/bin/csh -f:指定shell,上面提到过注释,但是对于脚本,第一行不被认为是注释。这里我们使用C shell,虽然这种shell不赞成被使用,但是就像我们使用任何语言写Helloworld的效率都差不多一样,我们这里使用别的shell也没有什么区别。其实最重要的一点还是C shell有我很需要的一个重要的支持,下面会有提到。

 

#setenv hello  :注释

 

setenv  CLASSPATH  ‘.’  :设定环境变量,setenv命令跟bashexport命令一样可以设定环境变量。

 

set batch_home=/home/oracle/project/:设定shell变量。

set flist=`ls $batch_home/lib/*.jar` :设定shell变量。刚刚说到的C shell的一个重要支持就在这里。其实其他shell也应该可以实现。

 

foreach name ($flist)

        setenv CLASSPATH ${CLASSPATH}:${name}

end

循环设定环境变量。下面几句的功能就是将jar文件设定到环境变量,让java程序得以执行。

setenv  CLASSPATH  ‘.’

set batch_home=/home/oracle/project/

set flist=`ls $batch_home/lib/*.jar`

foreach name ($flist)

        setenv CLASSPATH ${CLASSPATH}:${name}

end

 

下面这几句很简单,就是执行java代码。

echo "Shukei Start  ………."

cd   $batch_home/bin

java divinemind.onlyfun.Helloworld HelloYG

echo "Shukei End ………"

 

上面描述了如何定时和如何写脚本执行我们的java程序,运行结果系统通过mail发送到当前用户的邮箱。bash.sh能够执行我们的java程序,crontab能够定时地执行我们的bash.sh,那么大家可以看到,batch处理功能已经做好,呵呵!

 

命令介绍:

如果对于Linux系统跟我一样不是很熟悉,下面介绍几个命令:

$ chmod 777 bash.sh

这个命令可以使bash.sh可执行。chmodchange modify的意思,后边的777表示文件所有者、组用户、其他用户都可以读、写、执行这个文件,修改777就可以设定别的权限(比如说666那么这个文件将不可以执行)。其实刚刚如果我们用vi建立bash.sh的时候,bash.sh是不可以执行的,但是使用这个命令之后就可以了。

 

还有就是chown(change owner)chgrp(change group)这两个命令,每当创建新文件与目录时,标记你是文件所有者,你的同组人员为组员,如果要把文件的使用权交给别人,只有文件所有者方可更改(当然root干什么都可以),命令如下:

chown owner file

chown改变了文件的身分ID UID

改变属组要改变文件所属的组别--文件组IDGID),命令变为:

chgrp group file

其实应该还有很多其他的命令,大家可以上网查询,我也有一些东西,可以跟大家共享。

 

Batch处理部署:

Batch处理的.class文件可以存放在任意目录下,当然需要当前用户拥有访问权限,所以我们一般情况下将文件存放在当前用户的工作目录(/home/currentuser)下,然后我们自己写的shell文件也是放在什么地方都可以,但是我们最好也是放在当前用户的工作目录下或者更下级目录。上面的例子中就将bash.sh放在了batch处理工程的/bin目录下。至于crontab,由于我们使用$crontab e命令,所以我们不需要关心文件所在的位置。


2006年02月08日
                                                               
                                                                                     作者: Bob Lee
<!–[if !supportEmptyParas]–> <!–[endif]–>
<!–[if !supportEmptyParas]–> <!–[endif]–>
在这里,Spring指的是Spring开发框架,一种依赖注入容器的实现。首先,我声明我没有一点冒犯Rod的意思(你是个优秀的人)。但是坦白地说,我不能狂热的追随您的开发框架。更为严重的是,我注意到,我所考虑的这些可能对于Spring框架的使用率来说是危险的,并且有可能降低对Spring框架的使用率。我读到一个关于Spring的很重要的文章或书籍,看起来好像除了我,所有的人都喜欢Spring。但是我有什么损失吗?可能采用Spring是J2EE一个类似“膝跳反射”的事情(这种事情可以不经过大脑)。“J2EE不是好东东,Spring的人说他们的产品更好一些,所以Spring一定是好的”但是事情并不是那样的。
第二点,我仅仅讨论Spring,而不讨论依赖注入。我喜欢依赖注入,并且每天都使用它。它是一个摆脱service locators的好方法。
我记不得有多少次有开发者对我说,“我的上一个项目使用了Spring,这个框架真好”。但是没有人能清楚明白的说出他们究竟喜欢Spring的哪一点,Spring到底帮助了他什么。所以我敢说,他们喜欢setter注入,这使得他们的代码更加灵活和可测;但并不一定必需Spring。我猜有些人并没有彻底理解依赖注入,所以他们依赖Spring来帮助(或者强迫?)他们使用依赖注入。然而,这种好处并没有在量上大于Spring在你的代码上带来的负面影响,这些负面相应在下面的清单上。
Spring的狂热者公开的指责J2EE,但是从他们的指责中,我可以说,Spring实际上既不是轻量级也不简单。Javadocs未必是必要的,但是这一切都要怪罪于一个用户API吗?最起码J2EE清晰的将API和它们的实现分离开来。Spring的鼓吹者吹捧Spring不会“动”你的代码,例如,你不必去实现任何的Spring指定的接口(生命周期接口除外等等)。新闻特写,“XML配置文件是我们自己的代码,通过它,我们能组织起很多的代码,这些代码都是Spring给定的”
为什么我一定要把我所有的依赖使用XML文件来表达?我是需要把我所有的对象都用Spring来处理,还是仅仅一些没有考虑成熟的?上面我所说的这些问题,Spring文档没有给出一些可靠的答案,而且所有的Spring书籍也没有给出。我假定我们使用Spring是为了产生所有的对象。那么这样还是我们喜欢的Java编程吗?我希望在编译期和随后的装载期能够确定这些对象,而不是在运行我的代码的时候才能够确定。Spring能做到支持这些吗?
很明显,我希望装载一些像JDBC驱动这样的动态实现的依赖(即不要求编译期的检测);但是在我的系统中,这样的依赖只是一小部分;而剩下的部分,我们代码的绝大部分却不是。我是在使用一种强类型的语言。如果我希望像Spring那样,我会使用Ruby。难道Spring的配置文件不像是我们在猜测着将Java代码写入XML文件里吗?难道那些使用Spring的开发者使用起Java来不那么舒服?我确信增加了一个XML层并不能使得代码变得哪怕是一点点简单。
现在回过头来谈谈关于对Spring API的依赖的问题。我不得不调用容器来产生我的第一个对象(假定剩下的Spring管理对象是注入的),不是吗?我需要一些方法在编译期间来测试我所请求的对象是正确的类型;我不想靠抛出违例的方法。究竟,我真的需要一个容器吗?在Spring里,你通过使用一个唯一的ID来获取对象。我假定大部多数的开发者在他们Java代码里使用一个String类型的常量来定义他们的ID。Spring并不能帮助我使得我的Java代码里的ID值和我的XML文件里的ID值保持一致。当一个对象已经够用了的时候,为什么要使用两个对象?是否我们真的把所有的信息组织到一起放到了容器里?是Java的包和类不够用了吗?
还有一个困扰我的问题是,在我的XML配置文件里,我不得不引用Spring实现的类,我不想管这些东西。在Spring的计划里,我听说更加简单的、域范围的XML将在2.0版本中被使用,但是我到现在还没有看到。为什么这些不能早一点被采用呢?
什么继承上的东西啊?关于超常类名置换的变换。但这些都不是我的风格。
Spring在哪里支持了JDK1.5的泛型?我知道你有很多客户运行在JDK1.4甚至JDK1.3的版本上,但是这和JDK1.5没有分歧啊。泛型打开了通向像这种框架的各种各样的可能性的大门。这些是我最喜欢的JDK1.5的新特性,拥抱这些新特性吧!
你曾经看过每次你产生一个对象Spring要做多少事情吗?我需要在运行期内有少量的instanceof,而大多数是在装载期的Class.isAssignableFrom()。不是因为内部的实现给最终用户带来了很多的麻烦,而是因为把它作为了一个测试框架剩下部分质量的试金石。一个好的对于bean的创建渠道的重构将会很容易的被遵循和产生更加高质量的代码,并且不需要求助于更多的继承就能被重用。
Auto-wiring也有同样的问题。每一个人真的在使用它吗?或者是为未来的重要功能的一个铺垫?

Spring是怎么处理领域范围的?我听说在2.0版本中,Spring最终会支持HTTP request和session的。我很惊讶难道现在这些没有被支持吗,那么Spring的用户在整个期间里都做了些什么。难道是新的版本将使我能够定义自己的领域范围?例如,它将实现我想要一个“conversation”领域范围,就像Seam将要支持的那样。

我们将不涉及MVC或者AOP框架。幸运或者不幸的是在某一时候,我将不使用一个依赖注入容器。难道不认为PicoContainer已经远离了简单二字。它也和Spring有同样的问题,我认为Aslak和Jon已经转移到了Ruby的领地。还有其他的开发框架没有这样的问题吗?
幸运地,简单的采用依赖注入设计模式能让你完成Spring的90%的工作,特别是当你在并不急于使用它的时候。这是我推荐的方法。我的确没有看到让我自己马上使用Spring的理由。没有它我工作的很好。如果你在使用它,那么请你把眼睛睁大一些,使用怀疑的、鉴赏的目光。如果仅仅因为某人有流行的开源框架,他们有很好的市场,并且他们被一些大的公司支持(IBM在很多年前就推荐我使用Struts)。但这并不意味着他们知道什么工具对你最好,尽管他们比你知道什么是更好的。
2006年01月10日

   每一个对象能被Spring配置里的一个<bean>标记引用。在这个例子里,bean “mySessionFactory”代表一个HibernateSessionFactory,bean “myTransactionManager”代表一个Hibernate transaction manager。注意transactionManger bean有一个叫作sessionFactory的属性元素。HibernateTransactionManager有一个为 sessionFactory准备的setter和getter方法,它们是用来当Spring容器启动时的依赖注入。sessionFactory属性引用mySessionFactory bean。这两个对象现在当Spring容器初始化时将被连在一起。这种连接把你从为引用和创建这些对象而创建singleton对象和工厂中解放出来,这减少了你应用程序中的代码维护。mySessionFactory bean有两个属性元素,它们翻译成为mappingResources 和 hibernatePropertes准备的setter方法。通常,如果你在Spring之外使用Hibernate,这个配置将被保存在 hibernate.cfg.xml文件中。不管怎样,Spring提供了一个便捷的方式–在Spring配置文件中合并Hibernate的配置。获得更多的信息查阅Spring API(http://www.springframework.org/docs/api/index.html)。

既然我们已经配置了我们的容器服务beans和把它们连在了一起,我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后,我们需要把这些对象连接到事务管理器。
  
这是在Spring配置文件里的样子:

<!-- ORDER SERVICE --> 
<bean id="orderService"
  class="org.
         springframework.
         transaction.
         interceptor.
         TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref local="myTransactionManager"/>
  </property>
  <property name="target">
    <ref local="orderTarget"/>
  </property>
  <property name="transactionAttributes">
    <props>
      <prop key="find*">
     PROPAGATION_REQUIRED,readOnly,-OrderException
      </prop>
      <prop key="save*">
     PROPAGATION_REQUIRED,-OrderException
      </prop>
    </props>
  </property>
</bean>

<!-- ORDER TARGET PRIMARY BUSINESS OBJECT:
Hibernate implementation -->
<bean id="orderTarget"
         class="com.
                meagle.
                service.
                spring.
                OrderServiceSpringImpl">
  <property name="orderDAO">
    <ref local="orderDAO"/>
  </property>
</bean>

<!-- ORDER DAO OBJECT -->
<bean id="orderDAO"
         class="com.
                meagle.
                service.
                dao.
                hibernate.
                OrderHibernateDAO">
  <property name="sessionFactory">
    <ref local="mySessionFactory"/>
  </property>
</bean>



图4是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。

image
图4:这是Spring怎样将在这个配置的基础上装配beans。

  这个例子使用一个TransactionProxyFactoryBean,它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象,它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理, transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。获得更多的关于在一个事务上配置隔离层和提交或回滚查阅 TransactionAttributeEditor(http: //www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html)。

  TransactionProxyFactoryBean(http: //www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.html)类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用(a reference)。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中,orderDAO bean是我们的和持久层交流的DAO对象。

  还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton,意味着一个共享的bean的实例将被管理。这是用于无状态操作–像一个无状态会话bean将提供的那样。当bean由Spring提供时,prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。

提供一个服务定位器(Providing a Service Locator)
  既然我们已经把我们的服务和我们的DAO连起来了,我们需要把我们的服务暴露给其它层。通常是一个像使用Struts或Swing这样的用户接口层里的代码来使用这个服务。一个简单的处理方法是使用一个服务定位器模式的类从一个Spring上下文中返回资源。这也可以靠引用bean ID通过Spring来直接完成。
  这儿是一个在Struts Action中怎样配置一个服务定位器的例子:

public abstract class BaseAction extends Action { 

  private IOrderService orderService;

  public void setServlet(ActionServlet
                                 actionServlet) {
    super.setServlet(actionServlet);
    ServletContext servletContext =
               actionServlet.getServletContext();

    WebApplicationContext wac =
      WebApplicationContextUtils.
         getRequiredWebApplicationContext(
                                 servletContext);

      this.orderService = (IOrderService)
                     wac.getBean("orderService");
  }

  protected IOrderService getOrderService() {
    return orderService;
  }
}



用户接口层配置 (UI Layer Configuration)
  示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。让我们从在struts-config.xml文件里检查一个Action配置开始。
  

<action path="/SaveNewOrder" 
    type="com.meagle.action.SaveOrderAction"
    name="OrderForm"
    scope="request"
    validate="true"
    input="/NewOrder.jsp">
  <display-name>Save New Order</display-name>
  <exception key="error.order.save"
    path="/NewOrder.jsp"
    scope="request"
    type="com.meagle.exception.OrderException"/>
  <exception key="error.order.not.enough.money"
    path="/NewOrder.jsp"
    scope="request"
    type="com.
          meagle.
          exception.
          OrderMinimumAmountException"/>
  <forward name="success" path="/ViewOrder.jsp"/>
  <forward name="failure" path="/NewOrder.jsp"/>
</action>



  SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action;然而,注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件(applicationContext-hibernate.xml)中配置了(在transactionAttributes属性里)。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常,OrderException,当在持久层里保存订单对象失败时将被这个 action使用。这将引起事务回滚和通过业务对象传递把异常传回给Struts层。OrderMinimumAmountException,在业务对象逻辑里的一个事务因为提交的订单达不到最小订单数量而失败也将被处理。然后,事务将回滚和这个异常能被用户接口层恰当的处理。

  最后一个连接步骤是使我们的表现层和我们的业务层交互。这已经通过使用前面讨论的服务定位器来完成了。服务层充当一个到我们的业务逻辑和持久层的接口。这儿是 Struts中的SaveNewOrder Action可能怎样使用一个服务定位器调用一个业务方法:

public ActionForward execute( 
  ActionMapping mapping,
  ActionForm form,
  javax.servlet.http.HttpServletRequest request,
  javax.servlet.http.HttpServletResponse response)
  throws java.lang.Exception {

  OrderForm oForm = (OrderForm) form;

  // Use the form to build an Order object that
  // can be saved in the persistence layer.
  // See the full source code in the sample app.

  // Obtain the wired business service object
  // from the service locator configuration
  // in BaseAction.
  // Delegate the save to the service layer and
  // further upstream to save the Order object.
  getOrderService().saveNewOrder(order);

  oForm.setOrder(order);

  ActionMessages messages = new ActionMessages();
  messages.add(
      ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(
      "message.order.saved.successfully"));

  saveMessages(request, messages);

  return mapping.findForward("success");
}



结论
  这篇文章按照技术和架构覆盖了许多话题。从中而取出的主要思想是怎样更好的给你的应用程序分层:用户接口层、持久逻辑层、和其它任何你需要的应用层。这样可以解耦你的代码,允许添加新的代码组件,使你的应用在将来更易维护。这里覆盖的技术能很好的解决这类的问题。不管怎样,使用这样的构架可以让你用其他技术代替现在的层。例如,你也许不想使用Hibernate持久化。因为你在你的DAO对象中编码到接口,你能怎样使用其它的技术或框架,比如 iBATIS(http://www.ibatis.com/),作为一个替代是显而易见的。或者你可能用不同于Struts的框架替代你的UI层。改变 UI层的实现不会直接影响你的业务逻辑层或者你的持久层。替换你的持久层不会影响你的UI逻辑或业务服务层。集成一个web应用其实也不是一件烦琐的工作,靠解耦你的各应用层和用适当的框架组成它,它能变得更容易处理。

   其实,就算用Java建造一个不是很烦琐的web应用程序,也不是件轻松的事情。当为一个应用程序建造一个构架时有许多事情需要考虑。从高层来说,开发者需要考虑:怎样建立用户接口(user interfaces)?在哪里处理业务逻辑?和怎样持久化应用数据。这三层每一层都有它们各自的问题需要回答。各个层次应该使用什么技术?怎样才能把应用程序设计得松耦合和能灵活改变?构架允许层的替换不会影响到其它层吗?应用程序怎样处理容器级的服务(container level services),比如事务处理(transactions)?

  当为你的web应用程序创建一个构架时,需要涉及到相当多的问题。幸运的是,已经有不少开发者已经遇到过这类重复发生的问题,并且建立了处理这类问题的框架。一个好框架具备以下几点:减轻开发者处理复杂的问题的负担(“不重复发明轮子”);内部定义为可扩展的;有一个强大的用户群支持。框架通常能够很好的解决一方面的问题。然而,你的应用程序有几个层可能都需要它们各自的框架。就如解决你的用户接口(UI)问题时你就不应该把事务逻辑和持久化逻辑掺杂进来。例如,你不应该在控制器(controller)里面写jdbc代码,使它包含有业务逻辑,这不是控制器应该提供的功能。它应该是轻量级的,代理来自用户接口(UI)外的调用请求给其它服务于这些请求的应用层。好的框架自然的形成代码如何分布的指导。更重要的是,框架减轻开发者从头开始写像持久层这样的代码的痛苦,使他们专注于对客户来说很重要的应用逻辑。

  这篇文章将讨论怎样组合几个著名的框架去做到松耦合的目的,怎样建立你的构架,怎样让你的各个应用层保持一致。富于挑战的是:组合这些框架使得每一层都以一种松耦合的方式彼此沟通,而与底层的技术无关。这篇文章将使用3种流行的开源框架来讨论组合框架的策略。表现层我们将使用Struts(http://jakarta.apache.org/struts);业务层我们将使用Spring (http://www.springframework.org/);持久层使用Hibrenate(http: //www.hibernate.org/).你也可以在你的应用程序中替换这些框架中的任何一种而得到同样的效果。图1展示了当这些框架组合在一起时从高层看是什么样子。


图1用Struts, Spring, 和 Hibernate框架构建的概览

应用程序的分层 (Application Layering)
大多数不复杂的web应用都能被分成至少4个各负其责的层次。这些层次是:表现层(presentation)、持久层(persistence)、业务层(business)、领域模型层(domain model)。每层在应用程序中都有明确的责任,不应该和其它层混淆功能。每一应用层应该彼此独立但要给他们之间放一个通讯接口。让我们从审视各个层开始,讨论这些层应该提供什么和不应该提供什么。



表现层 (The Presentation Layer)

  
  在一个典型的web应用的一端是表现层。很多Java开发者也理解Struts所提供的。然而,太常见的是,他们把像业务逻辑之类的耦合的代码放进了一个org.apache.struts.Action。所以,让我们在像Struts这样一个框架应该提供什么上取得一致意见。这儿是Struts负责的:

为用户管理请求和响应;
提供一个控制器(controller)代理调用业务逻辑和其它上层处理;
处理从其它层掷出给一个Struts Action的异常;
为显示提供一个模型;
执行用户接口(UI)验证。


这儿是一些经常用Struts编写的但是却不应该和Struts表现层相伴的项目:
直接和数据库通讯,比如JDBC调用;
业务逻辑和与你的应用程序相关的验证;
事务管理;
在表现层中引入这种代码将导致典型耦合(type coupling)和讨厌的维护。


持久层 (The Persistence Layer )
在典型web应用的另一端是持久层。这通常是使事情迅速失控的地方。开发者低估了构建他们自己的持久层框架的挑战性。一般来说,机构内部自己写的持久层不仅需要大量的开发时间,而且还经常缺少功能和变得难以控制。有几个开源的“对象-关系映射”(ORM)框架非常解决问题。尤其是,Hibernate框架为 java提供了"对象-关系持久化"(object-to-relational persistence)机制和查询服务。Hibernate对那些已经熟悉了SQL和JDBC API的Java开发者有一个适中的学习曲线。Hibernate持久对象是基于简单旧式Java对象(POJO)和Java集合(Java collections)。此外,使用Hibernate并不妨碍你正在使用的IDE。下面的列表包含了你该写在一个持久层框架里的代码类型:

查询相关的信息成为对象。Hibernate通过一种叫作HQL的面向对象(OO)的查询语言或者使用条件表达式API(expressive criteria API)来做这个事情。 HQL非常类似于SQL– 只是把SQL里的table和columns用Object和它的fields代替。有一些新的专用的HQL语言成分要学;不过,它们容易理解而且文档做得好。HQL是一种使用来查询对象的自然语言,花很小的代价就能学习它。

保存、更新、删除储存在数据库中的信息。

像Hibernate这样的高级“对象-关系”映射(object-to-relational mapping)框架提供对大多数主流SQL数据库的支持,它们支持“父/子”(parent/child)关系、事务处理、继承和多态。


这儿是一些应该在持久层里被避免的项目:


业务逻辑应该在你的应用的一个高一些的层次里。持久层里仅仅允许数据存取操作。

你不应该把持久层逻辑(persistence logic)和你的表现层逻辑(presentation logic)搅在一起。避免像JSPs或基于servlet的类这些表现层组件里的逻辑和数据存取直接通讯。通过把持久层逻辑隔离进它自己的层,应用程序变得易于修改而不会影响在其它层的代码。例如:Hebernate能够被其它持久层框架或者API代替而不会修改在其它任何层的代码。


业务层(The Business Layer)

在一个典型的web应用程序的中间的组件是业务层或服务层。从编码的视角来看,这个服务层是最容易被忽视的一层。不难在用户接口(UI)层或者持久层里找到散布在其中的这种类型的代码。这不是正确的地方,因为这导致了应用程序的紧耦合,这样一来,随着时间推移代码将很难维护。幸好,针对这一问题有好几种 Frameworks存在。在这个领域两个最流行的框架是Spring和PicoContainer,它们叫作微容器(microcontainers),你可以不费力不费神的把你的对象连在一起。所有这些框架都工作在一个简单的叫作“依赖注入”(dependency injection)(也通称“控制反转”(inversion of control))的概念上。这篇文章将着眼于Spring的为指定的配置参数通过bean属性的setter注入(setter injection)的使用。Spring也提供了一个构建器注入(constructor injection)的复杂形式作为setter注入的一个替代。对象们被一个简单的XML文件连在一起,这个XML文件含有到像事务管理器(transaction management handler)、对象工厂(object factories)、包含业务逻辑的服务对象(service objects)、和数据存取对象(DAO)这些对象的引用(references)。

这篇文章的后面将用例子来把Spring使用这些概念的方法说得更清楚一些。业务层应该负责下面这些事情:
处理应用程序的业务逻辑和业务验证;
管理事务;
预留和其它层交互的接口;
管理业务层对象之间的依赖;
增加在表现层和持久层之间的灵活性,使它们互不直接通讯;
从表现层中提供一个上下文(context)给业务层获得业务服务(business services );
管理从业务逻辑到持久层的实现。


领域模型层 (The Domain Model Layer )
最后,因为我们讨论的是一个不是很复杂的、基于web的应用程序,我们需要一组能在不同的层之间移动的对象。领域对象层由那些代表现实世界中的业务对象的对象们组成,比如:一份订单(Order)、订单项(OrderLineItem)、产品(Product)等等。这个层让开发者停止建立和维护不必要的数据传输对象(或者叫作DTOs),来匹配他们的领域对象。例如,Hibernate允许你把数据库信息读进领域对象(domain objects)的一个对象图,这样你可以在连接断开的情况下把这些数据显示到UI层。那些对象也能被更新和送回到持久层并在数据库里更新。而且,你不必把对象转化成DTOs,因为DTOs在不同的应用层间移动,可能在转换中丢失。这个模型使得Java开发者自然地以一种面向对象的风格和对象打交道,没有附加的编码。


结合一个简单的例子
  既然我们已经从一个高的层次上理解了这些组件,现在就让我们开始实践吧。在这个例子中,我们还是将合并Struts、Spring、Hibernate框架。每一个这些框架在一篇文章中都有太多的细节覆盖到。这篇文章将用一个简单的例子代码展示怎样把它们结合在一起,而不是进入每个框架的许多细节。示例应用程序将示范一个请求怎样跨越每一层被服务的。这个示例应用程序的一个用户能保存一个订单到数据库中和查看一个在数据库中存在的订单。进一步的增强可以使用户更新或删除一个存在的订单。  

你可以下载这个应用的源码(http://www.onjava.com/onjava/2004/04/07/examples/wiring.zip)。


  因为领域对象(domain objects)将和每一层交互,我们将首先创建它们。这些对象将使我们定义什么应该被持久化,什么业务逻辑应该被提供,和哪种表现接口应该被设计。然后,我们将配置持久层和用Hibernate为我们的领域对象(domain objects)定义“对象-关系”映射(object-to-relational mappings)。然后,我们将定义和配置我们的业务对象(business objects)。在有了这些组件后,我们就能讨论用Spring把这些层连在一起。最后,我们将提供一个表现层(presentation layer),它知道怎样和业务服务层(business service layer)交流和知道怎样处理从其它层产生的异常(exceptions)。

领域对象层(Domain Object Layer)
因为这些对象将和所有层交互,这也许是一个开始编码的好地方。这个简单的领域模型将包括一个代表一份订单(order)的对象和一个代表一个订单项(line item for an order)的对象。订单(order)对象将和一组订单项(a collection of line item)对象有一对多(one-to-many)的关系。例子代码在领域层有两个简单的对象:
com.meagle.bo.Order.java: 包括一份订单(oder)的概要信息;
com.meagle.bo.OrderLineItem.java: 包括一份订单(order)的详细信息;
考虑一下为你的对象选择包名,它将反映你的应用程序是怎样分层的。例如:简单应用的领域对象(domain objects)可以放进com.meagle.bo包[译者注:bo-business object?]。更多专门的领域对象将放入在com.meagle.bo下面的子包里。业务逻辑在com.meagle.service包里开始打包, DAO对象放进com.meagle.service.dao.hibernate包。对于forms和actions的表现类(presentation classes)分别放入com.meagle.action 和 com.meagle.forms包。准确的包命名为你的类提供的功能提供一个清楚的区分,使当故障维护时更易于维护,和当给应用程序增加新的类或包时提供一致性。


持久层配置(Persistence Layer Configuration)
用Hibernate 设置持久层涉及到几个步骤。第一步是进行配置持久化我们的领域业务对象(domain business objects )。因为我们用于领域对象(domain objects )持久化的Hibernate和POJOs一起工作( 此句原文:Since Hibernate works with POJOs we will use our domain objects for persistence.),因此,订单和订单项对象包括的所有的字段的都需要提供getter和setter方法。订单对象将包括像ID、用户名、合计、和订单项这样一些字段的标准的JavaBean格式的setter和getter方法。订单项对象将同样的用JavaBean的格式为它的字段设置 setter和getter方法。
  Hibernate在XML文件里映射领域对象到关系数据库。订单和订单项对象将有两个映射文件来表达这种映射。有像XDoclet(http://xdoclet.sourceforge.net/)这样的工具来帮助这种映射。Hibernate将映射领域对象到这些文件:
Order.hbm.xml
OrderLineItem.hbm.xml
你可以在 WebContent/WEB-INF/classes/com/meagle/bo目录里找到这些生成的文件。配置Hibernate SessionFactory(http: //www.hibernate.org/hib_docs/api/net/sf/hibernate/SessionFactory.html)使它知道是在和哪个数据库通信,使用哪个数据源或连接池,加载哪些持久对象。SessionFactory提供的Session(http: //www.hibernate.org/hib_docs/api/net/sf/hibernate/Session.html)对象是Java对象和像选取、保存、更新、删除对象这样一些持久化功能间的翻译接口。我们将在后面的部分讨论Hibernate操作Session对象需要的 SessionFactory配置。                                                                                                                                                                                                                                                                                                                                   
业务层配置(Business Layer Configuration )
  既然我们已经有了领域对象(domain objects),我们需要有业务服务对象来执行应用逻辑、执行向持久层的调用、获得从用户接口层(UI layer)的请求、处理事务、处理异常。为了将所有这些连接起来并且易于管理,我们将使用Spring框架的bean管理方面(bean management aspect)。Spring使用“控制反转”(IoC),或者“setter依赖注入”来把这些对象连好,这些对象在一个外部的XML文件中被引用。 “控制反转”是一个简单的概念,它允许对象接受其它的在一个高一些的层次被创建的对象。使用这种方法,你的对象从必须创建其它对象中解放出来并降低对象耦合。

  这儿是个不使用IoC的对象创建它的从属对象( object creating its dependencies without IoC)的例子,这导致紧的对象耦合:

    image
  图2:没有使用IoC的对象组织。对象A创建对象B和C。

  这儿是一个使用IoC的例子,它允许对象在一个高一些层次被创建和传进另外的对象,所以另外的对象能直接使用现成的对象·[译者注:另外的对象不必再亲自创建这些要使用的对象](allows objects to be created at higher levels and passed into objects so that they can use the implementations directly):

    image
  图3:对象使用IoC组织。对象A包含setter方法,它们接受到对象B和C的接口。这也可以用对象A里的接受对象B和C的构建器完成。

建立我们的业务服务对象(Building Our Business Service Objects)
  我们将在我们的业务对象中使用的setter方法接受的是接口,这些接口允许对象的松散定义的实现,这些对象将被设置或者注入。在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化。当我们在这篇文章的例子中使用Hibernate( While the examples in this article use Hibernate),我们可以容易的转换到一个不同的持久框架的实现,通知Spring使用新的实现的DAO对象。你能明白编程到接口和使用“依赖注入”模式是怎样宽松耦合你的业务逻辑和你的持久化机制的。
  这儿是业务服务对象的接口,它是一个DAO对象依赖的桩。(Here is the interface for the business service object that is stubbed for a DAO object dependency: )

public interface IOrderService { 
  public abstract Order saveNewOrder(Order order)
    throws OrderException,
           OrderMinimumAmountException;

  public abstract List findOrderByUser(
                                     String user)
                           throws OrderException;

  public abstract Order findOrderById(int id)
                           throws OrderException;

  public abstract void setOrderDAO(
                             IOrderDAO orderDAO);
}



  注意上面的代码有一个为DAO对象准备的setter方法。这儿没有一个getOrderDAO方法因为它不是必要的,因为不太有从外面访问连着的 OrderDAO对象的需要。DAO对象将被用来和我们的持久层沟通。我们将用Spring把业务服务对象和DAO对象连在一起。因为我们编码到接口,我们不会紧耦合实现。

下一步是写我们的DAO实现对象。因为Spring有内建的对Hibernate的支持,这个例子DAO将继承 HibernateDaoSupport(http: //www.springframework.org/docs/api/org/springframework/orm/hibernate/support/HibernateDaoSupport.html)类,这使得我们容易取得一个到HibernateTemplate(http: //www.springframework.org/docs/api/org/springframework/orm/hibernate/HibernateTemplate.html)类的引用,HibernateTemplate是一个帮助类,它能简化Hibernate Session的编码和处理HibernateExceptions。这儿是DAO的接口:

public interface IOrderDAO { 

  public abstract Order findOrderById(
                                    final int id);

  public abstract List findOrdersPlaceByUser(
                           final String placedBy);

  public abstract Order saveOrder(
                               final Order order);
}



  我们还有两个对象要和我们的业务层连在一起。这包括HibernateSessionFactory和一个TransactionManager对象。这在Spring配置文件里直接完成。Spring提供一个HibernateTransactionManager(http: //www.springframework.org/docs/api/org/springframework/orm/hibernate/HibernateTransactionManager.html),它将从工厂绑定一个Hibernate Session到一个线程来支持事务(见ThreadLocal(http: //java.sun.com/j2se/1.4.2/docs/api/java/lang/ThreadLocal.html)获取更多的信息)。这儿是HibernateSessionFactory和HibernateTransactionManager的Spring配置。

<bean id="mySessionFactory" 
       class="org.springframework.orm.hibernate.
              LocalSessionFactoryBean">
  <property name="mappingResources">
    <list>
      <value>
        com/meagle/bo/Order.hbm.xml
      </value>
      <value>
        com/meagle/bo/OrderLineItem.hbm.xml
      </value>
    </list>
  </property>

  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">
        net.sf.hibernate.dialect.MySQLDialect
      </prop>
      <prop key="hibernate.show_sql">
        false
      </prop>
      <prop key="hibernate.proxool.xml">
        C:/MyWebApps/.../WEB-INF/proxool.xml
      </prop>
      <prop key="hibernate.proxool.pool_alias">
          spring
      </prop>
    </props>
  </property>
</bean>

<!-- Transaction manager for a single Hibernate
SessionFactory (alternative to JTA) -->
<bean id="myTransactionManager"
         class="org.
                springframework.
                orm.
                hibernate.
                HibernateTransactionManager">
  <property name="sessionFactory">
    <ref local="mySessionFactory"/>
  </property>
  </bean>
2005年12月23日

摘要:经常看见有人还在不厌其烦的用冒泡(最常见!)或是交换做排序,实际上用几行代码就可以既快又好地实现排序,不论是简单类型还是类,数组还是Java聚集(Collection)

 

简单类型的排序

简单类型不外是byte, char, short, int, long, float, double等数据类型, 这些类型不能放在聚集中,只能使用数组。java.util.Arrays方法提供了对这些类型的sort方法(实际上还有很多其他有用的方法),下面是对一个简单的int数组排序:

       int[] arr = {2, 3, 1,10,7,4};

 

       System.out.print("before sort: ");

       for (int i = 0; i< arr.length; i++)

           System.out.print(arr[i] + " ");

       System.out.println();      

 

       Arrays.sort(arr);

       System.out.print("after sort: ");

       for (int i = 0; i< arr.length; i++)

           System.out.print(arr[i] + " ");

       System.out.println();      

输出结果:

before sort: 2 3 1 10 7 4

after sort: 1 2 3 4 7 10

我们看到排序结果是按照升序排列的,下面的排序都是如此。

 

对象的排序

对象可以放在数组里,同样调用Arrays.sort(Object[] arr)即可;也可以放到聚集里,用java.util.Collectionssort(List list)。注意不是list必须实现List接口而不仅仅是Collection接口。

但是这个类必须实现了java.lang.Comparable接口。这个接口只有一个方法:int compartTo(Object o),当本对象比传入的对象大时,返回一个正整数。 以类Programmer为例:

class Programmer implements Comparable{

    private String name;

    private String language;

    private double pay;

   

    public Programmer(String name, String language, double pay) {

       this.name = name;

       this.language = language;

       this.pay = pay;

    }

 

    public int compareTo(Object o) {

       Programmer other = (Programmer)o;

       return (int)pay - (int)other.pay;

    }

 

    public String toString(){

       return "{name: " + name + ", language: " + language + ", money: " + pay + "}";

    }

}

对其进行排序:

       ArrayList list = new ArrayList();

       list.add(new Programmer("张三", "C", 12000));

       list.add(new Programmer("李四", "Java", 200));

       list.add(new Programmer("王五", "C++", 5000));

       list.add(new Programmer("钱六", "VB", 3000));

       System.out.println("before sort: " + list);

       Collections.sort(list);

       System.out.println("after sort: " + list);   

输出:

before sort: [{name: 张三, language: C, money: 12000.0}, {name: 李四, language: Java, money: 200.0}, {name: 王五, language: C++, money: 5000.0}, {name: 钱六, language: VB, money: 3000.0}]

after sort: [{name: 李四, language: Java, money: 200.0}, {name: 钱六, language: VB, money: 3000.0}, {name: 王五, language: C++, money: 5000.0}, {name: 张三, language: C, money: 12000.0}]

 

够简单吧!查查Comparablejavadoc可以知道,有很多类已经实现了该接口,因此对这些类的排序几行代码就可以搞定。

最近看C#发现其中用System.Array.sort对数组排序,适用于所有实现了IComparable接口的对象,看来微软的借鉴能力还真是强啊!

 

对已有类进行排序

上面的方法有一个问题,就是一个类已经存在了,并且没有实现Comparable接口,使用一个子类进行封装?很麻烦(你可以对下面的例子试试)。还有一种情况就是对一个类没法实现多种排序。以File类为例,它实现了Comparable接口,但是是按照名称排序的。如果要按照大小排序,或者按修改时间排序呢?对这两种情况,使用java.util包的Comparator接口:

Arrays.sort(Object[] arr, Comparator com)

Collections.sort(Object[] arr, Comparator com)

Comparator接口的方法:

public int compare(Object o1, Object o2) o1o2大时返回一个正整数

public boolean equals(Object obj)  判断obj与这个Comparator是否同一个对象

下面使用Comparator对文件实现了按文件大小或修改时间排序:

class FileUtils {

    static class CompratorByLastModified implements Comparator {

       public int compare(Object o1, Object o2) {

           File file1 = (File)o1;

           File file2 = (File)o2;

           long diff = file1.lastModified() - file2.lastModified();

           if (diff > 0)

              return 1;

           else if (diff == 0)

              return 0;

           else

              return -1;

       }

      

       public boolean equals(Object obj){

           return true;  //简单做法

       }

    }

 

    static class CompratorBySize implements Comparator {

       public int compare(Object o1, Object o2) {

           File file1 = (File)o1;

           File file2 = (File)o2;

           long diff = file1.length() - file2.length();

           if (diff > 0)

              return 1;

           else if (diff == 0)

              return 0;

           else

              return -1;

       }

      

       public boolean equals(Object obj){

           return true;  //简单做法

       }

    }

 

}

调用的示例:

       File dir = new File("C:\\temp");

       File[] files = dir.listFiles();

 

       System.out.print("before sort: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

      

       Arrays.sort(files);

       System.out.print("sort by name: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

 

       Arrays.sort(files, new FileUtils.CompratorBySize());

       System.out.print("sort by size: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

 

       Arrays.sort(files, new FileUtils.CompratorByLastModified());

       System.out.print("sort by last modified: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

      

自己找个目录试一下吧。用这些Java类库中的方法,一般情况下应该是不用自己写排序算法了吧?

 

最后附上完整代码占点版面:

TestSort.java

import java.io.*;

import java.util.*;

 

public class TestSort {

 

    public static void main(String[] args) {

       sortSimpleType();

       sortComparable();

       sortComparator();

    }

   

    public static void sortSimpleType() {

       int[] arr = {2, 3, 1,10,7,4};

 

       System.out.print("before sort: ");

       for (int i = 0; i< arr.length; i++)

           System.out.print(arr[i] + " ");

       System.out.println();      

 

       Arrays.sort(arr);

       System.out.print("after sort: ");

       for (int i = 0; i< arr.length; i++)

           System.out.print(arr[i] + " ");

       System.out.println();      

    }

 

    public static void sortComparable() {

       ArrayList list = new ArrayList();

       list.add(new Programmer("张三", "C", 12000));

       list.add(new Programmer("李四", "Java", 200));

       list.add(new Programmer("王五", "C++", 5000));

       list.add(new Programmer("钱六", "VB", 3000));

       System.out.println("before sort: " + list);

       Collections.sort(list);

       System.out.println("after sort: " + list);   

    }

   

    public static void sortComparator() {

       File dir = new File("C:\\temp");

       File[] files = dir.listFiles();

 

       System.out.print("before sort: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

      

       Arrays.sort(files);

       System.out.print("sort by name: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

 

       Arrays.sort(files, new FileUtils.CompratorBySize());

       System.out.print("sort by size: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

 

       Arrays.sort(files, new FileUtils.CompratorByLastModified());

       System.out.print("sort by last modified: ");

       for (int i = 0; i< files.length; i++)

           System.out.print(files[i] + " ");

       System.out.println();      

      

    }

}

 

class Programmer implements Comparable{

    private String name;

    private String language;

    private double pay;

   

    public Programmer(String name, String language, double pay) {

       this.name = name;

       this.language = language;

       this.pay = pay;

    }

 

    public int compareTo(Object o) {

       Programmer other = (Programmer)o;

       return (int)pay - (int)other.pay;

    }

 

    public String toString(){

       return "{name: " + name + ", language: " + language + ", money: " + pay + "}";

    }

}

 

class FileUtils {

    static class CompratorByLastModified implements Comparator {

       public int compare(Object o1, Object o2) {

           File file1 = (File)o1;

           File file2 = (File)o2;

           long diff = file1.lastModified() - file2.lastModified();

           if (diff > 0)

              return 1;

           else if (diff == 0)

              return 0;

           else

              return -1;

       }

      

       public boolean equals(Object obj){

           return true;  //简单做法

       }

    }

 

    static class CompratorBySize implements Comparator {

       public int compare(Object o1, Object o2) {

           File file1 = (File)o1;

           File file2 = (File)o2;

           long diff = file1.length() - file2.length();

           if (diff > 0)

              return 1;

           else if (diff == 0)

              return 0;

           else

              return -1;

       }

      

       public boolean equals(Object obj){

           return true;  //简单做法

       }

    }

 

}

2005年12月17日

http://www.duduwolf.com/post/webdev_by_ajax.asp

众所周知,异步交互、JavaScript脚本和XML封装数据是AJAX的三大特征。其实,在实际应用中,不需要牢牢套死这三条大律,在我看来,AJAX – X,即去掉用XML封装数据,也不失为一种好的设计思路,如果应用恰当,更显轻盈步伐和巧妙思路。

一般读取AJAX返回的XML结构的数据时使用XMLHttp的responseXML对象属性,同时,XMLHttp也提供了另外一个属性,即ResponseText,通过这个属性,XMLHttp可以接受来自服务器的文本结构的字符串信息。去掉XML的AJAX可以使用ResponseText这个对象属性,很灵活的操控返回数据的格式,可以自定义格式,比如我通常喜欢用c语言的那种文件流方式定义返回的字符串结构,有文件头和具体的文件信息实体,文件头分为状态信息以及文件字符长度,我摒弃了文件字符长度的定义,规定死接受的ResponseTex字符串中的第一位为状态码,比如设定常量值0表示一起正常,非0的数字表示不正常,甚至有错误等。如果有非0值,程序自动取第二位起到257位(长度为256)的字符串组成为状态信息,从258位开始到末尾的字符串就是服务器返回的正常结果信息。
substring(0,1)取状态码
substring(1,256)取服务器错误信息(错误信息不够256位用空格补齐,取到数据后进行Trim处理)
substring(256,末尾)取服务器返回的数据信息
三次substring即完成了一个简单但完整的交互工作。比起XML解析组件来说要快的多。

用ResponseText比封装为XML处理数据快和简单是一个原因,另一个原因是可操控性更大更灵活,打开Google Suggest,在搜索框输入字符可以给你给出拼写提示,Suggest就是应用了AJAX技术,不过它在从服务器返回数据时并没有使用XML封装,也没有自定义ResponseText格式,而是直接将返回代码组织成js脚本,通过浏览器返回后直接执行,如eval(XMLHttp.ResponseText)这样的方式进行执行,http://www.google.com/complete/search?hl=en&js=true&qu=ajax 通过这个链接你可以看到Suggest利用AJAX得到的返回数据,此页面是在Google Suggest的搜索框中输入"AJAX"后得系统动态返回的数据。

sendRPCDone(frameElement, "ajax", new Array("ajax", "ajax amsterdam", "ajax fc", "ajax ontario", "ajax grips", "ajax football club", "ajax public library", "ajax football", "ajax soccer", "ajax pickering transit"), new Array("3,840,000 results", "502,000 results", "710,000 results", "275,000 results", "8,860 results", "573,000 results", "40,500 results", "454,000 results", "437,000 results", "10,700 results"), new Array(""));

浏览器段拿到这段代码后直接eval就可以了,至于sendRPCDone这个函数,那当然得实现定义后并装载到页面中啦。XMLHttp这个名字以XML开头,让很多人禁锢了思想和创意,完全抛弃X,你也可以做出纯AJAX的实例来。

当然,对于大型系统来讲,为了保持数据接口的一致和整齐,还是用XML来传递更严谨更统一点,听说微软已经发起了重写XML Parse组件的号召,估计下一个版本的XMLHttp还是DOMParser还是MSXML2.DOMDocument都会大大提高效率,减少资源占用的。