<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    stone2083

    #

    Struts2.1.6 StrutsPrepareAndExecuteFilter bug

    在用strust2.1.6做小項目,結果居然發現在post數據的時候,居然有亂碼。
    自認為對編碼也算了解,立馬check應用的content type,struts2配置的struts.locale,struts.i18n.encoding,沒錯,都是統一使用了UTF-8。
    那是為什么呢?沒辦法,只能debug應用,結果發現:
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

            HttpServletRequest request 
    = (HttpServletRequest) req;
            HttpServletResponse response 
    = (HttpServletResponse) res;

            
    try {
                prepare.createActionContext(request, response);
                prepare.assignDispatcherToThread();
                prepare.setEncodingAndLocale(request, response);
                request 
    = prepare.wrapRequest(request);
                ActionMapping mapping 
    = prepare.findActionMapping(request, response);
                
    if (mapping == null) {
                    
    boolean handled = execute.executeStaticResourceRequest(request, response);
                    
    if (!handled) {
                        chain.doFilter(request, response);
                    }
                } 
    else {
                    execute.executeAction(request, response, mapping);
                }
            } 
    finally {
                prepare.cleanupRequest(request);
            }
        }
    看到沒?
    1) prepare.createActionContext(request, response);
    2)
    prepare.setEncodingAndLocale(request, response);
    setEncodingAndLocale居然在createActionContext之后,在沒有設置正確的encoding之前,解析request中的parameters,能成嗎?

    無奈之下,只能暫時用
    CharacterEncodingFilter這個filter設置request的character,猥瑣地臨時解決問題。

    今天打算向Struts提交bug的時候,發現該bug在2.1.7版本中被修復,詳見:https://issues.apache.org/struts/browse/WW-3075%3Bjsessionid=3EAC5B44A949CA77B4471AA0D45754E9?page=com.atlassian.jira.plugin.ext.subversion%3Asubversion-commits-tabpanel

    哎,在使用2.1.7之前,先用CharacterEncodingFilter吧 :)


    posted @ 2009-06-08 20:37 stone2083 閱讀(3997) | 評論 (3)編輯 收藏

    事件消息通知系統

    在SOA架構中,JMS協議中的“點對點”消息方式,已經能夠很好的支持1對1系統之間的通訊;
    但是,類似事件消息的通知(應用產生一個事件消息,其他多個系統做相應處理),理論上JMS協議中的“訂閱”方式,能夠支持此類場景,不過協議說:在消息產生通知訂閱者的時候,如果某個訂閱者系統不在線,則消息丟失--此訂閱者接受不到消息。
    并且,始終抱著對客戶端簡單,友好的態度,我希望client(應用)本身只要發出事件消息,并不需要去關注消息通知哪些訂閱者,而這一切,應該由“事件消息通知系統”,代為完成。

    經過昨天晚上空閑時間的思考,大概設計了“事件消息通知系統”的概念模型。(此概念模型,基于IP,路由,DNS思考得來)
    詳見下圖:


    事件消息通知系統


    概念解釋:
    1)Application:業務應用,一旦有事件消息產生,不再關注需要發送到哪些目的地,只需要統一發送到Event Message Center;

    2)Event Message Center:一切事件消息的暫存地;

    3)Event Message Router:事件消息路由器,根據Event Message Configuration Center(事件消息配置中心,即事件消息路由配置中心),將不同的消息,路由分發到不同的訂閱者目的地;

    4)Event Message Configuration Center:事件消息配置中心,即事件消息路由配置中心;

    5)Event Message Registrar:事件消息登記中心,通過GUI界面,將Event Message Configuration Center中的路由信息展現給用戶,并且允許用戶進行事件消息路由信息的配置;

    6)Event Message Subscription:事件消息訂閱中心,同一類事件消息的暫存地;

    7)Event Message Subscription Distributer:事件消息訂閱分發者,根據Event Message Subscription Configuration Center(事件消息訂閱配置中心)的配置信息,講事件消息分發到不同的訂閱者目的地;

    8)Event Message Subscription Configuration Center:事件消息訂閱配置中心,即事件消息訂閱分發路由信息配置;

    9)Event Message Subscription Registrar:事件消息訂閱登記中心,通過GUI界面,將 Event Message Subscription Configuration Center中的分發路由信息展現給用戶,并且允許用戶進行事件消息訂閱路由信息的配置;

    10)Event Message Destination:事件消息目的地;

    11)Event Message Consumer:不同訂閱者的消費端


    寫下此隨筆,僅僅把把自己對“事件消息系統”的感觀認識記錄。
    由于思考和整理時間很短,此概念模型存在很多缺陷之處,還望大家多多指點。

    posted @ 2009-05-07 21:49 stone2083 閱讀(2777) | 評論 (2)編輯 收藏

    最近加班統計

    最近是進入公司來,遇到的第三次瘋狂加班時期,記錄如下:
    04/09 -- 凌晨1點;
    04/10 -- 通宵;
    04/11 -- 晚上23點;
    04/12 -- 凌晨12點;
    04/13 -- 晚上22點;
    04/14 -- 晚上23點;
    04/15 -- 凌晨3點;
    04/16 -- 晚上22點;
    04/17 -- 凌晨3點;
    04/18 -- 通宵到第二天12點;

    沒有參與項目前期設計工作,導致一些接口設計存在問題,項目上線后,瘋狂暴露問題.
    結果俺成了救火隊員:
    分析錯誤(救火之前還不清楚接口邏輯,通過分析錯誤日志,慢慢了解一些邏輯和實現)
    做數據訂正;
    重新設計新接口實現;
    晚上還要留下來監控任務執行情況--光靠SA監控已經不夠了;

    上一次連續通宵加班,是由于和美國Verisign合作,做域名項目,由于有時差,只能晚上干活,到也也心甘情愿;
    而這次連續加班,完全是被不切實際的"Dead Line"項目整死的.

    忍不住抱怨下 :(

    posted @ 2009-04-15 18:54 stone2083 閱讀(331) | 評論 (0)編輯 收藏

    基于java實現的多層目錄結構

         摘要: 最近在做小需求的時候,需要用到目錄樹,特地寫了一個基于java的實現。 由于需求原因,目前只實現了讀部分的功能--如何將平面節點build成樹。動態新增,刪除等功能尚未實現。 目錄結構概念: Node:目錄節點,具備節點屬性信息 NodeStore:平面目錄節點持久化接口,提供方法如下: public List<T> findByType(String t...  閱讀全文

    posted @ 2009-02-28 21:25 stone2083 閱讀(2532) | 評論 (3)編輯 收藏

    Velocity之EventHandler

    Velocity在渲染頁面的時候,提供了不同的EventHanlder,供開發者callback。
    本文簡要說明下,在Velocity1.6.1版本下,不同EventHanlder的作用:

    EventHandler(接口):
    Base interface for all event handlers
    僅僅是一個事件偵聽標志



    IncludeEventHandler
    (接口):
    Event handler for include type directives (e.g. #include(), #parse()) Allows the developer to modify the path of the resource returned.
    在使用#include(),#parse()語法的時候,允許開發修改include或者parse文件的路徑(一般用于資源找不到的情況)
    IncludeNotFound(IncludeEventHandler實現類):

    Simple event handler that checks to see if an included page is available. If not, it includes a designated replacement page instead.By default, the name of the replacement page is "notfound.vm", however this page name can be changed by setting the Velocity property eventhandler.include.notfound, for example:

    eventhandler.include.notfound = error.vm

    當使用#include(),#parse()語法的時候,如果提供的資源文件找不到,則默認使用notfound.vm模板代替。

    開發者可以通過設置eventhandler.include.notfound屬性,修改替代模板的路徑。

    IncludeRelativePath(IncludeEventHandler實現類):

    Event handler that looks for included files relative to the path of the current template. The handler assumes that paths are separated by a forward slash "/" or backwards slash "\".

    使用相對路徑方式,尋找#include或者#parse()中指定的資源文件



    InvalidReferenceEventHandler(接口):

    Event handler called when an invalid reference is encountered. Allows the application to report errors or substitute return values

    當渲染頁面的時候,一旦遇到非法的reference,就會觸發此事件。開發者可以偵聽此事件,用于錯誤的報告,或者修改返回的內容。

    ReportInvalidReferences(InvalidReferenceEventHandler實現類):

    Use this event handler to flag invalid references.

    使用這個實現類用于標志非法的references。修改eventhandler.invalidreference.exception屬性,可以在捕捉到第一個非法references的時候,停止模板的渲染。



    MethodExceptionEventHandler(接口):

    Event handler called when a method throws an exception. This gives the application a chance to deal with it and either return something nice, or throw. Please return what you want rendered into the output stream.

    渲染模板,一旦發現調用的方法拋出異常的時候,就會觸發此事件。允許開發者處理這個異常,輸出友好信息或者拋出異常。必須返回一個值用于模板的渲染。

    PrintExceptions(MethodExceptionEventHandler實現類):

    Simple event handler that renders method exceptions in the page rather than throwing the exception. Useful for debugging.

    By default this event handler renders the exception name only. To include both the exception name and the message, set the property eventhandler.methodexception.message to true. To render the stack trace, set the property eventhandler.methodexception.stacktrace to true.

    模板渲染時,遇到方法異常,輸出異常名,而不是拋出這個異常。對于調式,非常有幫助。

    通過eventhandler.methodexception.messageeventhandler.methodexception.stacktrace屬性的設置,可以輸出異常message和stacktrace.



    NullSetEventHandler(接口):

    Event handler called when the RHS of #set is null. Lets an app approve / veto writing a log message based on the specific reference.

    當使用#set()語法,設置一個null值的時候,會觸發此事件。--目前Velocity官方沒有提供默認實現。



    ReferenceInsertionEventHandler(接口):

    Reference 'Stream insertion' event handler. Called with object that will be inserted into stream via value.toString(). Please return an Object that will toString() nicely

    當渲染變量(reference)的時候,就會觸發此事件。允許開發者返回更加友好的值--一般用于內容的escape,比如HtmlEscape等。

    EscapeHtmlReference(ReferenceInsertionEventHandler實現類):

    html escape

    EscapeJavaScriptReference(ReferenceInsertionEventHandler實現類):

    javascript escape

    EscapeSqlReference(ReferenceInsertionEventHandler實現類):

    sql escape

    EscapeXmlReference(ReferenceInsertionEventHandler實現類):

    xml escape


    以上是Velocity組件中提供的EventHandler介紹。下面寫一個簡單的例子來說明EventHandler的使用。

    模擬需求,假如輸出的內容帶有html標簽,而輸出的內容需要過濾這些標簽。如果我們手工對輸出變量通過StringEscapeUtils.escapeHtml()來實現,則太過繁瑣。所以,我們就可以使用Velocity中的EscapeHtmlReference。demo代碼如下:

    VelocityEngine ve = new VelocityEngine();
    EventCartridge eventCartridge 
    = new EventCartridge();
    eventCartridge.addEventHandler(
    new EscapeHtmlReference());

    Context context 
    = new VelocityContext();
    context.put(
    "name""<table></table>");
    eventCartridge.attachToContext(context);

    StringWriter writer 
    = new StringWriter();
    ve.mergeTemplate(VM_LOCATION, 
    "utf-8", context, writer);
    System.out.println(
    "================================");
    System.out.println(writer.toString());
    System.out.println(
    "================================");


    模板文件中,僅僅為 $name

    則輸出內容如下:

    ================================
    &lt;table&gt;&lt;/table&gt;
    ================================

    posted @ 2009-02-05 22:26 stone2083 閱讀(2624) | 評論 (0)編輯 收藏

    ibatis支持枚舉類型

    很多應用中,數據庫表結構都會存在一些狀態字段。在關系性數據庫中,一般會用VARCHAR類型。使用ibatis的應用,傳統做法,往往會使用String的屬性,與之對應。
    例如一張member表,結構設計如下:

    其中status為狀態字段。

    ibatis中,使用class MemberPO 與之mapping,設計往往如下:
    public class MemberPO implements Serializable {
        
    private Integer id;
        
    private String loginId;
        
    private String password;
        
    private String name;
        
    private String profile;
        
    private Date gmtCreated;
        
    private Date gmtModified;
        
    private String status;

       
    //getter/setters

    缺點:
    1)不直觀,沒人會知道status具體有哪些值。在缺乏文檔,并且歷史悠久的系統中,只能使用“select distinct(status) from member”,才能得到想要的數據。如果是在千萬級數據中,代價太大了;
    2)類型不安全,如果有人不小心拼寫錯誤,將會導致錯誤狀態。假設上面列子中,status只允許ENABLED/DISABLED,如果一不小心,memberPO.setStatus("ENABLEDD"),那么將會造成臟數據。

    既然jdk5之后,引入了enum,是否可以讓ibatis支持enum類型呢?事實上,最新的ibatis版本,已經支持enum類型(本文使用的是2.3.4.726版本--mvn repsitory上最新的版本)。
    以上代碼可以修改成:
    1)Status類:
    public enum Status {

        
    /** enabled */
        ENABLED,

        
    /** disabled */
        DISABLED;

    2)MemberPO類:
    public class MemberPO implements Serializable {
        
    private Integer id;
        
    private String loginId;
        
    private String password;
        
    private String name;
        
    private String profile;
        
    private Date gmtCreated;
        
    private Date gmtModified;
        
    private Status status;

        
    //getter/setters

    除此之外,其他均無需改動。
    為什么呢?ibatis如何知道VARCHAR/Enum的mapping呢?
    看過ibatis源碼的同學,知道,ibatis是通過jdbcType/javaType得到對應的TypeHandler做mapping處理的。ibatis有基本類型的TypeHandler,比如StringTypeHandler,IntegerTypeHandler等等。在最新版本中,為了支持enum,增加了一個EnumTypeHandler。

    并且在TypeHandlerFactory中,加了對enum類型的判斷,請看:
    public TypeHandler getTypeHandler(Class type, String jdbcType) {
        Map jdbcHandlerMap 
    = (Map) typeHandlerMap.get(type);
        TypeHandler handler 
    = null;
        
    if (jdbcHandlerMap != null) {
          handler 
    = (TypeHandler) jdbcHandlerMap.get(jdbcType);
          
    if (handler == null) {
            handler 
    = (TypeHandler) jdbcHandlerMap.get(null);
          }
        }
        
    if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
          handler 
    = new EnumTypeHandler(type);
        }
        
    return handler;
      }
    ibatis使用了取巧的方法,當取不到基本類型的handler時候,判斷javaType是否是Enum類型--Enum.class.isAssignableFrom(type),如果是,則使用 EnumTypeHandler進行mapping處理。

    為什么說它取巧,原因是早期ibatis設計過程中,自定義的接口無法得到具體的java class type。故早期的ibatis中,要實現對enmu的支持,非常苦難。而新版本中,為了達到這個功能,作者直接修改了TypeHandlerFactory的實現,打了一個補丁,如下:
    if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
          handler 
    = new EnumTypeHandler(type);
    }
    這個設計有悖于和早前的設計思想。早期,TypeHandler都是通過public void register(Class type, String jdbcType, TypeHandler handler)方式事先注冊到factory中的,而這次,是在運行期,通過new方法動態得到EnumTypeHandler。
    當然,新版本ibatis能支持enum,已經是一件開心的事情了。

    Status枚舉類除了描述狀態,就足夠了嗎?回想起很多應用,我是做web開發的,在view層(velocity,jsp,等),見多了類似這樣的代碼:
    #if($member.getStatus()==Status.ENABLED)開通#elseif($member.getStatus()==Status.DISABLED)關閉#end

    <select>
      
    <option value="ENABLED" #if($member.getStatus()==Status.ENABLED) selected="selected"#end >開通</option>
      
    <option value="DISABLED" #if($member.getStatus()==Status.DISABLED) selected="selected"#end >關閉</option>
    </select>

    web層需要多少個頁面,就需要維護多少份這樣的代碼;以后每添加/刪除一種狀態,多個地方都需要修改,還要擔心邏輯不一致。

    而事實上,關于狀態的信息描述,按照職責分,就應該由枚舉類來維護:
    1)制定一個接口,EnumDescription.java
    public interface EnumDescription {

        
    public String getDescription();

    }
    2)寫一個ResourceBundleUtil.java,通過Properties文件得到描述信息:
    public class ResourceBundleUtil {

        
    private ResourceBundle resourceBundle;

        
    public ResourceBundleUtil(String resource) {
        
    this.resourceBundle = ResourceBundle.getBundle(resource);
        }

        
    public ResourceBundleUtil(String resource, Locale locale) {
        
    this.resourceBundle = ResourceBundle.getBundle(resource, locale);
        }

        
    public String getProperty(String key) {
        
    return resourceBundle.getString(key);
        }

    }
    3)Status等枚舉類實現EnumDescription:
    public enum Status implements EnumDescription {

        
    /** enabled */
        ENABLED,

        
    /** disabled */
        DISABLED;

        
    private static ResourceBundleUtil util = new ResourceBundleUtil(Status.class.getName());

        
    public String getDescription() {
           
    return util.getProperty(this.toString());
        }

    }

    這樣,有什么好處:
    1)通過Properties文件,支持國際化。
    2)描述信息統一由自己來維護,方便維護,并且顯示層邏輯簡化,如:
    $member.getStatus().getDescription()

    <select>
      #foreach($status in $Status.values())
        
    <option value="$status" #if($member.getStatus()==$status)selected="selected"#end >$status.getDescription()</option>
      #end
    </select>

    ##############################################################################
    那么使用老版本ibatis的客戶怎么辦呢?就像我們公司使用ibatis 2.3.0,難道只能眼饞著?解決方案:
    1)升級到最新版本。 :)
    2)ibatis提供了TypeHandler/TypeHandlerCallback接口,針對每種枚舉類型,寫相應的TypeHandler/TypeHandlerCallback的接口實現即可--工作量大,重復的勞動力。
    主要是早期ibatis TypeHandler無法得到javaType類型,無法從jdbc value轉成對應的枚舉。在我看來,TypeHandler是作mapping用的,它至少有權知道javaType。
    3)實現偽枚舉類型(允許繼承)來實現狀態類型安全,而拋棄jdk5的方式--不方便日后升級。


    不知道大家是否還有更好的方案?

    本文涉及演示代碼如下:
    演示代碼
    workspace file encoding:utf-8
    build tool: maven
    repository:spring/2.5.5;ibatis/2.3.4.726

    posted @ 2008-11-05 23:08 stone2083 閱讀(6731) | 評論 (2)編輯 收藏

    ajax原理簡介以及簡單demo演示

    如今web應用上,ajax技術是大行其道。
    ajax框架層出不窮,prototype,dojo,jquery,mootools,dwr,buffalo,ext,yui,spry。。。
    ajax框架的出現,在提升開發生產效率的同時,也讓不少同學不明其內在原理,僅僅成為了某些框架的使用者。
    (對于產品生產是好事,對于技術追求是壞事)

    本文不涉及任何ajax框架的使用,本文僅通過一個模擬需求,在不使用任何ajax框架的前提下,以demo演示的方式,
    向大家介紹ajax的原理以及應用場景。

    ajax全稱是:Asynchronous JavaScript And XML。
    其本意是,通過javascript技術(JavaScript),通過異步http請求方式(Asynchronous),得到XML文本內容(XML)之后,通過javascript技術局部刷新web頁面內容。
    從廣義的概念看,只要符合“異步請求,局部刷新web頁面”的技術,都可以成為ajax。
    未必一定要使用javascript,一般情況下,大多數client端腳本代碼都可以;返回內容也未必一定要是xml,目前json格式,更為流行。

    如何異步請求內容呢?
    以javascript代碼作演示,如下:
    function xmlhttpPost(url,func) {
        
    var xmlHttpReq = false;
        
    var self = this;
        
    // Mozilla/Safari
        if (window.XMLHttpRequest) {
            self.xmlHttpReq 
    = new XMLHttpRequest();
        }
        
    // IE
        else if (window.ActiveXObject) {
            self.xmlHttpReq 
    = new ActiveXObject("Microsoft.XMLHTTP");
        }
        self.xmlHttpReq.open('POST', url, 
    true);
        self.xmlHttpReq.setRequestHeader('Content
    -Type', 'application/x-www-form-urlencoded');
        self.xmlHttpReq.onreadystatechange 
    = function() {
            
    if (self.xmlHttpReq.readyState == 4) {
                func(self.xmlHttpReq.responseText);
            }
        }
        self.xmlHttpReq.send(
    null);
      }
    參數一,url:表明異步請求的資源地址
    參數二,func:表明請求結束后,采用什么函數對請求結果內容進行回調處理

    其實,這一個js代碼,就詮釋了ajax的全部含義--異步請求資源,將得到的資源內容,使用指定的function進行處理。
    所以,ajax很簡單,大家千萬別被如今層出不窮的ajax框架給嚇怕了。要了解ajax的原理,就只要參看這段代碼即可。
    如今的一些框架,僅僅在此基礎上,是封裝了一些公用的函數,方便開發人員調用。(當然,說說簡單,其實所謂的這些函數,大大方便了開發人員使用ajax技術。具體請參看ajax framework的官方介紹。)

    特別說明:這個xmlhttpost方法改進了simple-ajax。在原基礎上,將回調方法作為參數傳遞。


    解釋了原理性的內容之后,接下來,以一個模擬的應用場景,demo說明ajax的使用,以及它的主要應用場景。
    模擬場景:
    目錄選擇,即當選擇一個目錄的時候,需要顯示這個目錄下的所有子目錄。

    首先,我們來虛擬一個目錄結構,如下:


    那么,要實現目錄選擇,有三個方式:
    1)頁面初始化的時候,服務端將所有的目錄信息都put到頁面中。
       優點:選擇操作簡單,有了全部的目錄信息,做選擇操作,都可以使用js完成,無需和服務端進行交互
       缺點:當目錄信息很大的時候,比如有上萬個節點,整個目錄信息有1m左右大小,那么要渲染這個頁面,估計得20秒左右(視網速)
    并且,很可能用戶僅僅只要選擇有限的幾個節點就可以,比如上萬個節點中選擇6-7個節點,那么浪費太大了;
    2)頁面初始化的時候,服務端將當前需要的節點信息put到頁面上,一旦有選擇操作,重新刷新頁面。
       優點:選擇操作簡單,對于節點信息,每次取需要的內容,不存在浪費現象
       缺點:每次都要刷新整個頁面,除節點信息外,其他不變的東西都需要重新從服務端取,增加無謂的消耗。
    3)頁面初始化的時候,服務端將當前需要的節點信息put到頁面上,一旦有選擇操作,只刷新節點相關的內容;
       優點:每次只load需要的信息,局部刷新頁面內容,不存在任何浪費現象
       缺點:需要異步請求數據,每次請求都需要和服務器交互,選擇操作稍顯復雜(異步請求,局部刷新)

    通過這三種方式做對比,發現ajax主要適用的場景如下:
    1)整體內容量大(幾百k,幾m,甚至幾十m),而頁面只需要其中一小部分信息即可;
    2)數據顯示,只涉及一個頁面中部分數據信息的變動;

    特別說明:至于使用ajax性能如何,需要對1,3兩個情況做性能測試,權衡使用。

    針對第三種方案,
    首先需要一個取節點資源的url,
    演示代碼中,為了演示方便,使用php語言,而非使用主要語言java;
    tree_node.php
    <?php
    $id =  $_GET['id']; 
    if("1" == $id) {
      
    echo("{\"id\":1,\"parentId\":-1,\"name\":\"1-1\",\"children\":[{\"id\":2,\"name\":\"2-1\"},{\"id\":3,\"name\":\"2-2\"},{\"id\":4,\"name\":\"2-3\"}]}");
    else if("2" == $id) {
      
    echo("{\"id\":2,\"parentId\":1,\"name\":\"2-1\",\"children\":[]}");
    else if("3" == $id) {
      
    echo("{\"id\":3,\"parentId\":1,\"name\":\"2-2\",\"children\":[]}");
    else if("4" == $id) {
      
    echo("{\"id\":4,\"parentId\":1,\"name\":\"2-3\",\"children\":[{\"id\":5,\"name\":\"3-1\"},{\"id\":6,\"name\":\"3-2\"}]}");
    else if("5" == $id) {
      
    echo("{\"id\":5,\"parentId\":4,\"name\":\"3-1\",\"children\":[]}");
    else if("6" == $id) {
      
    echo("{\"id\":6,\"parentId\":4,\"name\":\"3-2\",\"children\":[{\"id\":7,\"name\":\"4-1\"}]}");
    else if("7" == $id) {
      
    echo("{\"id\":7,\"parentId\":6,\"name\":\"4-1\",\"children\":[]}");
    else {
      
    echo("");
    }
    ?>
    該文件中,寫死了目錄結構(一般情況下,往往根據樹對象,動態取得需要的節點)。


    通過js,動態請求節點信息,部分刷新頁面內容:
     <script type="text/javascript">
        
    //模擬需求js
        var nodeSelect = function(text) {
          
    var tree = toJsonObje(text);
          
    var options = document.getElementById("tree").options;
          options.length 
    = 0;
          options.add(
    new Option("請選擇","-1"));
          
    if(tree == null) {
            
    return;
          } 
    else {
            
    var children = tree.children;
            
    for(i = 0; i < children.length; i++) {
              
    var child = children[i];
              options.add(
    new Option(child.name,child.id));
            }
            
    if(tree.parentId != "-1") {
              options.add(
    new Option("上一級",tree.parentId));
            }
          }
          document.getElementById(
    "l").innerHTML = "當前位置:" + tree.name;
        }

        
    function nodeSelectAjax(id) {
          
    var TREE_NODE_URL = "tree_node.php";
          
    var url = TREE_NODE_URL + "?id=" + id;
          xmlhttpPost(url,nodeSelect);
        }   
      
    </script>
    nodeSelectAjax,異步請求節點資源
    nodeSelect,回調函數,根據請求信息,局部刷新頁面


    至于請求資源信息格式,任何方式都可以,只要client端能解析就行。
    目前json格式,比較流行。
    最后,附上java使用json庫,生成json格式的方法:
    JSONObject node = new JSONObject();
    node.put(
    "id"1);
    node.put(
    "parentId"-1);
    node.put(
    "name""1-1");
    JSONArray children 
    = new JSONArray();
    JSONObject c1 
    = new JSONObject();
    c1.put(
    "id"2);
    c1.put(
    "name""2-1");
    JSONObject c2 
    = new JSONObject();
    c2.put(
    "id"3);
    c2.put(
    "name""2-2");
    children.put(c1);
    children.put(c2);
    node.put(
    "children", children);
    System.out.println(node.toString());



    ajax demo
    工程文件編碼:utf-8
    工程運行:http server with php supported
    ubuntu firefox下測試通過


    其他:
    不知道是不是ie的bug,居然不支持 select.innerHTML = value的方式
    只能通過select.options.add(new Option("content","value") 動態往select中添加選項。

    posted @ 2008-09-21 19:05 stone2083 閱讀(1856) | 評論 (2)編輯 收藏

    部門合作 和 數學公式

    萬物之間都有聯系,這句話一點都沒錯,一組數學公式恰好可以反應常見的幾種部門合作情況。

    假設:
    1) 一部門按質量交付產品的權值為 1
    2) 質量每提升一分,權值加0.1
    3) 質量每降低一分,權值減0.1
    4) 某公司產品線需要4個部門合作

    結果:
    情況A:每個部門,都按質量完成自己的工作,則
    產品總體質量 = 1 * 1 * 1 * 1 = 1
    符合公司產品質量要求

    情況B:每個部門交付的產品,均只有要求的9成,則
    產品總體質量 = 0.9 * 0.9  * 0.9 * 0.9 = 0.6561
    產品質量僅僅為要求的6成,剛好達到及格線而已
    每個部門完成9分,似乎并不是很差,但是整體產品,卻只達到及格而已

    情況C:每個部門交付的產品,均超質量1分,則
    產品總體質量 = 1.1 * 1.1 * 1.1 * 1.1 = 1.4641
    產品質量為要求的1.4641倍,優質產品.
    每個部門多完成1.分,似乎額外工作量并不是很大,但是最終產品卻能成為優質產品

    情況D:兩個部門交付的產品,只有要求的9成,另兩個部門為了彌補產品的缺陷,努力做到超質量1分,則
    產品總體質量 = 0.9 * 0.9 * 1.1 * 1.1 = 0.9801
    和情況A同樣的工作強度,結果還是沒有達到產品質量要求

    情況E:兩個部門交付的產品,只有要求的8成,另兩個部門為了彌補產品的缺陷,努力做到超質量2分,則
    產品總體質量 = 0.8 * 0.8 * 1.2 * 1.2 = 0.9216
    和合情D同樣的工作強度,結果總體質量比情況D更差

    總結:
    一個公司,只要每個部門對工作懈怠一點,公司產品就會和產品要求差很多,部門越多,差距越大
    一個公司,只要每個部門對工作要求更嚴格一點,公司產品質量卻遠遠高于產品要求,部門越多,質量越高
    一個公司,如果有部門A對工作不到位,與其讓后續其他部門加強工作強度,彌補部門A的產品缺陷,還不如加強對部門A的教育和培訓,讓其交付符合質量要求的產品.


    公司產品開發歷經PD(產品設計規劃部門),RA(需求分析),Developer(開發工程師),Test(測試部門)四個環節.

    在自己經歷的一些項目中,因為時間關系等原因,往往出現:
    PD提交的需求邏輯流程有點問題;提交的demo不符合要求;RA UC中僅僅考慮主流程,遺忘一些分支流程;開發對原有代碼不敢做重構,搭積木似的添加功能,埋地雷等等...幾個環節下來,試問最終產品質量如何??

    我僅僅是一名普通的程序員,原先在面對邏輯不完善,demo不符合等等情況,都試圖通過自己最大的努力,來彌補這些缺陷,但是當明白上述公式之后,我越來越希望每個部門都能盡自己最大的努力,來交付高質量的產品.

    一個成功的公司,不可能儀仗個人英雄,而是需要各個部門協同工作.

    posted @ 2008-07-20 20:46 stone2083 閱讀(345) | 評論 (0)編輯 收藏

    單元測試分享

    最近,在小組內部做了一次關于“單元測試”的分享。把自己兩年來做單元測試遇到的問題和對單元測試的認識做了一次總結和討論。

    本文不會詳細地講述分享的內容,僅僅是ppt的大綱顯示:

    使用單元測試前提:
    最小的成本,換來最大的收益

    單元測試目的

    1)測試代碼錯誤(?) -- 不是主要目的
    2)便于重構時的測試
    3)改善既有代碼的設計

    分享核心
    1)如何脫離“webx“--做隔離測試
    2)dal/biz/web 層如何做單元測試
    3)如何通過改善代碼設計,更方便測試

    dal層(數據庫訪問層)特點:
    1)獨立,邏輯單一,對表做操作
    2)業務相對比較穩定
    3)采用ibatis,寫sql的方式

    dal層測試方式
    1)壓根兒就不需要測試
    2)僅僅配置spring bean,通過日志打印的方式(無法達到自檢)
    3) 自檢方式 -- AbstractTransactionalDataSourceSpringContextTests (高成本,不輕易使用)
    需要權衡

    biz(業務層)測試方式--分BO和AO:
    BO:(即所謂的Service/Manager)
    AO:(一個UseCase對應的業務流)
    隔離 + 設計 (主要通過代碼演示--見附件)

    單元測試的缺點
    專注于單一業務測試,銜接點容易出錯

    解決方案
    接口輸入輸出明確
    集成測試

    web層:
    集成測試的入口

    分享文檔和演示代碼 (ppt是在ubuntu下制作,可能效果并不是很好)

    備注:
    自己對單元測試了解也比較膚淺,歡迎一起探討

    posted @ 2008-07-17 22:04 stone2083 閱讀(450) | 評論 (0)編輯 收藏

    ibatis--部分更新表記錄字段的方法

    使用ibatis,如果要更新表記錄,一般常用的做法就是,查找出記錄,然后修改部分字段,進行update操作.
    以member表為例:
    MemberDO member = memberDAO.findById(1);
    member.setName(
    "stone");
    memberDAO.update(member);

    這種是最常用的方法.不錯,在很多應用場景下,這么干,完全沒有問題.
    但是(往往存在但是),如果member表中存在一個或者多個text(或者blob)字段.難道僅僅為了更新一個name字段,需要重新update那些本不需要更新的text/blob字段嗎?

    于是乎,人們又想出了一個辦法,參數采用map,把需要更新的字段put到map中,
    演示代碼(省略ibatis的sqlmap文件):
    Map<String,Object> map = new HashMap<String,Object>();
    map.put(
    "name","stone");
    memberDAO.update(map);

    沒錯,這種方法不錯.需要更新哪些字段,只需要動態put到map中就可以.
    但是,對于這種方法,需要調用更新的地方,需要手工維護數據庫的字段名,如果在put的時候,一不小心拼錯字段名,那么更新操作肯定和你預計的會有差別.
    比如上面的代碼:
    Map<String,Object> map = new HashMap<String,Object>();
    map.put(
    "nama","stone");
    memberDAO.update(map);
    不小心把name拼成了nama,那么新的name字段就無法保存到數據庫中.試想一下,任何需要更新字段的地方,都存在拼寫錯誤的風險.

    于是乎,人們又想到了參數類,比如就把MemberDO當成參數類:
    MemberDO memberParam = new MemberDO();
    memberParam.setName(
    "stone");
    memberDAO.update(memberParam);
    sqlmap.xml如下:
    update member
    set gmt_modified = current_date
    <dynamic>
    <isNotNull property="loginId",prepend=",">
    login_id = #loginId#
    </isNotNull>
       
    <isNotNull property="name",prepend=",">
    name = #name#
    </isNotNull>
       
    </dynamic>
    where id = #id# 
    這方法貌似不錯,不會存在字段名拼寫錯誤的風險.并且需要更新哪些字段,動態set一下就可以.
    但是,如果要把某個字段設置為null,那怎么辦?那沒轍咯...(sqlmap中約定,只有不為null的時候,才更新).

    那...那...那怎么辦呢?
    貌似只有Map才能滿足需求嘛...因為sqlmap中有個
    "isPropertyAvailable"和"isNull"屬性支持.只要配合這兩個屬性,就能區分需要更新為null,還是不更新保持原字段內容.
    sqlmap文件演示:
    <isPropertyAvailable property="loginId" prepend=",">
            
    <isNotNull property="loginId">
              
    <![CDATA[
                login_id = #loginId#
              
    ]]>
            
    </isNotNull>
            
    <isNull property="loginId">
              
    <![CDATA[
                login_id = null
              
    ]]>
            
    </isNull>
    </isPropertyAvailable>
    只要map不put loginId,那么更新的時候,就不會更新這個字段,如果map.put("loginId",null),那么就會把loginId更新為null.
    看來只有map能勝任.

    不是說,使用map,維護字段內容很麻煩嘛.但是好像又只能使用它?
    于是乎,又想到了一種思路(也是本文要介紹的一個方法)
    通過方法攔截,在設置參數類的時候,把設置的屬性值put到map中.(cglib是很勝任這樣的場合的)

    首先,需要一個BaseDO.java DataObject的基類,僅僅用于維護一份Map對象.
    BaseDO.java:
    public class BaseDO implements Serializable {

        
    private static final long serialVersionUID = -315506079592557582L;

        
    private Map<String, Object> setterMap;

        
    public synchronized void initSetterMap() {
        
    if (setterMap == null) {
            setterMap 
    = new HashMap<String, Object>();
        }
        }

        
    public Map<String, Object> getSetterMap() {
        
    return setterMap;
        }

    }

    采用Cglib,寫一個對set方法的攔截器:
    SetterInterceptor.java 用于對截獲set操作,把set的對象put到map中
    public class SetterInterceptor implements MethodInterceptor {

        
    private static final String SET_METHOD = "set";

        @Override
        
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) 
    throws Throwable {
        
    // 攔截DataObject中所有的set方法,把set的屬性放入到map中
        if (method.getName().startsWith(SET_METHOD)) {
            
    if (obj instanceof BaseDO) {
            BaseDO baseDO 
    = (BaseDO) obj;
            baseDO.initSetterMap();
            String attribute 
    = StringUtils.substring(method.getName(),
                SET_METHOD.length());
            attribute 
    = StringUtils.uncapitalize(attribute);
            
    if (args != null && args.length == 1) {
                baseDO.getSetterMap().put(attribute, args[
    0]);
            }
            }
        }
        
    return proxy.invokeSuper(obj, args);
        }

    }

    寫一個創建Setter的工廠類,用于創建帶方法攔截的DataObject對象
    public class SetterFactory {

        
    private static final SetterInterceptor setterInterceptor = new SetterInterceptor();

        @SuppressWarnings(
    "unchecked")
        
    public static <extends BaseDO> T getSetterInstance(Class<T> clazz) {
        Enhancer enhancer 
    = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(setterInterceptor);
        
    return (T) enhancer.create();
        }

    }

    那么對于client調用,就非常簡單了.
    如:
    public class Client {

        
    private static final Log log = LogFactory.getLog(Client.class);

        
    private static final String APP_CONFIG_FILE = "cn/zeroall/javalab/ibatis/app.xml";

        
    public static void main(String[] args) {
        ApplicationContext ctx 
    = new ClassPathXmlApplicationContext(
            APP_CONFIG_FILE);
        MemberDAO memberDAO 
    = (MemberDAO) ctx.getBean("memberDAO");

        MemberDO setter 
    = SetterFactory.getSetterInstance(MemberDO.class);
        setter.setId(
    1);
        setter.setLoginId(
    "stone1");
        setter.setName(
    "stone1");
        memberDAO.updateById(setter);

        MemberDO member 
    = memberDAO.findById(1);
        log.info(member.getLoginId());

        }
    }

    sqlmap文件如下:
    <update id="update-by-id" parameterClass="java.util.Map">
        
    <![CDATA[
          update member
          set gmt_modified = current_date
        
    ]]>
        
    <dynamic>
          
    <isPropertyAvailable property="loginId" prepend=",">
            
    <isNotNull property="loginId">
              
    <![CDATA[
                login_id = #loginId#
              
    ]]>
            
    </isNotNull>
            
    <isNull property="loginId">
              
    <![CDATA[
                login_id = null
              
    ]]>
            
    </isNull>
          
    </isPropertyAvailable>
          
    <isPropertyAvailable property="password" prepend=",">
            
    <isNotNull property="password">
              
    <![CDATA[
                password = #password#
              
    ]]>
            
    </isNotNull>
            
    <isNull property="password">
              
    <![CDATA[
                password = null
              
    ]]>
            
    </isNull>
          
    </isPropertyAvailable>
          
    <isPropertyAvailable property="name" prepend=",">
            
    <isNotNull property="name">
              
    <![CDATA[
                name = #name#
              
    ]]>
            
    </isNotNull>
            
    <isNull property="name">
              
    <![CDATA[
                name = null
              
    ]]>
            
    </isNull>
          
    </isPropertyAvailable>
          
    <isPropertyAvailable property="profile" prepend=",">
            
    <isNotNull property="profile">
              
    <![CDATA[
                profile = #profile#
              
    ]]>
            
    </isNotNull>
            
    <isNull property="profile">
              
    <![CDATA[
                profile = null
              
    ]]>
            
    </isNull>
          
    </isPropertyAvailable>
        
    </dynamic>
        
    <![CDATA[
            where id = #id#
        
    ]]>
      
    </update>


    一旦采用了Setter對象,那么對于表記錄的更新操作,僅僅需要一個sql,就能解決.比較方便.

    附件中,把整個演示代碼附上,有興趣的朋友,可以了解下:
    采用maven構建,workspace編碼采用utf-8.數據庫采用pgsql

    demo附件

    備注:
    member表創建sql如下:
    -- Table: member

    -- DROP TABLE member;

    CREATE TABLE member
    (
      id serial 
    NOT NULL,
      login_id 
    character varying(16),
      "password" 
    character varying(16),
      "name" 
    character varying(32),
      profile 
    text,
      gmt_created 
    timestamp without time zone,
      gmt_modified 
    timestamp without time zone,
      
    CONSTRAINT member_pkey PRIMARY KEY (id)
    )
    WITH (OIDS=FALSE);
    ALTER TABLE member OWNER TO javalab;


    特別說明:
    此方法原創作者為公司同事,本文只是盜用了他的創意.


    posted @ 2008-06-26 22:46 stone2083 閱讀(7875) | 評論 (2)編輯 收藏

    僅列出標題
    共10頁: First 上一頁 2 3 4 5 6 7 8 9 10 下一頁 
    主站蜘蛛池模板: 久久久久久av无码免费看大片| 久久精品无码精品免费专区| 亚洲精品中文字幕无码蜜桃| 在线观看的免费网站无遮挡| 国产亚洲成在线播放va| 国产亚洲一区二区精品| 无码一区二区三区免费视频| 2022免费国产精品福利在线 | 亚洲精品高清在线| 久久青草免费91观看| 亚洲av无码一区二区三区四区| 亚洲精品无码永久在线观看你懂的| 国产成人无码免费看视频软件| 亚欧洲精品在线视频免费观看| 亚洲视频一区二区三区四区| 黑人大战亚洲人精品一区| 最近最新MV在线观看免费高清| XXX2高清在线观看免费视频| 亚洲色大成网站www久久九| 亚洲va无码专区国产乱码| 免费一级成人毛片| 1000部啪啪未满十八勿入免费| eeuss影院ss奇兵免费com| 亚洲人成网站免费播放| 亚洲ⅴ国产v天堂a无码二区| 亚洲Av无码国产情品久久| 免费精品国偷自产在线在线| 国内精品免费在线观看| 四虎国产精品成人免费久久 | 亚洲私人无码综合久久网| 久久国产亚洲电影天堂| 亚洲精品第一国产综合境外资源 | 亚洲AV无码乱码在线观看富二代 | 亚洲无线观看国产精品| 日韩高清免费观看| 1000部国产成人免费视频| 中国一级特黄高清免费的大片中国一级黄色片 | 亚洲国产美女精品久久| 亚洲成在人线av| 中文字幕亚洲无线码| 免费播放特黄特色毛片|