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

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

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

    狂奔 lion

    自強(qiáng)不息

    2008年9月12日

    淺談Java中的同步的方法和原理

    Java的內(nèi)存模型中Thread會(huì)附有自己的堆棧,寄存器,必要時(shí)需要和主存即heap之間同步。
    可以使用Synchornized關(guān)鍵字和Concurrent包中的Lock可以保證線程互斥和可見性。

    互斥性體現(xiàn)在類鎖或者對象鎖上,每個(gè)對象自身都包含一個(gè)監(jiān)視器,該監(jiān)視器是一個(gè)每次只能被一個(gè)線程所獲取進(jìn)入的臨界區(qū),可以通過wait和notify來退出和準(zhǔn)入臨界區(qū)。可以看出這是一個(gè)生產(chǎn)者-消費(fèi)者的模型。而Concurrent包中的Lock為了能夠獲得更好的性能和更好的擴(kuò)展性,以及不依賴于關(guān)鍵字的可讀代碼,自己實(shí)現(xiàn)了這樣一個(gè)生產(chǎn)消費(fèi)隊(duì)列,也就是AbstractQueuedSynchronizer,被稱為AQS的機(jī)制。每個(gè)Lock都內(nèi)置了一個(gè)AbstractQueuedSynchronizer。需要說明的是AbstractQueuedSynchronizer內(nèi)部實(shí)現(xiàn)采用了CAS機(jī)制,通過getState, setState, compareAndSetState訪問控制一個(gè)32bit int的形式進(jìn)行互斥。

    那么可見性是如何保證的呢?

    對于關(guān)鍵字的同步機(jī)制,其實(shí)可見性就是線程和主存之間的同步時(shí)機(jī)問題。共有4個(gè)時(shí)間點(diǎn)需要注意:
    1 獲取或釋放類鎖/對象鎖的時(shí)候。Thread保證reload/flush全部變更
    2 volatile就是flush on write或者reload on read
    3 當(dāng)線程首次訪問共享變量時(shí),可以得到最新的結(jié)果。
    題外:所以在構(gòu)造方法中公布this時(shí)很危險(xiǎn)的。簡單的說,就是構(gòu)造時(shí)不逃脫任何變量,不開啟新的線程,只做封裝。關(guān)于安全構(gòu)造,請參考
    http://www.ibm.com/developerworks/cn/java/j-jtp0618/#resources
    4 線程結(jié)束時(shí),所有變更會(huì)寫回主存

    關(guān)于Concurrent Lock如何實(shí)現(xiàn)可見性的問題,Doug Lea大俠,只在他的論文中提到,按照J(rèn)SR133,Unsafe在getState, setState, compareAndSetState時(shí)保證了線程的變量的可見性,不需要額外的volatile支持,至于具體這些native做了哪些magic就不得而知了,總之,最后的contract就是保證lock區(qū)間的共享變量可見性。開發(fā)團(tuán)隊(duì)被逼急了就這樣回答:
    There seems to be a real reluctance to explain the dirty details. I think the question was definitely understood on the concurrent interest thread, and the answer is that synchronized and concurrent locking are intended to be interchangable in terms of memory semantics when implemented correctly. The answer to matfud's question seems to be "trust us.”

    不過這個(gè)地方的確是開發(fā)團(tuán)隊(duì)給我們用戶迷惑的地方,在同樣應(yīng)用了CAS機(jī)制的Atomic類中,都內(nèi)嵌了volatile變量,但是再lock塊中,他告訴我們可以保證可見性。

    感興趣的同學(xué)可以下面的兩個(gè)thread和Doug Lea的thesis:
    http://altair.cs.oswego.edu/pipermail/concurrency-interest/2005-June/001587.html
    http://forums.sun.com/thread.jspa?threadID=631014&start=15&tstart=0
    http://gee.cs.oswego.edu/dl/papers/aqs.pdf

    posted @ 2010-07-09 19:49 楊一 閱讀(1859) | 評論 (0)編輯 收藏

    commons-net FTPClient API存取設(shè)計(jì)

    文件系統(tǒng)無非就是文件的存取和組織結(jié)構(gòu)。
    訪問一個(gè)文件系統(tǒng)的API也應(yīng)該是寫,讀,定位方法(Pathname?/URI?)

    FTPClient針對文件的保存和獲取各提供了兩個(gè)方法,分別是:

    public boolean storeFile(String remote, InputStream local)
    public OutputStream storeFileStream(String remote)

    public boolean retrieveFile(String remote, OutputStream local)
    public InputStream retrieveFileStream(String remote)

     

    兩個(gè)方法貌似相同,實(shí)際不同,返回流的那個(gè)因?yàn)椴荒荞R上處理流,所以需要用戶手工調(diào)用completePendingCommand,而另一個(gè)傳遞流進(jìn)去的則不需要。可能有同學(xué)已經(jīng)遇到過這個(gè)問題了,讀寫第一個(gè)文件時(shí)總是正確的,當(dāng)相同API讀寫第二個(gè)文件時(shí),block住了。這是因?yàn)镕TPClient要求在進(jìn)行流操作之后執(zhí)行completePendingCommand,以確保流處理完畢,因?yàn)榱魈幚聿皇羌磿r(shí)的,所以也沒有辦法不手工調(diào)用completePendingCommand。問題是開發(fā)者把不返回流的方法末尾加上了completePendingCommand,如果不看代碼可能根本不知道。
    文檔上說:

         * There are a few FTPClient methods that do not complete the
         
    * entire sequence of FTP commands to complete a transaction.  These
         
    * commands require some action by the programmer after the reception
         
    * of a positive intermediate command.  After the programmer's code
         * completes its actions, it must call this method to receive
         
    * the completion reply from the server and verify the success of the
         
    * entire transaction.


    但是這樣仍然還是讓人有點(diǎn)困惑,為什么都是存儲(chǔ)/讀取的方法,有時(shí)候要調(diào)用completePendingCommand,有時(shí)候不調(diào)用?更嚴(yán)重的問題是completePendingCommand調(diào)用了getReply,如果一個(gè)命令通過socket stream傳了過去但是沒有g(shù)etReply,即沒有completePendingCommand,那么下次發(fā)命令時(shí),將會(huì)受到本次返回碼的干擾,得到無效的響應(yīng)。而如果在completePendingCommand之后又進(jìn)行了一次無辜的completePendingCommand,那么因?yàn)镕TP Server上沒有Reply了,就會(huì)block。所以completePendingCommand并不是可以隨意添加的。

    現(xiàn)在出現(xiàn)了兩個(gè)問題:
    1 completePendingCommand很容易多出來或遺漏
    2 顯式調(diào)用completePendingCommand暴露了底層實(shí)現(xiàn),給用戶帶來不便,用戶只想要InputStream或者OutputStream

    為了解決這個(gè)問題,可以對InputStream進(jìn)行擴(kuò)展,建立一個(gè)ReplyOnCloseInputStream,如下:

    private static ReplyOnCloseInputStream extends InputStream{
      
    //
      public ReplyOnCloseInputStream(InputStream is, FTPClient c){
        
    //
      }

      
    //
      @override
      
    public void close(){
        
    if(c.completePendingCommand){
          is.close();
        }
    else{
          
    //throw Exception
        }

      }

    }
     
    //
    return new ReplyOnCloseInputStream(is, client);


    這樣封裝之后,F(xiàn)TPClient的用戶只需要正常在處理完流之后關(guān)閉即可,而不必暴露實(shí)現(xiàn)細(xì)節(jié)。保存文件也可以用相同的方法封裝OutputStream。

    posted @ 2010-07-07 23:08 楊一 閱讀(3464) | 評論 (1)編輯 收藏

    關(guān)于ThreadLocal的內(nèi)存泄露

    ThreadLocal是一種confinement,confinement和local及immutable都是線程安全的(如果JVM可信的話)。因?yàn)閷γ總€(gè)線程和value之間存在hash表,而線程數(shù)量未知,從表象來看ThreadLocal會(huì)存在內(nèi)存泄露,讀了代碼,發(fā)現(xiàn)實(shí)際上也可能會(huì)內(nèi)存泄露。

    事實(shí)上每個(gè)Thread實(shí)例都具備一個(gè)ThreadLocal的map,以ThreadLocal Instance為key,以綁定的Object為Value。而這個(gè)map不是普通的map,它是在ThreadLocal中定義的,它和普通map的最大區(qū)別就是它的Entry是針對ThreadLocal弱引用的,即當(dāng)外部ThreadLocal引用為空時(shí),map就可以把ThreadLocal交給GC回收,從而得到一個(gè)null的key。

    這個(gè)threadlocal內(nèi)部的map在Thread實(shí)例內(nèi)部維護(hù)了ThreadLocal Instance和bind value之間的關(guān)系,這個(gè)map有threshold,當(dāng)超過threshold時(shí),map會(huì)首先檢查內(nèi)部的ThreadLocal(前文說過,map是弱引用可以釋放)是否為null,如果存在null,那么釋放引用給gc,這樣保留了位置給新的線程。如果不存在slate threadlocal,那么double threshold。除此之外,還有兩個(gè)機(jī)會(huì)釋放掉已經(jīng)廢棄的threadlocal占用的內(nèi)存,一是當(dāng)hash算法得到的table index剛好是一個(gè)null key的threadlocal時(shí),直接用新的threadlocal替換掉已經(jīng)廢棄的。另外每次在map中新建一個(gè)entry時(shí)(即沒有和用過的或未清理的entry命中時(shí)),會(huì)調(diào)用cleanSomeSlots來遍歷清理空間。此外,當(dāng)Thread本身銷毀時(shí),這個(gè)map也一定被銷毀了(map在Thread之內(nèi)),這樣內(nèi)部所有綁定到該線程的ThreadLocal的Object Value因?yàn)闆]有引用繼續(xù)保持,所以被銷毀。

    從上可以看出Java已經(jīng)充分考慮了時(shí)間和空間的權(quán)衡,但是因?yàn)橹脼閚ull的threadlocal對應(yīng)的Object Value無法及時(shí)回收。map只有到達(dá)threshold時(shí)或添加entry時(shí)才做檢查,不似gc是定時(shí)檢查,不過我們可以手工輪詢檢查,顯式調(diào)用map的remove方法,及時(shí)的清理廢棄的threadlocal內(nèi)存。需要說明的是,只要不往不用的threadlocal中放入大量數(shù)據(jù),問題不大,畢竟還有回收的機(jī)制。

    綜上,廢棄threadlocal占用的內(nèi)存會(huì)在3中情況下清理:
    1 thread結(jié)束,那么與之相關(guān)的threadlocal value會(huì)被清理
    2 GC后,thread.threadlocals(map) threshold超過最大值時(shí),會(huì)清理
    3 GC后,thread.threadlocals(map) 添加新的Entry時(shí),hash算法沒有命中既有Entry時(shí),會(huì)清理

    那么何時(shí)會(huì)“內(nèi)存泄露”?當(dāng)Thread長時(shí)間不結(jié)束,存在大量廢棄的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一個(gè)廢棄ThreadLocal在map中命中)時(shí)。

    posted @ 2010-07-02 18:27 楊一 閱讀(2280) | 評論 (2)編輯 收藏

    關(guān)于軟件文檔,我的看法

    文檔應(yīng)該包括兩大部分,一部分是清晰的代碼結(jié)構(gòu)和注釋,比如Concurrent API就是這樣,還有一部分是文字文檔,包括三個(gè)小部分:一是開發(fā)文檔,應(yīng)該講架構(gòu)和功能;二是索引文檔,詳細(xì)介紹功能和參數(shù),三是用戶文檔,包括安裝和使用說明

    文檔最困難的莫過于版本的一致性,當(dāng)軟件升級后,一些obsolete的內(nèi)容和新的feature很難同步。要是架構(gòu)發(fā)生了變化,那就更困難了。一般document team都不是太精于技術(shù),所以也會(huì)產(chǎn)生一些問題。

    只能說任何事物永遠(yuǎn)都有改進(jìn)的空間,但是同樣也永遠(yuǎn)沒有達(dá)到完美的程度

    posted @ 2010-06-29 18:26 楊一 閱讀(320) | 評論 (0)編輯 收藏

    NIO學(xué)習(xí)之Web服務(wù)器示例

         摘要: 1 根據(jù)cpu core數(shù)量確定selector數(shù)量 2 用一個(gè)selector服務(wù)accept,其他selector按照core-1分配線程數(shù)運(yùn)行 3 accept selector作為生產(chǎn)者把獲得的請求放入隊(duì)列 4 某個(gè)selector作為消費(fèi)者從blocking queue中取出請求socket channel,并向自己注冊 5 當(dāng)獲得read信號時(shí),selector建立工作...  閱讀全文

    posted @ 2010-06-25 19:19 楊一 閱讀(1967) | 評論 (0)編輯 收藏

    多線程的知識(shí)

    多線程的優(yōu)點(diǎn):
    1 多核利用
    2 為單個(gè)任務(wù)建模方便
    3 異步處理不同事件,不必盲等
    4 現(xiàn)代的UI也需要它
    風(fēng)險(xiǎn):
    1 同步變量易錯(cuò)誤
    2 因資源限制導(dǎo)致線程活躍性問題
    3 因2導(dǎo)致的性能問題
    用途:
    框架,UI,Backend
    線程安全的本質(zhì)是什么:
    并非是線程和鎖,這些只是基礎(chǔ)結(jié)構(gòu),本質(zhì)是如何控制共享變量訪問的狀態(tài)
    什么是線程安全:
    就是線程之間的執(zhí)行還沒有發(fā)生錯(cuò)誤,就是沒有發(fā)生意外
    一個(gè)線程安全的類本身封裝了對類內(nèi)部方法和變量的異步請求,調(diào)用方無需考慮線程安全問題
    無狀態(tài)的變量總是線程安全的
    原子性:
    完整執(zhí)行的單元,如不加鎖控制,則會(huì)發(fā)生競態(tài)條件,如不加鎖的懶漢單例模式,或者復(fù)合操作。
    鎖,內(nèi)在鎖,重入:
    利用synchronized關(guān)鍵字控制訪問單元,同一線程可以重入鎖內(nèi)部,避免了面向?qū)ο螽a(chǎn)生的問題。同一變量的所有出現(xiàn)場合應(yīng)該使用同一個(gè)鎖來控制。synchronized(lock)。
    即使所有方法都用synchronized控制也不能保證線程安全,它可能在調(diào)用時(shí)編程復(fù)合操作。
    活躍性和性能問題:
    過大的粒度會(huì)導(dǎo)致這個(gè)問題,用鎖進(jìn)行異步控制,導(dǎo)致了線程的順序執(zhí)行。
    簡單和性能是一對矛盾,需要適當(dāng)?shù)娜∩帷2荒茉跊]有考慮成熟的情況下,為了性能去犧牲簡潔性。
    要盡量避免耗時(shí)操作,IO和網(wǎng)絡(luò)操作中使用鎖

    posted @ 2010-06-25 19:17 楊一 閱讀(374) | 評論 (0)編輯 收藏

    Ext Store Filter的實(shí)現(xiàn)和問題

    Store包含兩個(gè)數(shù)據(jù)緩存 - snapshot和data,grid,combo等控件的顯示全部基于data,而snapshot是數(shù)據(jù)的完整緩存,當(dāng)首次應(yīng)用過濾器時(shí),snapshot從data中備份數(shù)據(jù),當(dāng)應(yīng)用過濾器時(shí),filter從snapshot獲取一份完整的數(shù)據(jù),并在其中進(jìn)行過濾,過濾后的結(jié)果形成了data并傳遞給展示,及data總是過濾后的數(shù)據(jù),而snapshot總是完整的數(shù)據(jù),不過看名字讓人誤以為它們的作用正好相反。
    相應(yīng)地,當(dāng)進(jìn)行store的增刪改時(shí),要同時(shí)維護(hù)兩個(gè)緩存。
    問題
    Store包含兩個(gè)增加Record的方法,即insert和add,其中的insert沒有更新snapshot所以當(dāng)重新應(yīng)用filter時(shí),即data被重新定義時(shí),在data中使用insert新增的記錄是無效的。
    解決方法
    用add不要用insert,如果用insert,記得把數(shù)據(jù)寫進(jìn)snapshot: store.snapshot.addAll(records)

    posted @ 2010-06-25 19:16 楊一 閱讀(1284) | 評論 (0)編輯 收藏

    Ext中Combo組件的聯(lián)動(dòng)封裝

         摘要: 在Extjs中構(gòu)造N級聯(lián)動(dòng)下拉的麻煩不少,需定制下拉數(shù)據(jù)并設(shè)定響應(yīng)事件。通過對Combo集合的封裝,無需自己配置Combo,只需設(shè)定數(shù)據(jù)和關(guān)聯(lián)層級,即可自動(dòng)構(gòu)造出一組支持正向和逆向過濾的聯(lián)動(dòng)下拉并獲取其中某一個(gè)的實(shí)例。 如: 數(shù)據(jù): Ext.test = {};       Ext.test.lcbdata&nb...  閱讀全文

    posted @ 2010-06-25 19:14 楊一 閱讀(1306) | 評論 (0)編輯 收藏

    前端框架動(dòng)態(tài)組件和代碼生成之間的選擇

    目前主流的SSH開發(fā)架構(gòu)中,為減輕開發(fā)者工作,便于管理開發(fā)過程,往往用到一些公共代碼和組件,或者采用了基于模版的代碼生成機(jī)制,對于后臺(tái)的DAO,Service等因?yàn)榧軜?gòu)決定,代碼生成必不可少,但是在前端頁面的實(shí)現(xiàn)上,卻可以有兩種不同的思路,一種是把配置信息直接封裝成更高級別的組建,一種是進(jìn)行代碼生成。請大家討論一下這兩種方案的優(yōu)劣,這里先拋磚引玉了。

    相同點(diǎn):
    配置信息:XML OR 數(shù)據(jù)庫

    控件化:
    優(yōu)點(diǎn):
    1 易于添加公共功能
    2 修改配置數(shù)據(jù)直接生效
    3 代碼結(jié)構(gòu)清晰,對開發(fā)者友好
    缺點(diǎn):
    1 重組內(nèi)存中對象結(jié)構(gòu),性能沒有代碼生成好(但渲染時(shí)間相同)
    2 僅能控制組件自身封裝的配置,不支持個(gè)性化修改,如果配置文件不支持的參數(shù),則控件不支持
    3 必須保證每個(gè)控件一個(gè)配置

    代碼生成:
    優(yōu)點(diǎn):
    1 性能較好
    2 易于定制內(nèi)容
    3 可以只配置一個(gè)模版,然后做出多個(gè)簡單的修改
    缺點(diǎn):
    1 不能針對多個(gè)頁面同時(shí)添加公共功能
    2 業(yè)務(wù)修改需要重新生成代碼
    3 開發(fā)者需要修改自動(dòng)生成的代碼,并需要了解一些底層的實(shí)現(xiàn)結(jié)構(gòu)

    =====================20091029
    代碼生成并不能提高工作效率,尤其是針對復(fù)雜的富客戶端開發(fā)
    開發(fā)組件可提提供一種有效的選項(xiàng),但是在運(yùn)行效率和內(nèi)存處理上需要細(xì)心處理

    posted @ 2010-06-25 19:11 楊一 閱讀(453) | 評論 (0)編輯 收藏

    Javascript工作流引擎代碼及實(shí)例

     

    最近在學(xué)習(xí)jBPMJavascript,所以按照一些相關(guān)概念自己寫了下面的200行代碼的“工作流引擎”,工作流管理系統(tǒng)包含了流程定義,引擎,及應(yīng)用系統(tǒng)三個(gè)主要部分,下面的代碼實(shí)現(xiàn)了流程的分支合并,目前只支持一種環(huán)節(jié)上的遷移。拷貝到html,雙擊就可以跑起來。

     

    var workflowDef = {
             start:{
                       fn:
    "begin"//對應(yīng)處理方法可以在內(nèi)部定義,也可以在外部定義
                       next:[
    "task1","task2"]
             },
             end:
    "end",
             tasks:[{
                       id:
    "task1",
                       fn:
    function(){
                                alert(
    "執(zhí)行任務(wù)一");
                       },
                       before:
    function(){
                                alert(
    "執(zhí)行任務(wù)一前");
                       },
                       after:
    function(){
                                alert(
    "執(zhí)行任務(wù)一后");
                       },
                       next:[
    "task4","task5"]
             },{
                       id:
    "task2",
                       fn:
    function(){
                                alert(
    "執(zhí)行任務(wù)二");
                       },
                       before:
    function(){
                                alert(
    "執(zhí)行任務(wù)二前");
                       },
                       after:
    function(){
                                alert(
    "執(zhí)行任務(wù)二后");
                       },
                       next:[
    "task3"]
             },{
                       id:
    "task3",
                       fn:
    function(){
                                alert(
    "執(zhí)行任務(wù)三");
                       },
                       before:
    function(){
                                alert(
    "執(zhí)行任務(wù)三前");
                       },
                       after:
    function(){
                                alert(
    "執(zhí)行任務(wù)三后");
                       },
                       
    //定義合并的數(shù)量
                       merge: 
    3,
                       next:
    "EOWF"
             },{
                       id:
    "task4",
                       fn:
    function(){
                                alert(
    "執(zhí)行任務(wù)四");
                       },
                       before:
    function(){
                                alert(
    "執(zhí)行任務(wù)四前");
                       },
                       after:
    function(){
                                alert(
    "執(zhí)行任務(wù)四后");
                       },
                       next:[
    "task3"]
             },{
                       id:
    "task5",
                       fn:
    function(){
                                alert(
    "執(zhí)行任務(wù)五");
                       },
                       before:
    function(){
                                alert(
    "執(zhí)行任務(wù)五前");
                       },
                       after:
    function(){
                                alert(
    "執(zhí)行任務(wù)五后");
                       },
                       next:[
    "task3"]
             }]
    }

     

     

    //////////定義引擎////////////

    Yi 
    = {};
    Yi.Utils 
    = {};
    Yi.Utils.execute 
    = function(o){
             
    if(typeof o != 'function')
                       eval(o)();
             
    else
                       o();
    }
    //工作流類
    Yi.Workflow 
    = function(workflowDef){
             
    this.def = workflowDef;
             
    this.tasks = this.def.tasks;
    }
    //public按照環(huán)節(jié)id查找查找
    Yi.Workflow.prototype.findTask 
    = function(taskId){
             
    for(var i=0;i<this.tasks.length;i++){
                       
    if(this.tasks[i].id == taskId)
                                
    return this.tasks[i];
             }
    }
    //public啟動(dòng)工作流
    Yi.Workflow.prototype.start 
    = function(){
             
    this.currentTasks = [];
             Yi.Utils.execute(
    this.def.start.fn);
             
    for(var i=0;i<this.def.start.next.length;i++){
                       
    this.currentTasks[i] = this.findTask(this.def.start.next[i]);
                       Yi.Utils.execute(
    this.currentTasks[i].before);
             }
    }
    //private
    Yi.Workflow.prototype.findCurrentTaskById 
    = function(taskId){
             
    for(var i=0;i<this.currentTasks.length;i++){
                       
    if(this.currentTasks[i].id == taskId)
                                
    return this.currentTasks[i];
             }
             
    return null;
    }
    //private
    Yi.Workflow.prototype.removeFromCurrentTasks 
    = function(task){
             
    var temp = [];
             
    for(var i=0;i<this.currentTasks.length;i++){
                       
    if(!(this.currentTasks[i] == task))
                                temp.push(
    this.currentTasks[i]); 
             }
             
    this.currentTasks = temp;
             temp 
    = null;
    }
    //public觸發(fā)當(dāng)前環(huán)節(jié)
    Yi.Workflow.prototype.signal 
    = function(taskId){
             
    //只處理當(dāng)前活動(dòng)環(huán)節(jié)
             
    var task = this.findCurrentTaskById(taskId);
             
    if(task == null){
                       alert(
    "工作流未流轉(zhuǎn)到此環(huán)節(jié)!");
                       
    return;
             }
             
    //對于合并的處理
             
    if(task.merge != undefined){
                       
    if(task.merge != 0){
                                alert(
    "工作流流轉(zhuǎn)條件不充分!");
                                
    return;
                       }
    else{
                                Yi.Utils.execute(task.before);
                       }        
             }
             
    //觸發(fā)當(dāng)前環(huán)節(jié)
             Yi.Utils.execute(task.fn);
             
    //觸發(fā)后動(dòng)作
             Yi.Utils.execute(task.after);
             
    //下一步如果工作流結(jié)束
             
    if(task.next === "EOWF"){
                       Yi.Utils.execute(
    this.def.end);
                       
    delete this.currentTasks;
                       
    return;
             }
             
    //遍歷下一步環(huán)節(jié)
             
    this.removeFromCurrentTasks(task);
             
    for(var i=0;i<task.next.length;i++){
                       
    var tempTask = this.findTask(task.next[i]);
                       
    if(!tempTask.inCurrentTasks)
                                
    this.currentTasks.push(tempTask);
                       
    if(tempTask.merge != undefined){
                                tempTask.merge
    --;
                                tempTask.inCurrentTasks 
    = true;
                       }
                       
    else
                                Yi.Utils.execute(tempTask.before);
             }
    }
    //public獲取當(dāng)前的活動(dòng)環(huán)節(jié)
    Yi.Workflow.prototype.getCurrentTasks 
    = function(){
             
    return this.currentTasks;
    }
    //public獲取流程定義
    Yi.Workflow.prototype.getDef 
    = function(){
             
    return this.def;
    }

     

    ////////應(yīng)用系統(tǒng)///////////////
    var wf = new Yi.Workflow(workflowDef);
    alert(
    "啟動(dòng)工作流");
    wf.start();
    alert(
    "嘗試手工執(zhí)行任務(wù)3,返回工作流沒有流轉(zhuǎn)到這里");
    wf.signal(
    "task3");
    alert(
    "分支開始");
    alert(
    "手工執(zhí)行任務(wù)1");
    wf.signal(
    "task1");
    alert(
    "手工執(zhí)行任務(wù)2");
    wf.signal(
    "task2");
    alert(
    "手工執(zhí)行任務(wù)4");
    wf.signal(
    "task4");
    alert(
    "手工執(zhí)行任務(wù)5");
    wf.signal(
    "task5");
    alert(
    "手工執(zhí)行任務(wù)3");
    wf.signal(
    "task3");
    function begin(){
             alert(
    "流程開始,該函數(shù)在外部定義");
    }
    function end(){
             alert(
    "流程結(jié)束");
    }

    posted @ 2009-03-06 17:39 楊一 閱讀(1991) | 評論 (1)編輯 收藏

    Spring Security 2 中動(dòng)態(tài)角色權(quán)限的實(shí)現(xiàn)

     

    安全框架的主體包括兩部分即驗(yàn)權(quán)和授權(quán)。Spring Security2可以很好的實(shí)現(xiàn)這兩個(gè)過程。Spring Security2對其前身acegi最大的改進(jìn)是提供了自定義的配置標(biāo)簽,通過Security的命名空間定義了httpauthentication-provider等標(biāo)簽,這樣做的好處是極大地簡化了框架的配置,并很好地隱藏了框架實(shí)現(xiàn)的細(xì)節(jié),在配置的表述上也更清晰,總體上提高了框架的易用性。

    然而,該框架默認(rèn)的權(quán)限配置方式在xml中,又因?yàn)樾掳姹倦[藏了實(shí)現(xiàn)細(xì)節(jié),在動(dòng)態(tài)權(quán)限的擴(kuò)展上,能力變小了。在驗(yàn)權(quán)過程中,遇到的問題不多。但在授權(quán)時(shí),如果是acegi,人們可以通過繼承AbstractFilterInvocationDefinitionSource類實(shí)現(xiàn)在授權(quán)(即資源角色和用戶角色的匹配)前,針對資源的角色的獲取。而新版本因?yàn)橛眯聵?biāo)簽進(jìn)行了整合,這個(gè)過程被默認(rèn)的類實(shí)現(xiàn)隱藏掉了,包括過濾器,資源獲取和角色定義等過程都由框架來實(shí)現(xiàn),于是很多人在使用Spring Security2時(shí)也想通過改動(dòng)DefaultFilterInvocationDefinitionSource對資源的獲取來實(shí)現(xiàn)數(shù)據(jù)庫或文件中的動(dòng)態(tài)的角色。不過這樣的改動(dòng)侵入性比較高,而且還保留了acegi的痕跡,也違背了開閉的原則。

    其實(shí),我們完全可以通過Spring Security2 accessManager提供的自定義投票機(jī)制來解決這個(gè)問題,這樣既不影響現(xiàn)有的基于URL的配置,還可以加入自己的動(dòng)態(tài)的權(quán)限配置。

             其實(shí)現(xiàn)策略如下:

    1 定義類DynamicRoleVoter實(shí)現(xiàn)AccessDecisionVoter,注入實(shí)現(xiàn)接口DynamicRoleProvider(用來定義獲取角色的方法)的提供動(dòng)態(tài)角色的類

    2 在兩個(gè)supports方法中返回true

    3 vote方法中,有三個(gè)參數(shù)(Authentication authentication, Object object,

    ConfigAttributeDefinition config) 通過第一個(gè)獲取用戶的權(quán)限集合,第二個(gè)可以獲取到資源對象,進(jìn)而通過DynamicRoleProvider獲取到角色集合進(jìn)行匹配。

    4 在配置文件中加入DynamicRoleVoter,如下:

    <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <beans:property name="decisionVoters">
    <beans:list>
    <beans:bean class="org.springframework.security.vote.RoleVoter" />
    <beans:bean class="org.springframework.security.vote.AuthenticatedVoter" />
    <beans:bean class="DynamicRoleVoter">
        
    <beans:property name="dynamicRoleProvider">
            
    <beans:ref local="dynamicRoleProvider"/>
    </beans:property>
    </beans:bean>
    </beans:list>
    </beans:property>
    </beans:bean>
    <beans:bean id=” dynamicRoleProvider” class=”…”>
        
    ……
    </beans:bean
    >

    posted @ 2009-03-04 12:55 楊一 閱讀(5074) | 評論 (0)編輯 收藏

    實(shí)現(xiàn)Ext表單對checkBoxGroup的統(tǒng)一管理

    1 對于類型是checkboxgroup的數(shù)據(jù),數(shù)據(jù)庫中保存數(shù)據(jù)的格式是value1,value2...valueN,其中1~N的數(shù)據(jù)有可能不存在,如果選中則存在,最后拼接成一個(gè)串。
    在Ext中,通過Record對象向FormPanel中的內(nèi)置對象BasicForm加載數(shù)據(jù)時(shí),采用的是setValues方法,而setValues第一步要通過Record中定義的name使用findField方法找到表單元素,遺憾的是,繼承了Field的checkboxgroup組件并不能正確的通過getName返回自身引用,所以,需要對getName方法進(jìn)行重寫,此外,為了適應(yīng)我們采用的數(shù)據(jù)格式,對于該組件的setValue(被setValues調(diào)用)和getValue(獲取到已加工的數(shù)據(jù),此事后話)也要進(jìn)行重寫。故而對于形如:
    {     
       xtype: 'checkboxgroup',   
       name: 'biztype',     
       width: 
    220,   
       columns: 
    3,   
       fieldLabel: '業(yè)務(wù)類別',   
       items: [     
           {boxLabel: '類別1', inputValue: '
    01'},     
           {boxLabel: '類別2', inputValue: '
    02'},     
           {boxLabel: '類別3', inputValue: '
    03'},     
           {boxLabel: '類別4', inputValue: '
    04'}     
           ]     
     }  

    的checkboxgroup定義,需重寫類如下:
     
    Ext.override(Ext.form.CheckboxGroup,{    
        
    //在inputValue中找到定義的內(nèi)容后,設(shè)置到items里的各個(gè)checkbox中    
        setValue : function(value){   
            
    this.items.each(function(f){   
                
    if(value.indexOf(f.inputValue) != -1){   
                    f.setValue(
    true);   
                }
    else{   
                    f.setValue(
    false);   
                }   
            });   
        },   
        
    //以value1,value2的形式拼接group內(nèi)的值   
        getValue : function(){   
            
    var re = "";   
            
    this.items.each(function(f){   
                
    if(f.getValue() == true){   
                    re 
    += f.inputValue + ",";   
                }   
            });   
            
    return re.substr(0,re.length - 1);   
        },   
        
    //在Field類中定義的getName方法不符合CheckBoxGroup中默認(rèn)的定義,因此需要重寫該方法使其可以被BasicForm找到   
        getName : function(){   
            
    return this.name;   
        }   
    });

    2 通過內(nèi)置對象basicForm的getValues方法可以獲取到一個(gè)form的完整json數(shù)據(jù),但遺憾的事,這里取到的是dom的raw數(shù)據(jù),類似emptyText的數(shù)據(jù)也會(huì)被返回,而Field的getValue方法不存在這個(gè)問題,所以如果想要返回一個(gè)非raw的json集合,可以給formpanel添加如下方法:
    getJsonValue:function(){   
        
    var param = '{';   
        
    this.getForm().items.each(function(f){   
            
    var tmp = '"' + f.getName() + '":"' + f.getValue() + '",';   
            param 
    +=  tmp;   
        });   
        param 
    = param.substr(0,param.length - 1+ '}';   
        
    return param;   
    }  

    這個(gè)方法同樣適用于上面定義的checkboxgroup,如此就可以把前后臺(tái)的數(shù)據(jù)通過json統(tǒng)一起來了

    posted @ 2009-03-04 12:50 楊一 閱讀(2235) | 評論 (0)編輯 收藏

    富客戶端技術(shù)中的JavaScript腳本國際化

         摘要: 當(dāng)前的富客戶端可以包含兩部分:分別為JSP頁面和通過富客戶端js組件(如extjs)渲染的組件化窗口頁。針對這兩部分分別做如下處理: 對于JSP頁面的部分采用JSTL標(biāo)準(zhǔn)庫的fmt標(biāo)簽,如通過: <fmt:message key="page.login.title"/>這樣的形式進(jìn)行展現(xiàn),其中message對應(yīng)的文本在服務(wù)端配置,并在web.xml中配置資源文件的位置,也可以采用s...  閱讀全文

    posted @ 2008-12-23 12:07 楊一 閱讀(2564) | 評論 (1)編輯 收藏

    windows中不能雙擊打開jar文件的解決辦法

    看此文前請保證jar包中有至少一個(gè)Main方法入口,及圖形化的界面。
    并保證META-INF/MANIFEST文件中的Main-Class已經(jīng)指向之前實(shí)現(xiàn)的main方法入口。

    最近硬盤壞了,于是重新安裝了OS,發(fā)現(xiàn)拷貝后的jdk或jre(未經(jīng)安裝的版本),不能打開jar文件執(zhí)行(jdk版本1.6_11),
    于是在打開方式中指向了javaw程序,發(fā)現(xiàn)無效,并提示"cannot find main class", 與此同時(shí)windows把jar類型的文件關(guān)聯(lián)到了
    指定的javaw程序上,上網(wǎng)找了一通,沒有人提及這個(gè)問題的解決辦法,而顯然這個(gè)問題又不是由開篇中提到的問題導(dǎo)致的。
    于是在注冊表中當(dāng)前用戶配置中刪除了當(dāng)前jar類型的定義。但是重新嘗試后依然無效。

    于是重新安裝了jdk,發(fā)現(xiàn)這次可以打開jar文件了,并且這次用來打開的程序從打開方式來看仍然是javaw。
    比較注冊表中文件類型的定義,并沒有差別。從文件夾選項(xiàng) -> 文件類型來看終于看到了差別,

    高級里面的open操作定義如下:
    "C:\Program Files\Java\jre6\bin\javaw.exe" -jar "%1" %*
    而如果我們自己選擇javaw,默認(rèn)的open操作是沒有 -jar參數(shù)的,必須手工加進(jìn)去。
    我們知道java啟動(dòng)jar包的參數(shù)是 -jar,但是記得以前javaw是可以直接打開jar的,不知什么時(shí)候起也需要帶有-jar參數(shù)了。

    所以對于一個(gè)拷貝的綠色jre只要修改一下open操作的定義就可以解決上面的問題了。

    解決了上面的問題,又產(chǎn)生了新的問題,之前選擇打開的javaw程序在打開方式中丟不掉了,比較多余,這個(gè)可以在注冊表中修改
    在HKEY_CLASSES_ROOT\Applications下面找到響應(yīng)的程序刪除就可以了,原來每次用一個(gè)程序打開一個(gè)類型的文件windows都會(huì)在
    注冊表中這個(gè)地方留下相關(guān)的記錄

    posted @ 2008-12-22 18:19 楊一 閱讀(3182) | 評論 (0)編輯 收藏

    關(guān)于文本協(xié)議中二進(jìn)制到文本的轉(zhuǎn)碼

    偶然間注意到一個(gè)困擾了我很久的問題,那就是如果我不通過Socket而通過應(yīng)用層的某種基于文本的協(xié)議,比如SOAP進(jìn)行通信的話,
    如何傳遞二進(jìn)制的數(shù)據(jù)呢?現(xiàn)在SOA,Web Service等很火,應(yīng)該會(huì)遇到這種問題吧?

    現(xiàn)在已知的方法可以通過Base64進(jìn)行編碼,其原理和方法見:
    http://baike.baidu.com/view/469071.htm

    這種方法采用了字節(jié)中的6位進(jìn)行文本轉(zhuǎn)換,并且在其他論壇上也看到了帖子說淘寶的搜索也采用了這種編碼方式進(jìn)行處理。
    但是采用了5位進(jìn)行轉(zhuǎn)換。并且大膽地給出了5位轉(zhuǎn)碼的算法,見:
    http://www.javaeye.com/topic/286240

    不過這種5位的轉(zhuǎn)換會(huì)產(chǎn)生更多多余的字節(jié),6位的轉(zhuǎn)碼充分利用了現(xiàn)今的可讀文本,可是5位卻沒有,因?yàn)?和8的最小公倍數(shù)是40,
    所以當(dāng)每轉(zhuǎn)換40位即5個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)需要8個(gè)字節(jié)來表示,這樣就多產(chǎn)生3個(gè)字節(jié),浪費(fèi)的效率是3/5, 而6位轉(zhuǎn)碼浪費(fèi)的效率是
    1/3。而且隨著字節(jié)增多,轉(zhuǎn)化效率也在下降。可見采用5位轉(zhuǎn)碼是一種既浪費(fèi)空間,又浪費(fèi)效率的解決方案。在不增加url長度的情況下充分提高效率,6位編碼是最佳的。如果可以任意的餓犧牲url長度,
    可以把0-9全部拿出來當(dāng)做標(biāo)記位,0-9不會(huì)單獨(dú)出現(xiàn),這樣一共有10*26 + 26 = 286 種可能還不包括小寫字母,
    此外還有=,+,-什么的至少256可以編碼8位的字節(jié)了,這樣處理效率就提高了。

    現(xiàn)在把問題優(yōu)化一下,人類可讀無歧義的文本碼有0-9,A-Z,a-z共62個(gè)
    設(shè)取出x個(gè)作為標(biāo)志位則(62-x) * x + (62 - x) >= 256
    解這個(gè)二元一次方程得到:
    3.366<=X<=57.634
    考慮到編碼的文本長度,取x的最小值,即 4
    最優(yōu)解:
    用0, 1, 2, 3做為標(biāo)志位
    4-9,A-Z, a-z參與編碼并與標(biāo)志位配合實(shí)現(xiàn)8位字節(jié)的文本化
    可以看到這種方法的轉(zhuǎn)碼效率會(huì)比較高,但是空間冗余大。

    此外其實(shí)可用的文本不知62個(gè),包括感嘆號等用上后補(bǔ)足64 = 2^6
    它的高位是 00
    那么只要再找到三個(gè)文本符保存其他三個(gè)高位01 10 11就可以了
    這樣的轉(zhuǎn)碼空間可以更小一些。


    想法還很不成熟,歡迎大家批評

    posted @ 2008-12-04 15:56 楊一 閱讀(1661) | 評論 (2)編輯 收藏

    JSON通用服務(wù)端處理

         摘要: 最近在學(xué)習(xí)JavaScript,發(fā)現(xiàn)不論是ext還是prototype都很推崇json這種通信協(xié)議的格式,但是這兩個(gè)框架都是比較偏前端的,和dwr不同,dwr是一個(gè)一站式的ajax框架,不僅提供了客戶端的工具方法,也包括服務(wù)端的配置和通信的處理。 而ext和prototype等僅僅設(shè)置好了json的接口并對ajax通信做了封裝,相對而言是一種比較“純粹”的AJAX實(shí)現(xiàn),當(dāng)...  閱讀全文

    posted @ 2008-11-24 18:14 楊一 閱讀(2229) | 評論 (1)編輯 收藏

    軟件開發(fā)平臺(tái)及框架的意義

    學(xué)過軟件工程的都知道,軟件產(chǎn)品的生產(chǎn)周期是一個(gè)經(jīng)歷若干階段的漫長過程,包括需求獲取 - 設(shè)計(jì) - 開發(fā) - 維護(hù)等等。

    需求階段 - 總想考慮到所有的問題,或是一切按合同辦事。但在現(xiàn)實(shí)中根本不得能,因此很多公司開始提倡“隨需而變”的能力,希望快速的響應(yīng)用戶的需求變化
    維護(hù)階段 - 總希望自己開發(fā)出來的東西一勞永逸,永遠(yuǎn)不要再產(chǎn)生任何麻煩,產(chǎn)生了麻煩也不要找到我。甚至有些項(xiàng)目組的人員開發(fā)出來一大堆不成熟的產(chǎn)品、項(xiàng)目后撒手不管,走人了事,毫無職業(yè)操守,亦是對自身行業(yè)聲譽(yù)(至少是國內(nèi)IT服務(wù)提供商聲譽(yù))的一個(gè)打擊。真正的項(xiàng)目開發(fā)不應(yīng)該這樣,一定是非常易于維護(hù)的,能夠快速地找出問題的所在,或是新需求切入點(diǎn)的所在,然后解決

     

    很明顯,前面提到的兩個(gè)問題都要也只能通過設(shè)計(jì)和開發(fā)來解決。問題來了,怎樣開發(fā)出的軟件才能快速地響應(yīng)需求,易于維護(hù)?可以有很多不相沖突的說法,比如解耦,比如通過POJO封裝數(shù)據(jù)等等。這些東西流行開來以后,很多人有疑問,為什么我的項(xiàng)目中一定要用這些框架?我不用這些框架也可以快速的開發(fā)出我要的功能,而且更加簡單,等等。如果孤立地從設(shè)計(jì)和開發(fā)的角度看這些問題,這種說法并沒有錯(cuò)誤,但是如果從整個(gè)軟件開發(fā)的生命周期來看,則不是這樣。當(dāng)然,這里還有一個(gè)是否“過度設(shè)計(jì)”的trade-off在里面,不過那又是另一個(gè)話題了。

    再說說各種各樣的平臺(tái)吧,它們和框架不同,軟件體系結(jié)構(gòu)中有一種架構(gòu)模型即層次模型,我們現(xiàn)在的TCP/IP協(xié)議棧即屬于這種模型,我們的軟件對于平臺(tái)產(chǎn)品的依賴是一種朝向穩(wěn)定的依賴,就好像我們在調(diào)試代碼時(shí)往往不會(huì)去調(diào)試操作系統(tǒng)API的bug一樣,因此在開發(fā)這種平臺(tái)層次級別的產(chǎn)品時(shí)就沒有必要再去采用那些為了保障“企業(yè)應(yīng)用”Web軟件生命周期中所采用的方法了,完全可以用最基礎(chǔ),最底層的手段。只要能夠做到高效、穩(wěn)定即可。因此,平臺(tái)中間件產(chǎn)品的開發(fā)必須和應(yīng)用軟件產(chǎn)品分開來看,雖然它們可能都在用Java這種編程語言。

    posted @ 2008-10-26 13:24 楊一 閱讀(1635) | 評論 (0)編輯 收藏

    形式化與自動(dòng)化

    本科讀書時(shí),曾聽過離散數(shù)學(xué)老師一句很精彩的論斷:“只要能夠形式化的東西,就可以自動(dòng)化”。可是今天我不談離散數(shù)學(xué),倒想說說其他不相關(guān)的東西。

    你一定聽到過“一流的企業(yè)賣標(biāo)準(zhǔn),二流的企業(yè)賣品牌,三流的企業(yè)賣產(chǎn)品”。

    什么是形式化?為什么形式化的東西就可以自動(dòng)化呢?撇開數(shù)學(xué)符號不談,對企業(yè)來說,形式化的東西可以是一些規(guī)章及做事的方法,生產(chǎn)產(chǎn)品的方法等等。為什么人民幣稍一升值,中國的中小制造型企業(yè)就要痛苦不堪?因?yàn)槲覀兠考葲]有品牌,更沒有標(biāo)準(zhǔn),拿生產(chǎn)DVD機(jī)為例,最初的90年代生產(chǎn)DVD機(jī)賣到歐美很賺,可是現(xiàn)在據(jù)說不賠錢就不錯(cuò)了,因?yàn)橐U納一大筆的專利費(fèi)。中國的大多數(shù)企業(yè)處在整個(gè)產(chǎn)業(yè)鏈的最下端,所得的利潤的大多數(shù)被其他企業(yè)剝奪了,因?yàn)槲覀冇玫氖莿e人的品牌,別人的標(biāo)準(zhǔn)。我們的公司在全球經(jīng)濟(jì)中處于“民工”的境地。

    回到我們的問題中來,一流企業(yè)所做的標(biāo)準(zhǔn),是生產(chǎn)一種產(chǎn)品的方式和規(guī)約,這一層面是抽象世界的最高層次,無法對這層抽象的產(chǎn)生進(jìn)行形式化,所以是一種創(chuàng)造性的勞動(dòng);二流企業(yè)所賣的品牌是對一種產(chǎn)品具體生產(chǎn)方法的規(guī)約,通過“模板方法模式”的應(yīng)用,這一層次的抽象的模板可以從上一個(gè)層次的形式化中自動(dòng)化,然后只需形式化具體的操作細(xì)節(jié),對于生產(chǎn)細(xì)節(jié)的形式化,也需要一定的創(chuàng)造性的勞動(dòng);三流的企業(yè)是一部機(jī)器,因?yàn)榈酱藶橹挂磺械臇|西都已經(jīng)形式化了,只需要有時(shí)間和精力的機(jī)器去自動(dòng)完成而已。

    讓我們好好想想在一個(gè)知識(shí)經(jīng)濟(jì)的社會(huì)里,什么事物是創(chuàng)造性的,是只能被形式化而不能被自動(dòng)化的。因?yàn)橹挥腥祟惖膭?chuàng)造性思想不能被機(jī)器所取代,這也是為什么機(jī)器人無法取代人類的原因。

    80年代出生的人應(yīng)該記得歷史教科書中的論述,工業(yè)革命時(shí),一些工人去砸毀機(jī)器,覺得這些機(jī)器剝奪了他們的工作。如果有一天,老板突然來告訴您:你可以離開了,請不要沮喪和懊悔,因?yàn)槟缭撘庾R(shí)到您其實(shí)就是一部機(jī)器。當(dāng)然為了防止上面悲劇的發(fā)生,早點(diǎn)去從事有創(chuàng)造性的工作吧,停止對于各種軟件自動(dòng)化輔助工具的抱怨和擔(dān)憂,勇敢地迎接明天。

    還記得小時(shí)候常看的電影《神鞭》吧!

    posted @ 2008-09-12 12:49 楊一 閱讀(1490) | 評論 (1)編輯 收藏

    <2008年9月>
    31123456
    78910111213
    14151617181920
    21222324252627
    2829301234
    567891011

    導(dǎo)航

    公告

    本人在blogjava上發(fā)表的文章及隨筆除特別聲明外均為原創(chuàng)或翻譯,作品受知識(shí)產(chǎn)權(quán)法保護(hù)并被授權(quán)遵從 知識(shí)分享協(xié)議:署名-非商業(yè)性使用-相同方式共享 歡迎轉(zhuǎn)載,請?jiān)谵D(zhuǎn)載時(shí)注明作者姓名(楊一)及出處(www.tkk7.com/yangyi)
    /////////////////////////////////////////
    我的訪問者

    常用鏈接

    留言簿(5)

    隨筆分類(55)

    隨筆檔案(55)

    相冊

    Java

    其他技術(shù)

    生活

    最新隨筆

    搜索

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    自強(qiáng)不息


    用心 - 珍惜時(shí)間,勇于創(chuàng)造
    主站蜘蛛池模板: 亚洲国产高清在线精品一区| 亚洲综合国产一区二区三区| 日韩一区二区在线免费观看 | 亚洲av色香蕉一区二区三区| 亚洲爆乳成av人在线视菜奈实| 免费看一级一级人妻片| 中文字幕在线免费看线人| 日韩精品极品视频在线观看免费| av无码国产在线看免费网站 | 国产又大又粗又长免费视频| 永久在线毛片免费观看| 亚洲日韩精品无码专区网站| 亚洲AV无码专区国产乱码4SE| 国产精品亚洲精品观看不卡| 黄色一级视频免费观看| 四虎国产精品免费永久在线| 一二三四免费观看在线视频中文版| 免费欧洲美女牲交视频| 亚洲高清视频在线观看| 亚洲精品国产高清在线观看| 国产免费网站看v片在线| 成年网站免费视频A在线双飞| 亚洲国产精品成人一区| 综合自拍亚洲综合图不卡区| 亚洲AV无码一区二区一二区| 黄色免费在线网站| 色播在线永久免费视频| 久久精品国产亚洲av麻豆| 亚洲日产乱码一二三区别| 韩日电影在线播放免费版| 成人性生免费视频| 国产亚洲人成网站在线观看不卡| 亚洲中文字幕AV在天堂| 中文字幕免费观看视频| 成人免费毛片视频| 亚洲成AV人片一区二区| 极品色天使在线婷婷天堂亚洲| 精品无码人妻一区二区免费蜜桃 | 亚洲美女激情视频| 乱爱性全过程免费视频| 动漫黄网站免费永久在线观看|