java學習

          java學習

           

          myeclipse自動提示配置

          1. 打開MyEclipse,然后 Window-------->Preferences;

           

          2.選擇Java-------->展開-------->Editor-------->選擇ContentAssist;

           

          3.選擇ContentAssist-------->然后看到右邊-------->右邊的Auto-Activation下面的Auto Activation triggers for java(指觸發代碼提示的就是”.”這個符號)這個選項;

          4. AutoActivation triggers for java這個選項-------->在”.”后加
          abcdefghijklmnopqrstuvwxyz
          字母,方便后面的查找修改-------->然后apply-------->點擊OK;

          posted @ 2013-01-18 14:15 楊軍威 閱讀(110) | 評論 (0)編輯 收藏

          struts2標簽

               摘要: Struts2 Taglib抽象了不同表示技術,現在Struts2主要支持三種表示技術:JSP,FreeMarker和Velocity。但部分的Tag在三種表示技術下都可以使用,但是也有部分只能在某一種情況下使用。 Tab可以分為兩類:通用標簽和UI標簽。 4.1節 通用標簽 通用標簽用來在頁面表示的時候控制代碼執行的過程,這些標簽也允許從Action或者值堆棧中取得數據。例如地域,JavaBea...  閱讀全文

          posted @ 2013-01-18 13:38 楊軍威 閱讀(792) | 評論 (0)編輯 收藏

          tomcat中配置數據庫連接池

          4.1連接池知識簡介

          眾所周知建立數據庫連接是一個非常耗時耗資源的行為,因此現代的Web中間件,無論是開源的Tomcat、Jboss還是商業的 websphere、weblogic都提供了數據庫連接池功能,可以毫不夸張的說,數據庫連接池性能的好壞,不同廠商對連接池有著不同的實現,本文只介 紹拜特公司使用較多的開源web中間件Tomcat中默認的連接池DBCP(DataBase connection pool)的使用。

          4.2 Tomcat下配置連接池

          下面以tomcat5.5.26為例來介紹如何配置連接池

          1:需要的jar

          在tomcat的安裝目錄common\lib下有一個naming-factory-dbcp.jar,這個是tomcat修改后的dbcp連接池實現,同時為了能夠正常運行,還需要commons-pool.jar。

          2:建立context文件

          進入到conf\Catalina\localhost新建一個上下文文件,文件的名稱既為將來要訪問是輸入url上下文名稱,例如我們建立一個名為btweb的文件內容如下:

          <Context debug="0"docBase="D:\v10_workspace\build\WebRoot"  reloadable="false">

             <Resource

             name="jdbc/btdb1"

            type="javax.sql.DataSource"

            factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

              username="v10"

              password="v10"

          driverClassName="oracle.jdbc.driver.OracleDriver"

          url="jdbc:oracle:thin:@127.0.0.1:1521:cahs"

              maxActive="5"

              maxIdle="3"

              maxWait="5000"

              removeAbandoned="true"

          removeAbandonedTimeout="60"

          testOnBorrow="true"

              validationQuery="selectcount(*) from bt_user"

              logAbandoned="true"

                 />

            </Context>

          4.3參數分步介紹

          數據庫連接相關

          username="v10"

             password="v10"

          driverClassName="oracle.jdbc.driver.OracleDriver"

          url="jdbc:oracle:thin:@127.0.0.1:1521:cahs"

          jndi相關

          name="jdbc/btdb1"

            type="javax.sql.DataSource"

            factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

          factory默認是org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory,tomcat也允許采用其他連接實現,不過默認使用dbcp。

          連接數控制與連接歸還策略

              maxActive="5" 

          maxIdle="3" 

          minIdle=”2”

              maxWait="5000"

            應對網絡不穩定的策略

          testOnBorrow="true"

              validationQuery="selectcount(*) from bt_user"

          應對連接泄漏的策略

              removeAbandoned="true"

          removeAbandonedTimeout="60"

              logAbandoned="true"

            

          如下圖所示:連接池處于應用程序與數據庫之間,一方面應用程序通過它來獲取連接,歸還連接,另一方面連接又需要從數據里獲取連接,歸還連接。

          步驟1:系統啟動

          系統啟動時,初始化連接池,由于沒有任何請求連接池中連接數為0。

          maxActive="5"

          表示并發情況下最大可從連接池中獲取的連接數。如果數據庫不是單獨,供一個應用使用,通過設置maxActive參數可以避免某個應用無限制的獲取 連接對其他應用造成影響,如果一個數據庫只是用來支持一個應用那么maxActive理論上可以設置成該數據庫可以支撐的最大連接數。maxActive 只是表示通過連接池可以并發的獲取的最大連接數。

          從圖上我們可以看到連接的獲取與釋放是雙向,當應用程序并發請求連接池時,連接池就需要從數據庫獲取連接,那么但應用程序使用完連接并將連接歸還給 連接池時,連接池是否也同時將連接歸還給數據庫呢?很顯然答案是否定的,如果那樣的話連接池就變得多此一舉,不但不能提高性能,反而會降低性能,那么但應 用成歸還連接后,連接池如何處理呢?

          maxIdle="3"

          如果在并發時達到了maxActive=5,那么連接池就必須從數據庫中獲取5個連接來供應用程序使用,當應用程序關閉連接后,由于maxIdle=3,因此并不是所有的連接都會歸還給數據庫,將會有3個連接保持在連接池種中,狀態為空閑。

          minIdle=”2”

          最小默認情況下并不生效,它的含義是當連接池中的連接少有minIdle,系統監控線程將啟動補充功能,一般情況下我們并不啟動補充線程。

          問題:如何設置maxActive和maxIdle?

          理論上講maxActive應該設置成應用的最大并發數,這樣一來即便是在最大并發的情況下,應用依然能夠從連接池中獲取連接,但是困難時的是我們 很難準確估計到最大并發數,設置成最大并發數是一種最優的服務質量保證,事實上,如果某個用戶登錄提示系統繁忙,那么在他再次登錄時,可能系統資源已經充 足,對于拜特資金管理系統我們建議將maxActive設置為系統注冊人數的十分之一到二十分之一之間。例如系統的注冊人數為1000,那么設置成50-100靠近100的數字,例如85或90。

          maxIdle對應的連接,實際上是連接池保持的長連接,這也是連接池發揮優勢的部分,理論上講保持較多的長連接,在應用請求時可以更快的響應,但是過多的連接保持,反而會消耗數據庫大量的資源,因此maxIdle也并不是越大越好,同上例我們建議將maxIdle設置成

          50-100中靠近50的數字,例如55。這樣就能在兼顧最大并發同時,保持較少的數據庫連接,而且在絕大多情況,能夠為應用程序提供最快的相應速度。

          testOnBorrow="true"

          validationQuery="selectcount(*) from bt_user"

          我們知道數據庫連接從本質上架構在tcp/ip連接之上,一般情況下web服務器與數據庫服務器都不在同一臺物理機器上,而是通過網絡進行連接,那 么當建立數據庫連接池的機器與數據庫服務器自己出現網絡異常時,保持在連接池中的連接將失效,不能夠在次使用,傳統的情況下只能通過重新啟動,再次建立連 接,通過設置以上兩個參數,但應用程序從連接池中獲取連接時,會首先進行活動性檢測,當獲取的連接是活動的時候才會給應用程序使用,如果連接失效,連接將 釋放該連接。validationQuery是一條測試語句,沒有實際意義,現實中,一般用一條最為簡單的查詢語句充當。

          removeAbandoned="true"

          removeAbandonedTimeout="60"

          logAbandoned="true"

          有時粗心的程序編寫者在從連接池中獲取連接使用后忘記了連接的關閉,這樣連池的連接就會逐漸達到maxActive直至連接池無法提供服務。現代連接池一般提供一種“智能”的檢查,但設置了removeAbandoned="true"時,當連接池連接數到達(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)時便會啟動連接回收,那種活動時間超過removeAbandonedTimeout="60"的連接將會被回收,同時如果logAbandoned="true"設置為true,程序在回收連接的同時會打印日志。removeAbandoned是連接池的高級功能,理論上這中配置不應該出現在實際的生產環境,因為有時應用程序執行長事務,可能這種情況下,會被連接池誤回收,該種配置一般在程序測試階段,為了定位連接泄漏的具體代碼位置,被開啟,生產環境中連接的關閉應該靠程序自己保證。

          posted @ 2013-01-18 13:27 楊軍威 閱讀(3461) | 評論 (0)編輯 收藏

          strut2ognl

          經過一段時間的閉關練習,終于對struts2有所了解.其實struts2并不難, 一看就能明白其中奧妙.我對struts2的驗證體系保留懷疑態度,因為它的驗證消息使用標簽打在頁面上,實在太丑,在真實項目中不知道是否有人這么做. 也許是我太菜了,還不知道如何將驗證消息顯示得更友好,希望各位不吝拍磚指導.
            然而,我認為struts2最復雜難學的是它內置的ognl表達式.這個ognl在我開始學struts2時,讓我云里霧里,不知如何應對.經過幾輪 的翻看書籍,與網上資料查詢,還算是讓我有所明白一點.在此記錄,以便日后溫習,同時,如果這篇文章對各位有哪怕一點幫助,那便是我最大的榮幸.
            首先,要知道ognl最主要的功能就是取數據,它可以利用一段簡短的表達式取出各種各樣豐富的數據.其次,它還附帶了一些便捷的功能,如:方法調用、 靜態方法和屬性調用、數值運算……我們最關心的是如何取數據,因此,接下來我將重點介紹如何取數據,至于附帶功能將不做介紹。
            知道了ognl最主要的功能是取數據后,那么數據從哪里取呢!ognl會從兩個地方取:一個是Action的實例屬性;另一個是 ValueStack(中文名叫值棧)。ognl會先從前者里面取,如果沒取到再到ValueStack里取。Action的實例屬性好理解,但這個 ValueStack從字面上看,著實不好理解,以致于我將struts2的源碼引進eclipse里,單步調試才算有所啟發。可以將 ValueStack初步理解為一個map,在這個map里存儲了request、session、application、response、 action實例、parameters數組……還有很多你不知道的對象。有了這個map,還愁數據取不到嗎。
            注意:將ValueStack初步理解為一個map,只適于初學struts2的人,其實它內部并沒這么簡單。由于水平、時間有限,我并不能掌握其內 部精髓,加上表達能力不佳,怕表達不對誤導大家,所以我們姑且理解ValueStack為一個map吧。如果想更深的了解的ValueStack,請查看 struts2的源碼。

            接下來,便是取數據。取action實例的屬性數據與取ValueStack中的數據不一樣,先說取action實例的屬性數據吧。
            action實例的屬性數據可以直接在struts2的標簽中通過屬性名取到。如:<s:property value="name"/>、<s:property value="user.password"/>
            注意:不要加#號。

            再是取ValueStack中的數據。
            struts2提供三種方式通過ognl表達式來取ValueStack中的數據:#、%{}、${}
            #和%{}需要放到struts2提供的標簽里才生效。如:<s:property value="#name"/>、<s:property value="%{'hello struts2'}"/>
            一、最常用的方式是:#
            1.#能取request、session、application里的attribute,但需要加前綴。如:<s:property value="#session.name2"/>、<s:property value="#application.name3"/>。如果是取request范圍的attribute,那么不需要加request前綴, 加上反而取不到數據,ognl默認從request里取,如果沒有取到并不會到session或application里取。 如:<s:property value="#name"/>
            2.#能取request里的請求參數,但必須加parameters前綴,且取到的是一個數組,所以如果你要得到參數的第一項值,那么還要加下標。 如:<s:property value="#parameters.name[0]"/>。這相當于調用 request.getParameterValues("name")[0];
            3.#加attr前綴能按request > session > application順序獲取attribute,這樣當在request中取不到時,會自動向session里取,如果session里也取不到,會 再向application里取。如果取到則返回,不再向上游歷。如:<s:property value="#attr.name"/>
            4.#能構造Map,如:<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" /><s:property value="#foobar['foo1']" />
            5.#能用于過濾和投影(projecting)集合,如:books.{?#this.price<100}
            以上第4、5項功能,我沒有做過多介紹,因為目前為止這兩項功能我使用并不多。
            二、%{}的用途是在標簽的屬性為字符串類型時,計算OGNL表達式的值。這個功能目前還沒有深刻體會,故不介紹。
            三、${}有兩個主要的用途。
            1.用于在國際化資源文件中,引用OGNL表達式。
            2.在Struts 2配置文件中,引用OGNL表達式。如 :
            <action name="AddPhoto" class="addPhoto">
                  <interceptor-ref name="fileUploadStack" />           
                  <result type="redirect">ListPhotos.action?albumId=${albumId}</result>
              </action>

            以上,其實主要介紹了#的使用,大部分情況下我們只與它打交道,另外兩種方式需要在以后的項目中多多使用才能有所體會。
            其實,我是jstl+el的忠實粉絲,在任何項目中,只要能用上jstl標簽的,我決不用其它標簽。因為它是官方標準,還有它簡單且已熟練,我已在眾多項目中實戰演練過,有了它們,我不想在使用其它標簽。
            說到了這里,我還是有必要再多說兩句,是不是使用了struts2,就不能再用el來取數據了呢?答案是否定的,完全可以使用el來取數據。 struts2會將ValueStack里的session、application里的attribute完全復制到HttpSession、 ServletContext里,這樣el表達式照樣能取到這兩個Scope里的數據。然而,struts2并沒有將ValueStack里的 request里的attribute復制到HttpServletRequest,這是不是意味著el表達式就不能取request里的數據了呢?還是 可以,不只可以取request里的數據,還可以取action實例的屬性值。神奇吧!奧秘就在struts2對request做了封裝,這個封裝類是 org.apache.struts2.dispatcher.StrutsRequestWrapper,它重寫了getAttribute()方法, 該方法先從真實的request類里取attribute,如果取到則返回,如果沒有取到則從ValueStack里取,現在明白了吧!

          posted @ 2013-01-18 13:20 楊軍威 閱讀(120) | 評論 (0)編輯 收藏

          各種數據庫默認端口

          一 :Oracle

              驅動:oracle.jdbc.driver.OracleDriver
              URL:jdbc:oracle:thin:@<machine_name><:port>:dbname
              注:machine_name:數據庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;    

               port:端口號,默認是1521


          二:SQL Server

              驅動:com.microsoft.jdbc.sqlserver.SQLServerDriver
              URL:jdbc:microsoft:sqlserver://<machine_name><:port>;DatabaseName=<dbname>
              注:machine_name:數據庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;          
               port:端口號,默認是1433

           

          三:MySQL

              驅動:org.gjt.mm.mysql.Driver
              URL:jdbc:mysql://<machine_name><:port>/dbname
              注:machine_name:數據庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;          
               port:端口號,默認3306
            

                  
           四:pointbase

              驅動:com.pointbase.jdbc.jdbcUniversalDriver
              URL:jdbc:pointbase:server://<machine_name><:port>/dbname
              注:machine_name:數據庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;
               port:端口號,默認是9092


           五:
          DB2

              驅動:com.ibm.db2.jdbc.app.DB2Driver
              URL:jdbc:db2://<machine_name><:port>/dbname
              注:machine_name:數據庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;
               port:端口號,默認是5000

          posted @ 2013-01-18 13:18 楊軍威 閱讀(7890) | 評論 (0)編輯 收藏

          js函數

          一、function概述

             javascript中的函數不同于其他的語言,每個函數都是作為一個對象被維護和運行的。通過函數對象的性質,可以很方便的將一個函數賦值給一個變量或者將函數作為參數傳遞。

             函數對象與其他用戶所定義的對象有著本質的區別,這一類對象被稱之為內部對象。內置對象的構造器是由JavaScript本身所定義的。

          二、function對象的創建

          在JavaScript中,函數對象對應的類型是Function,可以通過new Function()來創建一個函數對象,也可以通過function關鍵字來創建一個對象。

              //使用new Function()方式創建

              var myFunction=new Function("a","b","return a+b");

              //使用function關鍵字創建

              function myFunction(a,b) {

                return a + b;

              }

             用關鍵字創建對象的時候,在解釋器內部,就會自動構造一個Function對象,將函數作為一個內部的對象來存儲和運行。從這里也可以看到,一個函數對象 名稱(函數變量)和一個普通變量名稱具有同樣的規范,都可以通過變量名來引用這個變量,但是函數變量名后面可以跟上括號和參數列表來進行函數調用。

              new Function()的語法規范如下:

              var funcName=new Function(p1,p2,...,pn,body);

            參數的類型都是字符串,p1到pn表示所創建函數的參數名稱列表,body表示所創建函數的函數體語句,funcName就是所創建函數的名稱。可以不指定任何參數創建一個空函數,不指定funcName創建一個匿名函數。

            需要注意的是,p1到pn是參數名稱的列表,即p1不僅能代表一個參數,它也可以是一個逗號隔開的參數列表,例如下面的定義是等價的:

            new Function("a", "b", "c", "return a+b+c")

            new Function("a, b, c", "return a+b+c")

            new Function("a,b", "c", "return a+b+c")

             函數的本質是一個內部對象,由JavaScript解釋器決定其運行方式。并且可直接在函數聲明后面加上括號就表示創建完成后立即進行函數調用,例如:

              var i=function (a,b){

              return a+b;

            }(1,2);

            alert(i);

             這段代碼會顯示變量i的值等于3。i是表示返回的值,而不是創建的函數,因為括號“(”比等號“=”有更高的優先級。

          三、匿名函數和有名函數的區別

             匿名函數必須先定義后調用,有名函數可以先調用,后定義。

          A)匿名(這段語句將產生func未定義的錯誤)

             <script>

                func();

                var func = function() {

                  alert(1);

                }

             < /script>

          B)有名(輸出1)

             <script>

                func();

                 function func() {

                  alert(1);

                }

             < /script>

          這是因為JS解釋器是分段分析執行的。并且,在同一段中,有名函數會優先被分析。并且同名函數后面的覆蓋前面的。

          而且,下面這段代碼也是合法的:

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
              myfunc(); //這里調用myfunc,輸出yeah而不是hello
            
              function myfunc ()
              {
                  alert("yeah");
              };  
              myfunc(); //這里調用myfunc,輸出yeah

          </script>

          如果要讓上述代碼的第一次調用輸出“hello”,可以將它們分為兩段:

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
              myfunc(); //這里調用myfunc,輸出hello
          < /script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };  
              myfunc(); //這里調用myfunc,輸出yeah

          </script>

          下面的代碼輸出“hello”

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
          < /script>

          <script>

          myfunc(); //輸出“hello”

          </script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };

          </script>

          下面的代碼輸出“yeah”

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
          < /script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };

          </script>

          <script>

          myfunc(); //輸出“yeah”

          </script>

          從上面對段的位置變化導致輸出變化很清楚的解釋了JS解釋器分段分析執行的本質。

          posted @ 2013-01-18 13:11 楊軍威 閱讀(206) | 評論 (0)編輯 收藏

          java文件上傳下載

          JAVA的文件上傳遍一直是一個比較關注的問題,而且有幾個NB東西提供了這個功能.

          用的最多的算是三個(我就知道這三個)比較強的,一個是比較早的jspsmartupload,另一個是出身名族的commonupload,還有一個就是orellay的了.

          我用的比較多是前兩個,總的感覺是jspsmartuplod比較靈活,功能上更強一些(一點點吧),但是現在網上也不維護,也不能下載了,特別是 它上傳的時候把上傳文件放到內存里,所以上傳文件的大小會和內存有關系.commonupload雖然沒有提供很多API,但是它有比較靈活,它上傳的過 程中會把上傳的文件先寫入磁盤,所以上傳的大小只是帶寬有關系,我嘗試最大的上傳文件的大小是700M,當然是本地測試:>

          還有是就是在Linux/Unix系統上傳文件的中文問題,我在下面的代碼有了一些解決.

          下面是前兩種方式的上傳代碼:

          try{
          //取session 用戶oid
          int pid = userInfo.getUserId();
          String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數值
          String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
          String saveDir = (String)init.getObject("InfoUploadDir");
          String tempDir = (String)init.getObject("InfoUploadDir");
          String fileMemo = ""; //文件說明
          String fileName = null; //存儲到數據庫的文件名
          String saveName = null; //存儲到本地的文件名
          String filePath = null; //存儲到數據庫的文件路徑
          String savePath = null; //存儲到本地的文件路徑
          long fileSize = 0; //文件大小
          int maxPostSize = -1;
          int dinfo_upload_id = -1;
          %>
          <%
          //初始化
          mySmartUpload.initialize(pageContext);
          //上載文件
          mySmartUpload.upload();
          //循環取得所有上載文件
          for(int i=0; i<mySmartUpload.getFiles().getCount(); i++)
          {
          //取得上載文件
          com.jspsmart.upload.File file = mySmartUpload.getFiles().getFile(i);
          if(!file.isMissing())
          {
          fileName = file.getFileName();
          //取得文件擴展名file.getFileExt()
          try{
          saveName = fileName.substring(fileName.lastIndexOf("."));

          }catch(Exception e){
          saveName = "";
          }
          //取得文件大小
          fileSize = file.getSize();
          //存儲路徑
          String sql_id = " SELECT S_INFO_UPLOAD.nextval as seqid FROM dual ";
          try{
          Statement stmt = con.createStatement();
          ResultSet rst = stmt.executeQuery(sql_id);
          while(rst.next())
          {
          dinfo_upload_id = rst.getInt("seqid");
          }
          }catch(SQLException sqle){
          return;
          }

          filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
          savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
          //存儲文件到本地
          file.saveAs(filePath);
          //存儲文件到數據庫
          switch(i)
          {
          case 0: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo1"); break;
          case 1: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo2"); break;
          case 2: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo3"); break;
          case 3: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo4"); break;
          case 4: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo5"); break;
          default: fileMemo = "";
          }

          String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
          + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
          sqlcmd cmd = new sqlcmd(con,sql);
          //System.out.println(sql);
          java.sql.PreparedStatement pstmt = null;
          java.sql.Statement stmt = null;
          //fileName = fileName.substring(0, fileName.indexOf("."));
          String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
          java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
          java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

          java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
          java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");

          try{
          stmt = con.createStatement();
          stmt.getConnection().setAutoCommit(false);

          pstmt = con.prepareStatement(sql_cn);
          pstmt.setCharacterStream(1, isr_name, fileName.length());
          pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
          pstmt.setInt(3, dinfo_upload_id);

          //System.out.println(sql_cn);

          pstmt.execute();
          stmt.executeUpdate("COMMIT");

          }catch(Exception exce){
          System.out.println(exce);
          stmt.executeUpdate("ROLLBACK");
          }
          }
          }
          }catch(Exception e){
          }


          以上是jspsmart的方式,如果想要其它的方式,請下載全部源代碼.



          //upload_fileUpload.jsp

          <%@ include file = "../../backgeneral.jsp"%>
          <%@ contentType="text/html;charset=GBK" %>
          <jsp:useBean id="userInfo" scope="session" class="com.ges.hbgov.UserInfo"/>
          <%@ page import="org.apache.commons.fileupload.*" %>
          <%
          try{
           //request.setCharacterEncoding("GBK");
          //取session 用戶oid
              int pid = userInfo.getUserId();
              String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數值
           String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
           String saveDir  = (String)init.getObject("InfoUploadDir");
           String tempDir  = (String)init.getObject("InfoUploadDir");
           String fileMemo = "";    //文件說明
           String fileName = null;  //存儲到數據庫的文件名
           String saveName = null;  //存儲到本地的文件名
           String filePath = null;  //存儲到本地的文件路徑
           String savePath = null;  //存儲到數據庫的文件路徑
           long   fileSize = 0;     //文件大小
           int maxPostSize = -1;   
           int dinfo_upload_id = -1;
          %>
          <%
              DiskFileUpload df = new DiskFileUpload();
              //設定上傳文件大小
           df.setSizeMax(maxPostSize);
           //設定臨時目錄
           df.setRepositoryPath(sitePhysicalPath + tempDir);
              //取得request信息
           List items = df.parseRequest(request);
             
           Iterator iter = items.iterator();
             
           int temp = 0;
           FileItem tempItem = null;

           while(iter.hasNext()){
            temp++;
            FileItem item = (FileItem)iter.next();
            if(item.isFormField())    //取得文件說明信息
            {
             fileMemo = item.getString("GBK");
             
            }
            else
            {   //取得上傳文件信息
             fileName = (String)item.getName();
             try{
              fileName = fileName.substring(fileName.lastIndexOf("//")+1);
              fileName = fileName.substring(fileName.lastIndexOf("/")+1);
             }catch(Exception e){
              System.out.println(e);
             }
             fileSize = item.getSize();
             tempItem = item;
            }

            if(temp == 2 && fileSize != 0)
             {    //每兩個iter存儲一個上傳文件

                       //得到info_title_id
                        String SQL_ID="select S_INFO_UPLOAD.nextval as seqid from dual";
                     try {
                          java.sql.Statement stmt = con.createStatement();
                          java.sql.ResultSet rst= stmt.executeQuery(SQL_ID);
                          while(rst.next())
                 {
                                 dinfo_upload_id = rst.getInt("seqid");
                          }

                       }catch(SQLException e1){
                              return;
                       }
                      //取得文件擴展名
                      try{
              saveName = fileName.substring(fileName.lastIndexOf("."));
             }catch(Exception exc){
              saveName = "";
             }

                      filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
                      //存儲文件
             java.io.File uploadFile = new java.io.File(filePath);
             tempItem.write(uploadFile);
             /*try{
                 FileOutputStream fos = new FileOutputStream(filePath);
                 InputStream is = tempItem.getInputStream();
                 byte[] b = new byte[1024];
                 int nRead;
                 long per = 0;
                 double percent = 0;
                          while((nRead = is.read(b, 0, 1024))>0){
                  fos.write(b, 0, nRead);
                  per += nRead;
                  percent = (double)per/fileSize;

                  session.setAttribute("percent",Double.toString(percent).substring(2,4));
                  session.setAttribute("filename",fileName);
                          }
                 is.close();
              fos.close();    
              }catch(Exception e){
               System.out.println(e);
              }*/
                      savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
                      /*/存儲數據庫
                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_name,file_memo,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + ",'" + fileName + "','" + fileMemo + "'," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
             */
                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
                      //System.out.println(sql);
             java.sql.PreparedStatement pstmt = null;
             java.sql.Statement stmt = null;
             //fileName = fileName.substring(0, fileName.indexOf("."));
             String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
             
             java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
             java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

             java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
             java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");
             
             try{
              stmt = con.createStatement();
              stmt.getConnection().setAutoCommit(false);

              pstmt = con.prepareStatement(sql_cn);
              pstmt.setCharacterStream(1, isr_name, fileName.length());
              pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
              pstmt.setInt(3, dinfo_upload_id);

                          //System.out.println(sql_cn);

              pstmt.execute();
              stmt.executeUpdate("COMMIT");

             }catch(Exception exce){
              System.out.println(exce);
              stmt.executeUpdate("ROLLBACK");
             }

             temp = 0;
            }
            else if (temp == 2 && fileSize == 0) {temp = 0;}

           }
              //session.setAttribute("percent","ok");
          }catch(Exception ex){
           System.out.println(ex);
          }
          response.sendRedirect("list.jsp");

          %>




          //upload_jspSmart.jsp

          <%@ include file = "../../backgeneral.jsp"%>
          <%@ page language="java" import="java.util.*,java.sql.*,java.io.*"%>
          <%@ page language="java" import="com.jspsmart.upload.*"%>
          <%@ page language="java" import="com.ges.hbgov.*"%>
          <jsp:useBean id="userInfo" scope="session" class="com.ges.hbgov.UserInfo"/>
          <jsp:useBean id="mySmartUpload" scope="page" class="com.jspsmart.upload.SmartUpload" />
          <%
          //System.out.println("page=" + (String)session.getAttribute("SYS_USER_ID"));
          if(!userInfo.Request(request)){
          %>
          <script language=javascript>
           function relogin() {
            this.parent.location.href="../../login.jsp";
           }
           relogin();
          </script>
          <%
          }
          %>

          <%

          try{
          //取session 用戶oid
              int pid = userInfo.getUserId();
              String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數值
           String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
           String saveDir  = (String)init.getObject("InfoUploadDir");
           String tempDir  = (String)init.getObject("InfoUploadDir");
           String fileMemo = "";    //文件說明
           String fileName = null;  //存儲到數據庫的文件名
           String saveName = null;  //存儲到本地的文件名
           String filePath = null;  //存儲到數據庫的文件路徑
           String savePath = null;  //存儲到本地的文件路徑
           long   fileSize = 0;     //文件大小
           int maxPostSize = -1;   
           int dinfo_upload_id = -1;
          %>
          <%
           //初始化
           mySmartUpload.initialize(pageContext);
           //上載文件
              mySmartUpload.upload();
           //循環取得所有上載文件
              for(int i=0; i<mySmartUpload.getFiles().getCount(); i++)
           {
            //取得上載文件
            com.jspsmart.upload.File file = mySmartUpload.getFiles().getFile(i);
            if(!file.isMissing())
            {
             fileName = file.getFileName();
             //取得文件擴展名file.getFileExt()
             try{
              saveName = fileName.substring(fileName.lastIndexOf("."));

             }catch(Exception e){
              saveName = "";
             }
             //取得文件大小
             fileSize = file.getSize();
             //存儲路徑
             String sql_id = " SELECT S_INFO_UPLOAD.nextval as seqid FROM dual ";
             try{
              Statement stmt = con.createStatement();
              ResultSet rst = stmt.executeQuery(sql_id);
              while(rst.next())
              {
               dinfo_upload_id = rst.getInt("seqid");
              }
             }catch(SQLException sqle){
              return;
             }

             filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
             savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
             //存儲文件到本地
             file.saveAs(filePath);
             //存儲文件到數據庫
             switch(i)
             {
              case 0: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo1"); break;
              case 1: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo2"); break;
                          case 2: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo3"); break;
              case 3: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo4"); break;
              case 4: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo5"); break;
              default: fileMemo = "";
             }

                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
                      //System.out.println(sql);
             java.sql.PreparedStatement pstmt = null;
             java.sql.Statement stmt = null;
             //fileName = fileName.substring(0, fileName.indexOf("."));
             String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
             java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
             java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

             java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
             java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");
             
             try{
              stmt = con.createStatement();
              stmt.getConnection().setAutoCommit(false);

              pstmt = con.prepareStatement(sql_cn);
              pstmt.setCharacterStream(1, isr_name, fileName.length());
              pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
              pstmt.setInt(3, dinfo_upload_id);

                          //System.out.println(sql_cn);

              pstmt.execute();
              stmt.executeUpdate("COMMIT");

             }catch(Exception exce){
              System.out.println(exce);
              stmt.executeUpdate("ROLLBACK");
             }
            }
           }
          }catch(Exception e){
          }

          response.sendRedirect("list.jsp");
          %>

          posted @ 2013-01-17 14:31 楊軍威 閱讀(1826) | 評論 (0)編輯 收藏

          java處理xml方法

          最初,XML 語言僅僅是意圖用來作為 HTML 語言的替代品而出現的,但是隨著該語言的不斷發展和完善,人們越來越發現它所具有的優點:例如標記語言可擴展,嚴格的語法規定,可使用有意義的標記,內容 存儲和表現分離等等優勢注定了該語言從誕生之日起就會走向輝煌。 XML 語言在成為 W3C 標準之后進入到了一個快速發展的時期,當然它本身所具有的一系列優點和優勢也注定了各大技術廠商對它的偏愛,Java 作為軟件行業的一種開發技術也迅速作出了反應,出現了多種對 XML 支持的工具,本文將會從這個角度對 Java 處理 XML 的幾種主流技術進行介紹,希望能對您有所幫助。在這篇文章中,您將會得到以下信息:
          1. Java 提供了哪些優秀的類庫及工具便于程序員對 XML 進行處理 ?
          2. 有了 DOM 了,其它工具類庫還有必要么 ?
          3. 幾個小例程帶你快速了解這三種解析方式

            Java 有哪些優秀的類庫及工具便于程序員對 XML 進行處理 ?

          • 大名鼎鼎的 DOM
          • 綠色環保的 SAX
          • 默默無聞的 Digester

            XML 三種解析方式簡介

            大名鼎鼎的 DOM

            說它大名鼎鼎可是一點不為過,DOM 是 W3C 處理 XML 的標準 API,它是許多其它與 XML 處理相關的標準的基礎,不僅是 Java,其它諸如 Javascript,PHP,MS .NET 等等語言都實現了該標準, 成為了應用最為廣泛的 XML 處理方式。當然,為了能提供更多更加強大的功能,Java 對于 DOM 直接擴展工具類有很多,比如很多 Java 程序員耳熟能詳的 JDOM,DOM4J 等等, 它們基本上屬于對 DOM 接口功能的擴充,保留了很多 DOM API 的特性,許多原本的 DOM 程序員甚至都沒有任何障礙就熟練掌握了另外兩者的使用,直觀、易于操作的方式使它深受廣大 Java 程序員的喜愛。

            綠色環保的 SAX

            SAX 的應運而生有它特殊的需要,為什么說它綠色環保呢,這是因為 SAX 使用了最少的系統資源和最快速的解析方式對 XML 處理提供了支持。 但隨之而來繁瑣的查找方式也給廣大程序員帶來許多困擾,常常令人頭痛不已,同時它對 XPath 查詢功能的支持,令人們對它又愛又恨。

            默默無聞的 Digester:XML 的 JavaBean 化

            Digester 是 apache 基金組織下的一個開源項目,筆者對它的了解源于對 Struts 框架的研究,是否有很多程序員想要一解各大開源框架的設計甚至想要自己寫一個功能強大的框架時會碰到這樣一個難題: 這些形形色色的用 XML 語言標記的框架配置文件,框架底層是用什么技術來解析呢? DOM 解析耗費時間,SAX 解析又過于繁瑣,況且每次解析系統開銷也會過大, 于是,大家想到需要用與 XML 結構相對應的 JavaBean 來裝載這些信息,由此 Digester 應運而生。它的出現為 XML 轉換為 JavaBean 對象的需求帶來了方便的操作接口,使得更多的類似需求得到了比較完美的解決方法, 不再需要程序員自己實現此類繁瑣的解析程序了。與此同時 SUN 也推出了 XML 和 JavaBean 轉換工具類 JAXB,有興趣的讀者可以自行了解。

            三種解析方式比較

            DOM

            優缺點:實現 W3C 標準,有多種編程語言支持這種解析方式,并且這種方法本身操作上簡單快捷,十分易于初學者掌握。其處理方式是將 XML 整個作為類似樹結構的方式讀入內存中以便操作及解析,因此支持應用程序對 XML 數據的內容和結構進行修改,但是同時由于其需要在處理開始時將整個 XML 文件讀入到內存中去進行分析,因此其在解析大數據量的 XML 文件時會遇到類似于內存泄露以及程序崩潰的風險,請對這點多加注意。

            適用范圍:小型 XML 文件解析、需要全解析或者大部分解析 XML、需要修改 XML 樹內容以生成自己的對象模型

            SAX

            SAX 從根本上解決了 DOM 在解析 XML 文檔時產生的占用大量資源的問題。其實現是通過類似于流解析的技術,通讀整個 XML 文檔樹,通過事件處理器來響應程序員對于 XML 數據解析的需求。由于其不需要將整個 XML 文檔讀入內存當中,它對系統資源的節省是十分顯而易見的,它在一些需要處理大型 XML 文檔以及性能要求較高的場合有起了十分重要的作用。支持 XPath 查詢的 SAX 使得開發人員更加靈活,處理起 XML 來更加的得心應手。但是同時,其仍然有一些不足之處也困擾廣大的開發人員:首先是它十分復雜的 API 接口令人望而生畏,其次由于其是屬于類似流解析的文件掃描方式,因此不支持應用程序對于 XML 樹內容結構等的修改,可能會有不便之處。

            適用范圍:大型 XML 文件解析、只需要部分解析或者只想取得部分 XML 樹內容、有 XPath 查詢需求、有自己生成特定 XML 樹對象模型的需求

            Digester/JAXB

            優缺點 : 由于其是在上述兩者的基礎上衍生出來的工具類,為的是滿足將 XML 轉換為 JavaBean 的特殊需求,故而沒有什么特別明顯的優缺點。作為大名鼎鼎的開源框架 Struts 的 XML 解析工具 Digester,為我們帶來了將 XML 轉換為 JavaBean 的可靠方法。

            適用范圍 : 有將 XML 文檔直接轉換為 JavaBean 需求。

            應用示例

            下面給出一段用于解析的 XML 片段:
            清單 1. XML 片段

           <?xml version="1.0" encoding="UTF-8"?>   <books>     <book id="001">        <title>Harry Potter</title>        <author>J K. Rowling</author>     </book>     <book id="002">        <title>Learning XML</title>        <author>Erik T. Ray</author>     </book>   </books> 

            DOM 解析 XML

            Java 中的 DOM 接口簡介: JDK 中的 DOM API 遵循 W3C DOM 規范,其中 org.w3c.dom 包提供了 Document、DocumentType、Node、NodeList、Element 等接口,這些接口均是訪問 DOM 文檔所必須的。我們可以利用這些接口創建、遍歷、修改 DOM 文檔。

            javax.xml.parsers 包中的 DoumentBuilder 和 DocumentBuilderFactory 用于解析 XML 文檔生成對應的 DOM Document 對象。

            javax.xml.transform.dom 和 javax.xml.transform.stream 包中 DOMSource 類和 StreamSource 類,用于將更新后的 DOM 文檔寫入 XML 文件。

            下面給出一個運用 DOM 解析 XML 的例子:
            清單 2. DOM 解析 XML

           import java.io.File;   import java.io.IOException;   import javax.xml.parsers.DocumentBuilder;   import javax.xml.parsers.DocumentBuilderFactory;   import javax.xml.parsers.ParserConfigurationException;   import org.w3c.dom.Document;   import org.w3c.dom.Element;   import org.w3c.dom.Node;   import org.w3c.dom.NodeList;   import org.xml.sax.SAXException;   public class DOMParser {     DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();     //Load and parse XML file into DOM     public Document parse(String filePath) {        Document document = null;        try {           //DOM parser instance           DocumentBuilder builder = builderFactory.newDocumentBuilder();           //parse an XML file into a DOM tree           document = builder.parse(new File(filePath));        } catch (ParserConfigurationException e) {           e.printStackTrace();         } catch (SAXException e) {           e.printStackTrace();        } catch (IOException e) {           e.printStackTrace();        }        return document;     }     public static void main(String[] args) {           DOMParser parser = new DOMParser();           Document document = parser.parse("books.xml");           //get root element           Element rootElement = document.getDocumentElement();           //traverse child elements           NodeList nodes = rootElement.getChildNodes();           for (int i=0; i < nodes.getLength(); i++)           {              Node node = nodes.item(i);              if (node.getNodeType() == Node.ELEMENT_NODE) {                   Element child = (Element) node;                 //process child element              }           }           NodeList nodeList = rootElement.getElementsByTagName("book");           if(nodeList != null)           {              for (int i = 0 ; i < nodeList.getLength(); i++)              {                 Element element = (Element)nodeList.item(i);                 String id = element.getAttribute("id");              }           }     }   } 

            在上面的例子中,DOMParser 的 Parse() 方法負責解析 XML 文件并生成對應的 DOM Document 對象。其中 DocumentBuilderFactory 用于生成 DOM 文檔解析器以便解析 XML 文檔。 在獲取了 XML 文件對應的 Document 對象之后,我們可以調用一系列的 API 方便的對文檔對象模型中的元素進行訪問和處理。 需要注意的是調用 Element 對象的 getChildNodes() 方法時將返回其下所有的子節點,其中包括空白節點,因此需要在處理子 Element 之前對節點類型加以判斷。

            可以看出 DOM 解析 XML 易于開發,只需要通過解析器建立起 XML 對應的 DOM 樹型結構后便可以方便的使用 API 對節點進行訪問和處理,支持節點的刪除和修改等。 但是 DOM 解析 XML 文件時會將整個 XML 文件的內容解析成樹型結構存放在內存中,因此不適合用 DOM 解析很大的 XML 文件。

            SAX 解析 XML

            與 DOM 建立樹形結構的方式不同,SAX 采用事件模型來解析 XML 文檔,是解析 XML 文檔的一種更快速、更輕量的方法。 利用 SAX 可以對 XML 文檔進行有選擇的解析和訪問,而不必像 DOM 那樣加載整個文檔,因此它對內存的要求較低。 但 SAX 對 XML 文檔的解析為一次性讀取,不創建任何文檔對象,很難同時訪問文檔中的多處數據。

            下面是一個 SAX 解析 XML 的例子:
            清單 3. SAX 解析 XML

           import org.xml.sax.Attributes;   import org.xml.sax.SAXException;   import org.xml.sax.XMLReader;   import org.xml.sax.helpers.DefaultHandler;   import org.xml.sax.helpers.XMLReaderFactory;   public class SAXParser {     class BookHandler extends DefaultHandler {        private List<String> nameList;        private boolean title = false;        public List<String> getNameList() {           return nameList;        }        // Called at start of an XML document        @Override        public void startDocument() throws SAXException {           System.out.println("Start parsing document...");           nameList = new ArrayList<String>();        }        // Called at end of an XML document        @Override        public void endDocument() throws SAXException {            System.out.println("End");         }        /**         * Start processing of an element.         * @param namespaceURI  Namespace URI         * @param localName  The local name, without prefix         * @param qName  The qualified name, with prefix         * @param atts  The attributes of the element         */        @Override        public void startElement(String uri, String localName, String qName,           Attributes atts) throws SAXException {           // Using qualified name because we are not using xmlns prefixes here.           if (qName.equals("title")) {              title = true;           }        }        @Override        public void endElement(String namespaceURI, String localName, String qName)           throws SAXException {           // End of processing current element           if (title) {              title = false;           }        }        @Override        public void characters(char[] ch, int start, int length) {           // Processing character data inside an element           if (title) {              String bookTitle = new String(ch, start, length);              System.out.println("Book title: " + bookTitle);              nameList.add(bookTitle);           }        }     }     public static void main(String[] args) throws SAXException, IOException {        XMLReader parser = XMLReaderFactory.createXMLReader();        BookHandler bookHandler = (new SAXParser()).new BookHandler();        parser.setContentHandler(bookHandler);        parser.parse("books.xml");        System.out.println(bookHandler.getNameList());     }   } 

            SAX 解析器接口和事件處理器接口定義在 org.xml.sax 包中。主要的接口包括 ContentHandler、DTDHandler、EntityResolver 及 ErrorHandler。 其中 ContentHandler 是主要的處理器接口,用于處理基本的文檔解析事件;DTDHandler 和 EntityResolver 接口用于處理與 DTD 驗證和實體解析相關的事件; ErrorHandler 是基本的錯誤處理接口。DefaultHandler 類實現了上述四個事件處理接口。上面的例子中 BookHandler 繼承了 DefaultHandler 類, 并覆蓋了其中的五個回調方法 startDocument()、endDocument()、startElement()、endElement() 及 characters() 以加入自己的事件處理邏輯。

            Digester 解析 XML

            為了滿足將 XML 轉換為 JavaBean 的特殊需求,Apache 旗下的一個名為 Digester 的工具為我們提供了這么一個選擇。由于最終是將 XML 轉化為 JavaBean 存儲在內存當中, 故而解析性能等方面其實與使用者并沒有多大關系。解析的關鍵在于用以匹配 XML 的模式以及規則等,由于該工具較為復雜,限于篇幅,作者只能給予簡單的介紹。

            下面是一個 Digester 解析 XML 的例子片段:
            清單 4. Digester 解析 XML

           // 定義要解析的 XML 的路徑,并初始化工具類  File input = new File("books.xml");   Digester digester = new Digester();   // 如果碰到了 <books> 這個標簽,應該初始化 test.myBean.Books 這個 JavaBean 并填裝相關內容  digester.addObjectCreate("books", "test.myBean.Books");   digester.addSetProperties("books");   // 如果碰到了 <books/book> 這個標簽,同上初始化 test.myBean.Book 這個 JavaBean   digester.addObjectCreate("books/book", "test.myBean.Book");   digester.addSetProperties("books/book");   // 通過調用上面已經初始化過的 JavaBean 的 addBook() 方法來把多個 <books/book> 加到一個集合中  digester.addSetNext("books/book", "addBook", "test.myBean.Book");   // 定義好了上面的解析規則后,就可以開始進行解析工作了  Books books = (Books) digester.parse(input); 

            上述代碼簡單的向讀者展示了 Digester 處理 XML 的一些要點,主要是說明了一些模式以及規則的匹配。 簡言之,Digester 就是一種用來把一個 XML 轉化為一個與該 XML 結構類似的 JavaBean。你可以把 XML 根元素想象成一個 JavaBean, 該根元素的 attribute 就是這個 JavaBean 的各種 Field,當該根元素有其他子 tag 時,又要把這個子 tag 想象成一個個新的 XML,將其視為一個新的 JavaBean, 并作為一個 Field 加入到父 Bean 當中,然后以此類推,通過循環的方式將整個 XML 進行解析。

            結束語

            本文介紹了 Java 解析 XML 的三種常用技術,其中 DOM 易于上手,程序易于理解,但缺點在于占用內存大,不適合于解析較大的 XML 文件; SAX 基于事件模型占用系統資源少,能夠勝任較大的 XML 文件解析,但解析過程較為繁瑣查找元素不方便; Digester/JAXB 基于上述兩種技術衍生而來。文中的實例向讀者展示了三種 API 的基本使用方法, 在實際開發過程中使用那種技術解析 XML 更好要依據各自的優缺點視具體情況而定。

          posted @ 2013-01-16 14:09 楊軍威 閱讀(597) | 評論 (0)編輯 收藏

          前段開發10點

           第一日:初嘗禁果

            【上帝說:“要有光!”便有了光】

            萬物生靈、陽光雨露蓋源于造物之初的天工開物,我們無法想象上帝創造光明之前的世界模樣。但幸運的是,前端開發沒有神祗般的詭魅。這個技術工種 的孕育、定型、發展自有軌跡,也頗有淵源,當然,這非常容易理解。不嚴格的講,在楊致遠和費羅在斯坦福大學的機房里攛掇出Yahoo!時,Web前端技術 就已經開始進入公眾視野,只不過當時沒有一個響亮的名字。從那時起,“基于瀏覽器端的開發”就成了軟件開發的新的分支,這也是Web前端技術的核心,即不 論何時何地何種系統以及怎樣的設備,但凡基于瀏覽器,都是Web前端開發的范疇(當然,這個定義很狹隘,下文會提到)。

            在2000年之后瀏覽器技術漸漸成熟,Web產品也越來越豐富,中國有大批年輕人開始接觸互聯網,有一點需要注意,大部分人接觸互聯網不是始于 對瀏覽器功能的好奇,而是被瀏覽器窗口內的豐富內容所吸引,我們的思維模式從一開始就被限制在一個小窗口之內,以至于很長時間內我們將“視覺”認為是一種 “功能”,Web產品無非是用來展現信息之用。起初的入行者無一例外對“視覺”的關注超過了對“內容”的重視,先讓頁面看起來漂亮,去關注 html/css,沿著“視覺呈現”的思路,繼續深入下去。因此,這類人是被“視覺”所吸引,從切頁面入行,著迷于結構化的html和書寫工整的css, 喜歡簡潔優雅的UI和工整的頁面設計,之后開始接觸視覺特效,并使用jQuery來實現視覺特效,以此為線索,開始深入研究Dom、Bom和瀏覽器的渲染 機制等,html/css在這些人手中就像進攻兵器,而JavaScript則更如防守的盾牌。

            還有另外一群人從另一條道路接觸Web前端,即工程師轉行做前端,他們有較多的后臺語言開發背景,從讀寫數據開始,漸漸觸及瀏覽器端,接觸 JavaScript庫,起初是在html代碼上加js邏輯,后來開始涉及html和css,他們喜歡OO、邏輯清晰、結構悅目的代碼,更關注界面背后的 “程序語言”和數據邏輯。html/css在這些人手中則更像盾牌,而JavaScript更如進攻的兵器。

            應當說這兩類人是互補的,他們各自了解瀏覽器本質的一部分,一撥人對渲染引擎了如指掌,另一撥人則將JS引擎奉為至寶,其實任何一部分的優勢發 揮出來都能做出精品。大部分前端工程師都能從這兩條淵源中找到自己的影子。但,這兩類人的思維模式和觀點是如此不同,以至于形成了一些不必要的對抗,比如 在某些公司,干脆將Web前端技術一分為二,“切頁面的”和“寫js的”。這樣做看上去明確了分工提高了效率,但他對員工的職業發展帶來巨大傷害。在第二 日“科班秀才”中會有進一步討論。

            我應該屬于第二類,即在學校正兒八經的學習C/Java和C#之類,以為大學畢業后能去做ERP軟件、桌面軟件或者進某些通信公司寫TCP /IP相關的程序。校園招聘時選擇了中國雅虎,因為當年(08年)雅虎還是有一點兒名氣,而且我聽說雅虎比較算技術流的公司……自此就上了賊船,一發不可 收拾。

            在雅虎的這段時間,我有幸接觸到一股正氣凜然的技術流派,也形成了我對前端技術的一些基本看法,這些基本觀點一直影響我至今。

            【優雅的學院派】

            當年雅虎的技術流派正如日中天,擁有眾多“之父”級的高人,所營造出的Hack氛圍實在讓人陶醉的無法自拔,那段時間我甚至寧愿加班到深夜閱讀 海量的文檔和源代碼,感覺真的很舒服,我深深的被雅虎工程師這種低調務實、精工細琢的“服務精神”所打動,而這種不起眼的優秀品質很大程度的影響雅虎產品 的用戶體驗和高質量的技術輸出。那么,何謂“服務精神”?即你所做的東西是服務于人的,要么是產品客戶、要么是接手你項目的人、要么是使用你開發的功能的 人,所以技術文檔成為伴隨代碼的標配。因此,工程師之間通過代碼就能做到心有靈犀的溝通。這是工程師的一項基本素質,即,思路清晰的完成項目,且配備了有 價值的技術文檔,如果你的程序是給其他程序員用的,則更要如此,就好比你制造一款家電都要配備說明書一樣。因此,YDN成了當時最受全球程序員最喜愛的技 術文檔庫,這種優雅務實的“學院氣息”讓人感覺獨具魅力。

            讓人感覺奇怪的是,在中文社區始終未見這種學院派。甚至在具有先天開源優勢的Web前端技術社區里也是波瀾不驚,可見寫一篇好的技術文案真的比 登天還難。我所見到的大部分所謂文檔索性把代碼里輸出數據的語句塊拷貝粘貼出來,至于為什么數據格式要設計成這樣、如果字段有修改怎么做、編碼解碼要求如 何等等關鍵信息只字不提,或者開發者也沒想過這些問題呢。因此,我們一直在強調代碼的質量和可維護性,但一直以來都未見效,蓋源于缺少這種“服務”意識的 灌輸。這種意識在下文中還會多次提到,因為它能影響你做事的每個細節,是最應當首先突破的思想糾結。

            除了意識問題,另一方面是技術問題,即文筆。這也是工程師最瞧不上眼的問題,難以置信這竟然是阻礙工程師突破瓶頸的關鍵所在。我已看到過數不清 的人在晉升這道關卡吃了大虧,很多工程師技術實力很強,但就是表達不出來,要么羅列一大堆信息毫無重點、要么毫無趣味的講代碼細節,不知云云。除非你走狗 屎運碰到一個懂技術的老板,否則真的沒辦法逃脫碼農的宿命。但大部分人還振振有詞不以為然。而在Web前端開發領域情況更甚。前端工程師是最喜歡搞重構 的,但在快節奏的需求面前,你很難用“提高了可維護性”、“提升了性能”這類虛無縹緲的詞藻為自己爭取到時間來搞重構,說的露骨一點,可能你真的對某次重 構帶來的實際價值無法量化,只是“感覺代碼更整潔了”而已。我會在下文的“偽架構”中會展開分析前端工程師的這種浮躁獻媚的技術情結。而這正是前端工程師 最欠缺的素質之一:用數據說話,用嚴謹科學的論據來支撐你的觀點,老板不傻,有價值的東西當然會讓你去做。

            當然,情況不總是這么糟糕,我們看到中文社區中已經鍛煉出了很多寫手,他們在用高質量的文字推銷自己的技術理念,這是一個好兆頭,好的文筆是可 以鍛煉出來的。而在職場,特別是對前端工程師這個特殊職位來講,這種基本技能可以幫你反思梳理需求的輕重緩急,從凌亂的需求中把握七寸所在。因為當你開始 認真寫一封郵件的時候,這種思考已經包含其中了。

            所以,雅虎技術的推銷是相對成功和遠播的。關鍵在于兩方面,扎實的技術功底和高超的寫手。而真正的技術大牛一定是集兩者與一身,不僅鉆研劍道,還能產出秘籍。這也是Yahoo!優雅的學院派氣息的動力源泉。國內很多技術團體想在這方面有所建樹,應當首先想清楚這一點。

            【規范的破與立 1】

            雅虎的技術運作非常規范,剛才已經提到,包括技術、組織、文化,一切看起來有模有樣,也堪稱標桿,自然成了國內很多技術團隊和社區的效仿對象。一時間各種“規范“成風、各色“標準“大行其道,結果是質量參差不齊。

            我們到底需要什么樣的規范?雅虎的技術規范到底有何種魔力?以何種思路構建的規范才是貨真價實的?規范有著怎樣的生命周期?想清楚這些問題,能很大程度減輕很多Web前端工程師的思想負擔,看清一部分技術本質,避免盲目跟風。

            我們的確需要規范,但好的規范一定是務實的,一定是“解決問題“的。比如針對項目構建的DPL可以收納公用的視覺元件以減少重復開發、規定某 OPOA項目的事件分發原則以確立增量開發的代碼慣性。反之,糟糕的規范卻顯得過于“抽象“,比如頁面性能指標、響應式設計原則。另外,盡管他山之石可以 攻玉,但拿來主義有一個大前提,就是你了解你的項目的關鍵問題,你要優先解決的是些關鍵問題,而外來規范正好能解決你的問題。因此規范是一本案頭手冊,是 一攬子問題的解決方案,應當是“字典”,而不是“教程“。可見規范的源頭是“問題”。所以,當你想用CoffeeScript重構你的項目時、當你想引入 CommonJS規范時、當你想在頁面中揉進Bootstrap時、當你打算重復造輪子搞一套JS庫時、當你想重寫一套assets打包工具時,想想這些 東東解決了你的什么問題?會不會帶來新的問題、把事情搞復雜了?還是為了嘗鮮?或者為了在簡歷中堂而皇之的寫上使用并精通各種新技術?

            規范之立應當有動因,動因來源于項目需求,項目需求則來自對產品的理解和把握,這是Web前端初級工程師走向中級甚至高級的一次重要蛻變,軟件 工程領域早就有“架構師”角色,而架構師往往存在于項目需求分析和概設、詳設階段。我看到的情況是,Web前端工程師的思維過多的限制在“界面”之內,向 前和產品需求離的太遠(認為這是視覺設計師的事)、向后和數據邏輯又隔離開來(認為這是后臺工程師該干的事),因此前端規范也大都泛泛,無關項目痛癢,成 了玩具。

            雅虎技術規范的優秀之初在于它們解決問題。所以,學習使用規范應當多問一句,“他們為什么這樣做?”其實,想清楚這些問題時,腦海中自然形成了一種“遇山開山”的創造性思維。

            【規范的破與立 2】

            如果說新技術的嘗鮮缺少針對性,但至少滿足程序員的某種潔癖和快感,那么“負擔”從何而來呢?對于初學者來說,有價值學習資料可能只有這些規范,如果說規范價值不大,那又當從何入手呢?

            剛才我說的不是依賴于規范,而是對規范的反思,擺脫規范灌輸給我們的思維定勢。新人們大概是看了Wiki中的很多指標、結論、實踐,在做項目之 初就附加了不少“八股式”的負擔,甚至影響我們對項目關鍵需求和關鍵問題的洞察力和判斷力,負擔過重就無法輕裝上陣,Wiki中提到的這些指標和規范是結 論性的,是大量的實踐之后得出的,也只有經歷過大量實踐才會真正理解這些結論,比如DomReady時間和http請求數是否有因果關系,http請求數 增加是否真的會導致頁面性能下降,什么條件下會導致性能下降?我們從那些條文和結論中無法找到答案。

            舉個具體的例子,Kissy剛剛出了DPL,也是一大堆結論,比如他的布局就采用了經典的雙飛翼,使用容器浮動來實現,那么,這種做法就是不可 撼動的“標準”嗎?看看淘寶車險首頁,布局容器齊刷刷的inline-block,只要頂層容器去掉寬度,布局容器自身就能根據瀏覽器寬度調整自然水平/ 垂直排列,輕易的適應終端寬度了。

            再比如,淘寶旅行計劃項目中的部署方式,也沒有完全使用Loader管理依賴,而是將依賴層級做的很少,業務邏輯使用腳本來合并,這樣就可以更容易在build環節加入語法檢查和代碼風格檢查。

            類似這種擺脫原有編程思維,有針對性的用新思路新方法解決問題的做法顯然讓人感覺更加清爽,編程的樂趣也正體現在打破常規的快感之中,小馬曾經 說過:“制造規范是為了打破規范”,萬不要因為這些規范標準加重負擔,導致開始做一個簡單頁面時也顯得縮手縮腳,無法放開身手。大膽的動手實踐,才能真正 得出屬于自己的“結論 “和“標準“,才會真正深刻理解那些“結論”的意義所在。代碼寫的多了,自然熟能生巧,也容易形成成熟的技術觀點。

            在這個過程中,我們唯一的對手是懶惰,惰于思考,就無法真正發現問題,自然形不成自己的觀點。還是那句話,任何規范、方法、結論、實踐都是為了 解決項目中的問題的,所以,我們所接觸到那些看似“八股文”式的規范標準也是為了解決某些問題而提出的,想清楚這些問題,理解方法論背后的“因“,內心自 然有“果”。

            因此,“著眼當下、對癥下藥”的品質就顯得彌足珍貴了,比如,雙飛翼布局方法是為了解決一套(html)代碼適應多種布局設計,這里的布局相對 于固定的產品來說也是固定的,而無針對終端的自適應(適用于移動端的榻榻米布局似乎還沒有最佳實踐)。這是雙飛翼產生的背景,如今終端環境較之5年前已經 翻天覆地,問題早已不在“多種布局”上,而在“終端適應“上,這才是我們面臨的問題,需要我們給出新的技術方案。

            所以,勤于思考,輕裝上陣,大膽實踐,勇于創新,發掘問題所在,實打實的解決(潛在)問題,這才是我們真正需要的能力。放下思維定勢枷鎖,也會有一種豁然開朗的感覺。

            第二日:科班秀才

            【秀才仕途】

            Web前端工程師是一個特別的崗位,只存在于互聯網領域。最近幾年隨著互聯網產業的火爆,對前端工程師的需求量暴增,兵源幾近枯竭。各大公司技術掌門一定都有過類似的苦惱:“招一個靠譜的前端工程師、難于上青天”。

            我想,一部分原因是,當前不少入道的前端工程師大都是轉行而來,畢竟,正兒八經的學校里也不會教這玩意,覺得“切頁面”有啥好教的,甚至不覺得 html/css是一門語言。轉行這事自不必詳說,大家也各自瞄準當前市場需求,造成的現象是,初級前端工程師堆成山,中高級人才卻一將難求,計算機系的 科班出身就更加鳳毛麟角了。一方面反映了教育部門的后知后覺,另一方面也體現了大部分人急功近利的跟風。當然最重要的原因是,所謂中國“第一代前端工程 師”并未做好布道的工作。導致大家對于基礎和潛力的態度從之前的忽視演變為如今的蔑視。所謂基礎,就是在大學上的那些計算機基礎課。所謂潛力,就是戒驕戒 躁的務實作風。這些會在后文中多次提到。

            對于科班出身的莘莘學苗來說,根正苗紅本身就是一種優勢,事實證明,這些人在前端技術上的成長軌跡有一定的套路,而且大都能如期的突破技能瓶頸。從一個人大學畢業到他最滿意的工作狀態,中間會經過幾個階段。

            前2年是學習技能的階段,這個階段主要精力放在專業技能的提升上,2年內起碼要趕上平均水平,即所謂“中級“,在這個階段的人通常對軟技能不怎 么關注,溝通能力達不到平均水平,基本上是來啥活干啥活,干不完就加班的這種,對需求的合理性不甚理解,對項目也沒什么把控,盡管在技能上有提高的空間, 也不是公司最需要的人,但有不少成長空間。

            工作2-3年的人在前端技能上趨于穩定,也就是技能上的第一次瓶頸,這種人干活熟練,切頁面可能也很快,代碼看上去也比較規范,屬于熟練工,開 始注重溝通技巧和一些職業技能的積累,比如帶人帶項目,至少有這方面的意識,并有過推動項目、和業務方pk需求的經歷,這就達到了中級應當具備的職業技 能,但應當注意的是,這時最容易出現偏科的情況,特別是對于那些“專門切頁面的“和“專門寫腳本的“人,畢竟html/css/js三者不分彼此,三者是 一個合格前端工程師都必須要掌握的。如果你覺察到自身有偏廢的嫌疑,則要小心了,要清楚的了解自身的差距,并意識到瓶頸的存在,為過渡到“中級“的打下基 礎。

            過了這道坎之后,工作3年以上的人大部分技能也趨穩,有些人對前端新技術有鉆研,能夠熟練應對日常工作,軟技能也ok,具備有針對性的“拿來主 義“,代碼也具有一定的架構性,開始突破“代碼民工”的這一層瓶頸,對團隊氣氛、培訓、工作環境有個性化的要求,一般來講,這種人是典型的具有潛力的“中 級”工程師,但很快會遇到職業發展中的第二個技術瓶頸。

            有少數工作3年或4年以上,在不斷尋求新的技能上的突破,最明顯的一點體現是,開始關注“底層協議”,即HTTP、第三方應用、系統對接、制造 工具、工作流程等,這時思考的重點已經脫離了“切頁面”,變為“出方案“,比如要架設一個站點,能夠搭建站點框架,預見站點后續(前端)開發中的所有風 險,并一一給出解決方案。項目后續開發遇到問題只要翻閱你提供的“手冊”即能找到答案。這種人是標準的“高級”Web前端工程師。

            出方案是一件挺難的事情,它要求一個工程師同時具備經驗、技術、氣場等諸多硬技能。尤其是對技術底子的要求非常高。

            【半路出家】

            那么,轉行做前端的人又當如何呢?其實發展軌跡和科班秀才們非常類似,只是時間跨度可能會長一些,你要花更多的精力、做更多的項目、更多的反思和總結才能理解某個知識點的本質(比如HTTP協議)。當然這只是一般情況。

            此外,這些人還需要擺脫很多思維定勢的禁錮。這里我推薦大家閱讀阿當的《Web前端開發修煉之道》。當然,如果你有一個靠譜的師兄帶你入道,自然幸運萬倍。

            但不管怎樣,我始終認為應當秉承興趣第一的原則,不管你是誤打誤撞、還是意欲為之,不管你是科班秀才、還是半路出家,興趣始終應當是第一原則, 然后才是你“想做好“。我對自己的要求無法強加于人,所以很多業界大牛在回顧自己成功之路時,提到最多的是:“熱愛你的工作、擁抱它給你帶來的挑戰”。 N.C.Zakas曾經這樣勉勵大家:

            “我對Web開發人員最大的建議就是:熱愛你的工作。熱愛跨瀏覽器開發帶來的挑戰、熱愛互聯網技術的種種異端,熱愛業內的同行,熱愛你的 工 具。互聯網發展太快了,如果你不熱愛它的話,不可能跟上它的步伐。這意味著你必須多閱讀,多動手,保證自己的才能與日俱增。下了班也不能閑著,要做一 些對自己有用的 事兒。可以參與一些開源軟件的開發,讀讀好書,看看牛人的博客。經常參加一些會議,看看別人都在干什么。要想讓自己快速成長,有很多事兒 可以去做,而且付出一定會有回報。“

            第三日,幸福感

            【先精通十行?!】

            興趣第一,聽上去很美,但現實卻不總是這么酷。練就了一身本領,那也要找到對口的怪物來打一打才過癮。

            自然,每個人都想做出好東西,每個工程師也都渴求這樣的機遇,用層次分明的設計、漂亮優雅的代碼、精妙的細節雕琢,做出美觀、安全、實用耐用的 產品,不過現實是如此殘酷,以至于工程師們一直都缺乏對產品的歸屬感。作為前端工程師,如何才能在江湖中把握住前進方向、步步走高?畢竟,在職位繁雜的大 公司,缺乏人性化的工作流程影響著工程師的工作幸福感。產品從設計之初、到技術方案評審、再到實現,處處充滿了妥協,大部分產品都是雜交的產物,人與人相 互掣肘,每個人都對產品不滿意……,大躍進式的敏捷開發早就被證明百害無一利。但,或許這就是成長的代價。年輕的工程師需要更多的了解需求和設計、產品經 理更要懂得軟件迭代規律。對于前端工程師來講更是如此,多學習交互設計和UI,多了解網絡協議和軟件迭代模型,更能幫助前端工程師和需求方溝通、和后臺的 銜接、以及控制版本的迭代。

            說來奇怪,前端工程師不是寫html/css/js的嗎,搞懂那些邊緣知識有什么用?《Web前端開發修煉之道》中也提到,精通一行需要先精通十行。這里我來解釋一下原因。

            作為交互設計師的下游,前端工程師學需要習設計知識是很容易理解的,因為它能幫助你更準確的理解設計師的意圖,在原型不完整的時候也能正確的反 饋設計缺陷,將問題阻擋在設計的環節,會大大減少UI bug數量,比如說,設計師會給出理想狀態下的容器樣式,卻往往忽略了文字溢出折行、長連續字符、 容器寬高是否適應內容尺寸變化而變化,溢出部分是作截字還是隱藏等諸多細節,因為設計師不懂“邊界值測試”的道理,而這些問題往往在測試階段才被發現,所 以,如果能在拿到UI設計稿時就提醒設計師補充完整這些場景,自然減少測試回歸次數。

            另外,前端工程師必須要了解網絡協議,原因很簡單,我們做的產品運行在Web上。很多依賴于Ajax的實現,只有前端工程師才會提出實現方案, 產品經理不了解技術瓶頸,后臺工程師更不會在意客戶端的用戶體驗,舉個簡單的例子:通過JS實現一個Ajax,如果Ajax抓取的數據源是一個302跳 轉,則需要在JS程序中多做一些事情,這就需要前端工程師了解一些HTTP協議。應當說,這是很常見的一個場景。

            那么,為什么說前端工程師也要關注代碼版本控制呢?因為web開發和軟件開發本質無異,同樣具有迭代周期,需求不是一攬子提完、一口氣開發完 的,是有步驟的開發,因此,每次上線開發哪些功能、為后續擴展功能留足哪些接口、代碼在可擴展和可維護性上應當作哪些考慮……,這些應當是每個工程師關注 的事情,所謂迭代就是指這種需求的疊加,這是軟件開發的常態,也是web開發的常態,剛開始,前端工程師總會不斷抱怨沒完沒了的需求,代碼起初還算干凈, 但很快就越來越亂,代碼的版本管理對于Web前端工程師來說有些困難,這也使得大部分前端工程師很難上檔次,從這個角度講,前端工程師是需要向后臺工程師 學習的,他們的開發量不比前端少,維護代碼的能力要超過前端工程師。另外,對于剛入行的前端工程師,心態要放對,提需求是產品經理的職責所在,整理出有價 值的需求是交互設計師的職責所在,將需求作版本控制分步實現是前端工程師的職責所在,前端工程師沒必要去抱怨產品經理提一大堆沒規律的需求,而更應當去理 解需求緣由,將需求提煉成UC(用例),讓需求在自己手中可控制。只是多數前端工程師缺乏提煉、整理需求的能力,一味的在接需求,才會搞的手忙腳亂,帶著 情緒堆代碼。

            所以,只有練就了一身本領,才會更有目標的去尋找對產品的責任感和對團隊的歸屬感,不要誤以為能切出漂亮的頁面就是能力的提高,純粹的寫代碼每 個人都差不多的,要成為合格的工程師,眼界要進一步放開,前端工程師能做的,不僅僅是切頁面而已,作一個精品項目,一定不乏專業的過程把控,這也是大多數 人最易忽略的地方。

            【勵志之本】

            其實,除了個人需要明確努力的方向,每個人都更渴望身處一個好團隊,誰都不希望有豬一樣的隊友。我們都很羨慕處身這樣的團隊,可以放心的將精力 放在純粹的技術上,身邊每個人都自覺的補充文檔注釋,代碼也層次清晰解偶充分重用率高,精妙的設計實現可以更快的傳播,bug得到的改進建議也是務實專業 的,技術在這種良性互動中價值倍增。我想這也算是好團隊的一種境界了,這有賴于團隊成員水平水漲船高。不過,反觀Yahoo的成長之路,他們的技術積淀也 是靠點滴的積累,其實他們當初的狀況不比現在的我們好哪去,10年的進化,才造就了Yahoo技術團隊的專業性和Hack精神,我們每個人才剛剛起步而 已。為了積攢工作中的幸福感,多付出一些是值得的。

            但我猜,你現在的處境一定不會太過樂觀,產品亂提需求、一句話的PRD、不被重視,被生硬的當作“資源“……反正,情況就是這么個情況,要么你 選擇抱怨下去,要么想辦法去改變。“積極主動“是源自內心的一種堅韌品質,也是勵志之本,有些人在現實中被磨平了理想,有些人卻在黑暗森林中找到了方向, 這就是犬儒主義和英雄氣概之間的差別。這自不必詳說,因為這讓我想起了“大長今”,這簡直就是前端工程師的勵志典范:“這是一個可怕的環境,足以消磨任何 人的斗志和信念,所有來這里的人都變得麻木和無所作為,‘多栽軒‘惡劣的環境沒有改變長今,但長今卻改變了‘多栽軒‘所有的人“。

            如果你想做到“資深”,就一定要想清楚這一點,因為你是團隊的頂梁柱(業務),也是幸福感的源頭(士氣)。

            第四日,架構和偽架構

            【代碼設計的本質】

            讀到這里,你不禁會問,前端領域存在“架構師”嗎?這個問題會在后面的“碼農的宿命”中展開解釋。這里先說下代碼架構的一些瑣事吧。

            什么是架構?架構是由“架”和“構”組成,架,即元件,構,即連接件。因此,架構即是將總體分解為單元,然后定義單元之間的連接方式。架構的含 義源自禪宗,而禪宗的基本信條則之一就是真理是無法用語言來描述的。這個基本信條有其背景,即語言具有某種抽象性。而人們對這種抽象性的悟道則直接影響對 事物的看法,進而決定了對客觀世界的分解方法。

            而在編程語言中,同樣存在這種禪宗所隱喻的悖論。在面向對象的教科書中,通常舉一些顯而易見的例子,比如“水果”是一個類,包含有蘋果、桔子、 香蕉等實例,“蔬菜”也是一個類,包含白菜、冬瓜、茄子等實例。這兩個類之間并無交集,因此很容易理解。但實際項目中情況要復雜的多,比如兩個圖書類目 “文學”和“歷史”,那么“明朝那些事”應當是“文學”類的實例還是“歷史”類的實例呢?即一旦用語言說出了某一事物,即人為的割裂了世界,于是就會陷入 迷途。這在程序設計領域情況更甚,也是造成混亂的主要根源,也就是說,如果你的程序可擴展性不好,一定是程序作者對“單元”的定義不夠準確,即單元的概念 之間不夠“正交”。而這種架構終是徒有其形,根基不穩。

            因此,變量和類的命名才是真正考驗架構功力的關鍵(命名是否準確清晰、單元之間是否有概念重疊或盲區),而和所謂“組合”、“繼承”、“橋接”等模式化的“外表”無本質聯系。

            【偽架構】

            實際情況是,程序員早早的就想讓自己和“架構”扯上關系,并自封xx架構師。在項目中應用各種模式分層、解耦方法,每個項目都可以產出一套看上 去很復雜的“架構圖”,感覺很牛逼的樣子,沒錯,實踐這些方法論總不是壞事,但世界觀才是方法論的基礎,只有在概念上對產品模塊有科學的定義,方法論便自 然形成了,《編程珠璣》中一再提及數據結構就是靜態的算法,在Web前端領域亦是如此,在頁面的建模過程中,定義分解維度要比分解方法更加基礎和重要。我 想阿當可以在《Web前端開發修煉之道》的第二版里加上這部分內容。

            真正的高手用記事本就能寫出高質量的代碼、用cvs就能做到完美的版本控制、用字典式的分解就能做好系統架構,我想,這正是劍宗一派的最高境界吧。

            第五日:尋找突破

            【動心忍性】

            技術流派看上去是如此吸引人,高手就像俠客一般,來去如風瀟灑自如。但反觀自己怎么看怎么沒有俠客那股范兒。盡管上文提到了一些道理,了解這些 盡管不是壞事,但缺少實踐總感覺是紙上談兵。更何況,日常的工作又是枯燥無味、繁雜單調。每個人都盼望更高的目標、接觸新鮮技術、將新技術運用到日常,在 探索嘗試之中尋找成就感。這種感覺可以理解,但卻缺少更深層次的思考。因為越到最后越會發現一線的工作才是最有挑戰的。當然,我說這話的前提是,你能如前 文所說具備合格的軟技能,需要一些技巧讓工作變得工整有序、節奏健康,這樣你才能將注意力放在純粹的代碼中,擺脫了外界的煩擾,方能從技術的角度思考突 破。這也是從初級到高級的進化過程需要大量的歷練的原因。正如玉伯所說,“枯燥是創新的源泉。如果你發現自己沒什么新想法,做事缺少激情,很可能是因為你 還未曾體驗過真正的枯燥的工作”。

            關于如何尋找突破,我的建議是馬上動手做、不要等,相信自己的直覺(這里和上文提到的先思后行是兩碼事)。比如,Slide幻燈控件理應支持觸 屏事件以更好的適應移動終端,或許你在用的Slide幻燈版本很舊、或者時間不允許、再或者你害怕對Slide改造而引入bug,不要擔心,大不了多花業 余時間,只要想,只要感覺合理和必要,就去做。因為這個過程帶來的編程體驗才是工程師們獨有的美妙體味。我現在還時常深夜寫代碼,沒有打擾、思如泉涌、代 碼也更加工整嚴謹,不失為一種享受。因此,用眼睛去觀察,用心去感觸,“所以動心忍性,才會增益其所不能”啊。

            【得與失】

            互聯網的發展的確太快,Web前端技術也在花樣翻新,有人經不起誘惑,開始做新的嘗試。前端技術雖然范圍廣,但各個分支都還比較容易入門,比如 服務器端腳本編程、再比如純粹的WebApp,我認為這兩者都是前端技術的范疇,畢竟他們都沒有脫離“瀏覽器”,或者說類似瀏覽器的環境。NodeJS依 賴于V8,WebApp更是軟件化的WebPage。只要打好基礎,這些方向都是值得深入鉆研的,因為,互聯網的形態越發多元,新的技術總能找到用武之 地,這就要憑借自己的技術嗅覺和產品直覺,尋找技術和業務的契合點。

            這看上去是一種放棄,放棄了自己賴以生存的鐵飯碗(熟練的切頁面至少不會失業),實則不然。這種想法是一種誤區,新的選擇并不會讓你放棄什么, 就像學會了開車,并不意味著就不會騎車了。其實改變的是思維方式而已,是一種進步,如果你能想通這一點,你也能跟得上互聯網發展的腳步了,打開你的思維, 讓技術變成你的金剛鉆,而不是包袱。

            所以,所謂得失之間的權衡,其實就是“解放思想”。做到了這一點,那么你已經在做“技術驅動”了。

            【誤區】

            但是,不要高興的太早,“技術驅動”是需要大量的積累和經驗的。在入行初期,很多人過于著迷與此,從而陷入了迷途。比如有人糾結于是否將dt、 dd的樣式清除從reset.css中拿掉,原因是覺得這兩個標簽的清除樣式會耗費一些渲染性能;或者是否需要將for循環改為while循環以提高js 執行速度。盡管這些考慮看上去是合理的,但并不是性能的瓶頸所在,也就是說,你花了很大力氣重構的代碼帶來的頁面性能提升,往往還不如將兩個css文件合 成一個帶來的提升明顯。就好比用一把米尺量東西,沒必要精確到小數點后10位,因為精確到小數點后2位就已經是不準確的了。這種技術誤區常常讓人撿了芝麻 丟了西瓜。

            話說回來,這里提到的懷疑權威的精神是絕對應當鼓勵的,但不應當止于表象,如果懷疑dt的清除樣式會對性能帶來影響,就應當想辦法拿到數據,用事實來證明自己的猜測。數據是不會騙人的。而求證過程本身就是一種能力的鍛煉。

            【技術驅動】

            說到這里,你大概對“技術驅動”有那么一點點感覺了。身邊太多人在抱怨“公司不重視前端”、公司不是技術驅動的、技術沒機會推動產品業績、我的價值得不到體現?

            什么是技術驅動?簡單講,就是技術對業務有積極推動作用。更多的是工程師發起、工程師影響、工程師負責。剛才提到的用數據說話只是一種“驅動”技巧,那么我需要何種數據,數據從哪里來?我來分享一個實際的場景吧。

            工程師A被委派一個重要的頻道首頁,因為是新年版,所以要趕在年前上線。A學了一點點響應式設計,想在這次重構中加上,但誰也沒做過響應式設 計,需求方根本不懂,設計師也懵懵懂懂,交互設計師太忙,做完交互稿就忙別的去了。A糾結了,按部就班的把項目做完上線發布,盡管不會出什么問題,但總覺 少點什么。這時A做了兩個決定,1,我要按時完成項目,2,趁機實踐我在響應式設計中的想法和思考,若成功,作為附加值贈送給需求方,若失敗,權當技術玩 具耍一耍罷了。所以A熟練的提前完成了項目,剩下的時間開始考慮如何將首頁適應到各個平臺中,視覺設計是一大難題,他用吃飯的時間找了設計師收集建議,對 窄屏中的內容模塊做了看似合理的編排,代碼上hack一下,能夠正確適配,就發布上線了。這件事情需求方不知道,視覺設計師也不了解,交互設計師更沒工夫 操心。A感覺挺爽,開始給工程師弟兄們到處炫耀這個好玩的功能,B看了問,手機端訪問量如何,A覺得這個問題有道理,就去部署埋點,一周后拿到數據出奇的 意外,首先,移動段的訪問量穩步增加,趨勢健康,再者,移動端首屏焦點廣告位的點擊率較PC端高了近一倍,這個數據讓A喜出望外,興奮的拿著報表找到交互 設計師C和市場研究的同事D,D看了報表之后立即啟動一個項目,專門調研公司全站響應式設計頁面在PC端和移動端的點擊率、PV、UV趨勢方面的影響…… 后來發生的事情就都水到渠成了,設計師C開始注意設計頁面交互時(至少是有條件的考慮)對移動端的適配,D的調研報告也放到了UED老大的案頭……接下來 的事情,你懂得。A被指派要出一套響應式最佳實踐和規范,最終,A走在了技術的前沿,也因此拿到了好績效。

            這件事情就是一個典型的技術驅動的例子。誰不讓你玩技術了,誰不重視你了,誰把你當工具了,誰覺得你的代碼沒價值?這世界只有自己把自己看扁,誰想跟你這個蠅頭小卒過不去?用實力說話,用數據說話,用獨到的見解說話,想不做技術驅動都難。

            第六日:碼農的宿命

            【青春飯】

            “碼農”是IT從業者一個自嘲的稱號,也有從事沒有發展前景的軟件開發職位,靠寫代碼為生的意思。但我認為碼農是一個愛稱,編碼的農民,和農民 一樣有著執著純真樸實豪爽的共性,僅僅分工不同而已。就好比農業社會對糧食的依賴,工業化進程對計算機應用也有著很強的依賴,大量的需求催生出這樣一群 人。他們有智慧的大腦,對于編程,設計,開發都有著熟練的技巧,但多數人看來,碼農的特點是:

            1,收入低
            2,工作單調
            3,工作時間長

            實際上這個描述非常片面,或者說是外行看熱鬧。第一,全行業比較來看,軟件開發領域收入為中等偏上;第二,程序員一般都是有癖好的,沉浸在自己 的癖好中是不會感覺單調的;第三,程序員有一定的時間自由度(如果你是一名合格的程序員的話),至少不會像流水生產線工人一樣。其實,通過幾十年的發展, 我們對程序員的定義更加科學,比如很多IT企業都開始建立詳細的JM(Job Module),即職級模型,程序員沿著專業方向可以走到很高,甚至可以 說,程序員是可以被當成一生的事業的。

            然而,有一個非常普遍的觀點是,程序員和做模特一樣是吃青春飯的,到了三十歲就要考慮轉行或者轉管理。盡管這種觀點頗具欺騙性,但至少它對一種 人是適用的,即入錯了行的人。如果你骨子里不想寫程序,就算年紀輕輕為了生計寫幾年代碼,之后肯定會另有他途。心非所屬則不必勉強,但問題是,即便如此, 你知道你的心之所屬嗎?

            我們知道,一個成熟的產業一定需要各色崗位來支撐,若要成熟,則需要時間的沉淀,比如實體經濟制造業,創意、生產線、高級技工、技術管理四個方 面都產出大量的高級人才。因為歷史悠久,我們能看得到。而軟件產業則不然,九成以上是剛出道的新手,并沒有太多“高級”和“資深”的具體樣板可供參照,在 前端開發領域中情況更甚,絕大部分人根本搞不清楚什么樣才是“資深”前端工程師,相比傳統軟件行業近四十年的進化,我不相信僅有幾年光景的前端技術崗位能 產出多少貨真價實的“資深”。但互聯網崛起速度太快,還沒有等技術基礎打牢,互聯網形態就又花樣翻新了,這種變化是一種常態,而崗位的設定也在這種變化之 中自然的優勝劣汰,比如兩年前可能還難以想象數據部門會需要前端工程師,他們甚至不直接和瀏覽器打交道。前端工程師需要適應這種變化帶來的觀念沖擊,不要 以為自己只能做切頁面、或者只會給頁面搞重構、只會搞兼容性,要把自己放在整個軟件行業來看。

            所以,由于歷史“不悠久”導致的崗位模糊本身不是什么大問題,崗位的演化本身就包含在互聯網的發展軌跡之中。所以,當今的互聯網IT狀況,就好 比移動終端的大哥大時代、云計算的肉雞時代、或者桌面操作系統的DOS時代。因此,前端工程師當前要務是要想清楚看清楚,在互聯網中我能做什么,而不是作 為前端工程師我能做什么,所以,從這個角度講,技術是一個工具,放大來看,技術也只是你職業生涯中很小的組成部分,而你的從業積累、和知識面的廣度深度才 是你隨著時間的推移慢慢步入“資深”的原因所在,而不是寫了個什么框架就變“資深”了。如果有一天互聯網形態固定了,它的崗位可能真正就定型了,才會有真 正清晰的職能邊界,就像藍色巨人IBM中的各色崗位一樣,邊界清晰,權責分明,普通程序員只能實現接口而無機會設計接口、低層級的工程師也無機會躍進式的 接觸項目架構、技術經理人也不能輕易對產品有決策性影響,到這時,人的能力才真正的被限制在方圓之內,容不得越界,這種環境下人的成長非常緩慢。根本不會 有像今天互聯網亂局之中所提倡的創新、革命、成長和思想解放。簡單講,一旦產業定型,就不太需要很多“創造”了,更多的是“維護”。所以,我個人寧愿互聯 網IT“黑暗”的中世紀越久越好,至少對于年輕氣盛程序員來說,黑暗的叢林環境才是真正的自然進化最理想的土壤,這時我想起了狄更斯在“雙城記”中的開 篇。

            “這是最好的時代,這是最壞的時代;這是智慧的時代,這是愚蠢的時代;這是信仰的時期,這是懷疑的時期;這是光明的季節,這是黑暗的季節;這是希望之春,這是失望之冬;人們面前有著各樣事物,人們面前一無所有;人們正在直登天堂,人們正在直下地獄”。

            【半路出家的危與機】

            然而,不管怎樣,信心的樹立不是一蹴而就的,對于轉行做前端的人來說更是如此。俗話說,隔行入隔山。每個行業自有其道,自然不是想做就做。前端 技術領域半路出家者非常多,我們來分析一下轉行的心理。第一,看到前端技術入門簡單、互聯網對前端技術的需求缺口巨大;第二,前端技術所見即所得、感覺學 習起來很快;第三,我身邊的某某轉行作前端看上去不錯、我似乎也可以;第四,我不喜歡我現在做的工作、想換行業、正好前端技術上手較快,就選他吧;第五, 我真的喜歡做Web前端,為它付出再多都是值得的。

            轉行者的心態比較容易走兩個極端,一是只看到新行業的好,二是只覺得原工作很糟糕。但不管是什么行業的轉行,對自己的職業規劃的思考都應當先行一步。即務必首先清晰的回答這些問題:

            1,我能做什么?
            2,我不能做什么?
            3,我的優勢是什么?
            4,我的劣勢是什么?
            5,做新行業對我有何好處?
            6,換行會讓我付出何種代價?
            7,如何定義轉行成功?

            因為面試的時候一定會被這些問題所挑戰。如果支支吾吾說不清楚,要么是對自己未來不負責任,要么骨子里就是草根一族,習慣做什么都蜻蜓點水淺嘗 輒止,也難讓人信服你的轉行是一個權衡再三看起來合理的選擇。我無法幫每個人回答這些問題,但至少有兩點是確定的,第一,Web前端技術是一個朝陽行業, 絕對值得義無反顧的堅持下去;第二,你將經歷從未有過的枯燥、苛刻的歷練,所謂痛苦的“行弗亂其所為“階段。不過話說回來,經歷過高考的人,還怕個屁啊。

            有心之人自有城府、懂得放棄,看得清大勢中的危機、識得懂繁華里的機遇。尤其當立足于Web前端技術時,這種感覺就愈發強烈。因為國內外前端技 術領域從2000年至今一直非常活躍,前端技術前進的步伐也很快,對于一些人來說,不管你是在大公司供職還是創業,不管你是在接外包項目還是自己寫開源項 目,從轉行到跟得上新技術的腳步是有一些方法和“捷徑”的。

            第一,梳理知識架構

            我們知道知識積累有兩種思路,第一種是先構建知識面、建立技術體系的大局觀,即構建樹干,然后分別深入每一個知識點,即構建枝葉,最終形成大 樹。第二種是先收集知識點,越多越好,最后用一根線索將這些知識點串接起來,同樣形成大樹。第一種方法比較適合科班秀才,第二種方法則更適合轉行作前端的 人,即實踐先行,理論升華在后。比如對“IE6怪異模式“這條線索來說,要首先將遇到的IE6下的樣式bug收集起來,每個bug都力爭寫一個簡單的 demo復現之,等到你收集到第100個bug的時候,再笨的人都能看出一些規律,這時就會自然的理解IE的hasLayout、BFC和各種bug的原 因、你就成為了IE6的hack專家了,當你成為100個知識線索的專家的時候,你已經可以稱得上“資深”的水平了。我們知道,10個人中有9個是堅持不 下來的,他們會以項目忙等各種理由萬般推托,將自己硬生生的限制在草根一族,坐等被淘汰。所以,對于立志作前端的人來說,這種點滴積累和梳理知識非常重 要。

            第二,分解目標

            將手頭的工作分解為幾部分來看待,1,基本技能,2,項目經驗,3,溝通能力,4,主動性和影響力。想清楚做一件事情你想在哪方面得到歷練,比 如,我之前在做第一次淘寶彩票常規性重構的時候(正好是一次視覺和交互上的全新設計),我清楚的明白這次重構的目的是鍛煉自己在架構準富應用時的模塊解偶 能力,尋找在其他項目中架構的共通之處,所以我寧愿加班或花更多精力做這個事情,當然更沒打算向業務方多解釋什么,這件事情對我來說純粹是技能的鍛煉。而 經過這一次重構之后,我意外的發現對業務的理解更透徹深入、更清晰的把握用戶體驗上的瓶頸所在。如果一開始就把這次常規改版當成一個普通的項目按部就班的 做,我只能說,你也能按時完成項目,按時發布,但真真浪費了一次寶貴的鍛煉機會,項目總結時也難有“動心忍性”的體會。

            所以,每個項目的每個事情都應當認真對待,甚至要超出認真的對待,想清楚做好每件事對于自己哪方面有所提升?哪怕是一個bug的解決,即便不是 自己的問題也不要草草踢出去了事,而是分析出問題原因,給出方案,有目的involve各方知曉……,正規的對待每個不起眼的小事,時間久了歷練了心智, 這時如果突然遇到一個p0級的嚴重線上bug(比如淘寶首頁白屏,夠嚴重的了吧)也不會立即亂了方寸,這也是我上文提到的心有城府自然淡定萬倍,而這種淡 定的氣場對身邊浮躁的人來說也是一種震懾和療傷,影響力自然而然就形成了。

            第三,作分享

            做分享這事兒真的是一本萬利。有心的人一定要逼著自己做分享,而且要做好。首先,自己了解的知識不叫掌握,只有理解并表達出來能讓別人理解才叫 掌握,比如如果你解釋不清楚hasLayout,多半說明自己沒理解,如果你搞不懂雙飛翼的使用場景,可能真的不知道布局的核心要素。再者,作分享絕對鍛 煉知識點的提煉能力和表達能力,我們作為工程師不知道多少次和強硬的需求方pk,被擊敗的一塌糊涂。也反映出工程師很難提煉出通俗易懂的語言將技術要點表 述清楚。而做ppt和分享正是鍛煉這種能力,將自己的觀點提煉出要點和線索,分享次數多了,自然熟能生巧。檔次也再慢慢提高。另一方面,逼迫自己站在公眾 場合里大聲講話,本來就是提高自信的一種鍛煉。

            這時,你或許會問,我講的東西大家都明白,我講的是不是多余,我第一次講講不好怎么辦,大家會不會像看玩猴似的看我“這SB,講這么爛還上來講”?要是講不好我以后再講沒人聽怎么辦,我今后怎么做人啊?

            老實說,這是一道坎,任何人都要跨過去的,誰都一樣,你敢鼓起勇氣在大庭廣眾之下向愛人表白,就沒勇氣對自己的職業宿命說不?其實勇敢的跨越這 一步,你會意外的收獲他人的掌聲和贊許,這些掌聲和贊許不是送給你所分享的內容,而是送給你的認真和勇氣。這個心結過不去,那就老老實實呆在自己的象牙塔 里遺老終生,當一輩子工程師里的鉆石王老五吧。

            【匠人多福】

            如果你能耐心讀到這里,心里一定有一個疑問,上面說的都是技術上能力上怎樣怎樣,那我所做項目不給力又當如何?如果項目不掙錢、黃了、裁了,我的努力不就白費了嗎?我又有什么績效和價值呢?

            沒錯,有這種想法的人不在少數。特別是剛出道的校招同學往往更加心高氣傲,以為自己有改變世界的本事,一定要參與一個牛逼的團隊做一款光鮮靚麗受人追捧能給自己臉上貼金的項目。如果你有這種想法,趁早打消掉這個念頭,當然,我們這里先不討論創業的情形。

            第一,如果你剛畢業就加入一個牛逼團隊,說難聽點,你就是團隊中其他人眼中的“豬一樣的隊友”,不創造價值且拖項目后腿(顯然大家都要照顧你的成長啊),按照271理論,你沒有理由不是這個1。至少相當長一段時間內是這樣。

            第二,你在所謂牛逼團隊中的創造性受限,因為創新多來自于團隊中的“資深“和大牛們,你參與討論但觀點通常不會被采納,他們只會給你這個菜鳥分活干,想想看,你如何能花兩到三年就超越身邊的大牛們?甚至連拉近與他們的距離都難。

            第三,如果身在牛逼團隊,自然心理對周圍的牛人們有所期待,希望他們能灌輸給你一些牛逼的知識和牛逼的理念。這種思想上的惰性在職場生涯之初是非常危險的。要知道技術和知識本身是很簡單和淳樸的,只不過披上了一個光鮮項目的外衣而讓人感覺與眾不同。

            第四,由簡入奢易,由奢入簡難,做過一個看似光彩的項目,心理再難放平穩,去踏實的做一個看上去不那么酷的產品。這種浮躁心態會嚴重影響今后的職業發展和成長。

            第五,光鮮靚麗的項目被各種老大關注,是難容忍犯錯誤的,傻瓜都知道犯錯誤在成長之初的重要性。

            就我所看到的情形看,一開始加入看似很牛的項目組,三年后得到的成長,比那些開始加入一個不被重視的項目的同學要小很多,而后者在能力上的彈性 卻更大。所以,道理很簡單,你是要把一個很酷的項目做的和之前差不多酷,還是把一個不酷的項目做的很酷?項目是不是因為你的加入而變得與眾不同了?

            從這個角度講,不管是轉行的新人還是剛出道的秀才,最好將自己當作“匠人”來對待,你的工作是“打磨”你的項目,并在這個過程中收獲經驗和成 長。付出的是勤奮,鍛煉的是手藝,磨練的是心智。因此,你的價值來自于你“活兒“的質量,“活兒”的質量來自于你接手的項目之前和之后的差別。做好活兒是 匠人應有的職業心態。想通這一點,內心自然少一些糾結,才會對自己對項目的貢獻度有客觀的認識,不會感覺被項目所綁架。

            做一名多福的匠人,擁有了金剛鉆、就不怕攬不到瓷器活兒。但對于人的成長來說,如果說“項目”重要但不關鍵,那么什么才是關鍵呢?這個話題還會在接下來的“伯樂與千里馬”這篇中給出答案。 

            【若干年后】

            現在,讓我們回過頭回答一下“青春飯”的問題。在“青春飯”小節中提到,“程序員到三十歲之后需要轉行或者轉管理嗎?”

            上文提到,工業化生產的四個領域,1,創意,2,生產線,3,高級技工,4,技術管理。Web前端技術也是如此,可以在這四個領域找到各自的歸宿。

            第一,“創意“

            即和產品需求越走越近,擁有良好的產品感,對產品需求、設計交互把握準確,能夠用適當的技術方案推動產品用戶體驗,屬于“架構師”的范疇,因為 職能更加靠前,偏“出主意”型的。這種人更貼近用戶,需要活躍的思維、廣闊眼界、厚實的項目經驗。更多的影響產品體驗方面的決策。

            第二,“生產線“

            即前端基礎設施建設,優化前端開發流程,開發工具,包括開發環境、打包上線自動化、和各種監控平臺和數據收集等,屬于“技術支持”的范疇,相比于很多企業粗獷難用的平臺工具,前端技術方面的基礎設施建設基礎還需更加夯實,因為這是高效生產的基本保證。

            第三,“高級技工“

            即高級前端開發工程師,專職做項目,將產品做精做透,用代碼將產品用戶體驗推向極致,偏“實戰”型的,是項目的中堅力量,直接產出成果,影響產品效益。屬于項目里的“資深”。

            第四,“技術管理“

            即做技術經理,這才是多數人所理解的“管理”,其實就是帶團隊、靠團隊拿成果。這類人具有敏感的技術情結,在技術風潮中把握方向,能夠指導培訓新人,為各個業務輸出前端人才,偏“教練”型的,促進新技術對業務的影響。并有意識的開辟新的技術領域。

            可見,轉管理可不是想當然,也不是所謂做項目變資深了就能轉管理,轉了也不一定能做好。根據“彼得原理”,即人總是傾向于晉升到他所不能勝任的崗位,這時就又陷入“帕金森”定律所隱喻的惡性循環之中,直到你帶的團隊整個垮掉。

            所以,轉管理應當是一件非常慎重的事情,不是所謂程序員混不下去就轉管理這么簡單。但不管怎樣,有一件事情是需要尤其要想清楚,即,轉了管理,技術就丟了嗎?我們在第七日“伯樂與千里馬”中再深入聊聊這個事兒。

            第七日,伯樂與千里馬

            【師兄們的抉擇 1】

            千里馬常有,而伯樂不常有。——韓愈,“馬說”。

            一個人這輩子能遇到一個好師兄是一種緣分,可遇不可求。很多人工作中的幸福感似乎也源自這種被認同,被師兄的了解和認同,有人能直言不諱的指出 你的不足,幫你發現機會,并將最適合你做的事情分配給你,這是莫大的幸運,但如此幸運的人十之一二,大多數人因為缺少伯樂的提點,漸漸辱于“奴隸人之手 “,潛力漸失,毀于中庸。

            在前端技術領域,這種情況很普遍也很特殊,當然有很多客觀原因。即前端技術進入公眾視野時間不長,有實力的伯樂更加是鳳毛麟角。更何況,Web 前端技術還有著一些江湖氣,知識點過于瑣碎,技術價值觀的博弈也難分伯仲,即全局的系統的知識結構并未成體系,這些因素也客觀上影響了“正統“前端技術的 沉淀,奇技淫巧被濫用,前端技術知識的傳承也過于泛泛,新人很難看清時局把握主次,加之業務上的壓力,未免過早導致技術動作變形。而這些問題也無法全賴自 己全盤消化,若有人指點迷津,情況要好上萬倍。因此,前端技術領域,為自己覓得一個靠譜的師兄,重要性要蓋過項目、團隊、公司、甚至薪水。

            這也是上文所說的“項目不重要,師兄才重要“的原因。說到這里就有一個問題,每個人都問下自己,你是想當師弟呢還是想當師兄呢?當師兄有什么好處呢?

            沒錯,很多師兄都是被師兄,甚至沒有做好當師兄的準備,更進一步說,不少經理人也都是“被經理人“,沒有做好準備就被推到了管理崗位。帶人是耗 精力的,師兄要做很多思想斗爭才舍得把這些寶貴的精力放在那些菜鳥身上,這不是一個技術問題,而是一個道德問題。要記住,沒有誰應該無緣無故把自己所掌握 技能給你傾囊相授,如此皆命也。讀到這里,作為菜鳥,作為學徒,作為新人,作為師弟,你做到對這份命運的足夠尊重了嗎?

            尊師重教的傳統美德并沒有在技術領域得以很好的延續。也正因為此,人才梯隊難建立起來,但對于師兄來說,卻是有更多機遇的。

            【師兄們的抉擇 2】

            作為師兄,不管是主動還是被動,肯定會想當師兄對我有什么提升?對于初次做師兄的人來說,最大的提升在于兩方面,1,任務分解,2,問題分析。

            第一,任務分解,作為師兄要給師弟派分任務,就涉及到任務分解,分解這事兒往低了說,就是派活,往高了說,其實就是做“架構”,比如一個頁面, 按照什么思路進行模塊劃分,模塊劃分是否適合單人開發,如何控制共用樣式和共用腳本,我需要為他提供什么接口,如何控制他的代碼并入整個頁面時不會影響整 體頁面代碼的熵值,這些都是實打實的“架構“應該包含的問題,而從小頁面開始就做這種鍛煉,做的多了,“架構感”自然就形成了。

            第二,問題分析,在之前自己寫代碼都是單打獨斗,什么都是用代碼解決問題,但一旦涉及協作,就要強迫自己分析問題,或者說給徒弟分析問題,告訴 他應當用什么方法來解決問題,當說到“方法”時,腦子里定形成了一個方案,按照這個方案路子走一定能解決問題。分析問題比寫代碼要更抽象、更高效,因為在 腦子里構建方案要比寫代碼要快,思考也會更加縝密,當鍛煉的多了,思考越來越快,代碼的草稿也很快就在腦海中形成了,這也是我們說為什么很多人不寫代碼但 編碼思路和水平都很高的原因。

            這些工作方法對了,積累多了,就是提高。對于技術經理人來說,也是同樣的道理。所以,就像在第五日的“得與失”部分提到的那樣,轉身師兄、變身管理并不意味著“失“掉技術飯碗,而是一種進步。

            【做自己的伯樂】

            那么,在前端技術領域里什么樣的人才算千里馬,其實人人都是千里馬,人人都可以發掘自己的潛力,如果上面的文字你能讀懂,能認可,這種自我發掘已經開始了,沒有一個好伯樂又何妨呢?做一個勤快的小碼農,少一些勢利的紛爭,很快會發現,自己才是最好的伯樂。

            但這并不是說,他人對自己的觀點不重要,有時甚至要綜合各種聲音,所以,多找身邊的大牛們聊聊天,多找你的師兄和主管,不管他們給你的建議是多么形而上,總有一些聲音對你是有益的,多收集,有好處。

            第八日,做地球上最牛的UED

            【誰推動了歷史前進,英雄?還是人民?】

            “做地球上最牛的UED!”,這是淘寶UED創立之初的口號,現在被漸漸淡忘了,因為微博上的一些討論,又想起了這份曾經美好的初衷。玉伯也感 嘆道:“這愿景曾吸引了多少好漢前往投奔呀。只可惜短短幾年間,這愿景好像越來越遠了”。問題是,要做好一個團隊,靠的是個人、還是整體?愿景是越來越遠 了嗎?

            是誰推動了歷史的前進,是英雄?還是人民?微觀來看,是英雄,宏觀來看,是人民。再放大了看,是互聯網大潮之崛起推動了前端技術的進步,時勢需要UED、需要用戶體驗。

            所以,UED團隊的創立發展受這些積極的外因影響,趕上了好時候,成員也跟著沾光。然而,我并不關心這個口號,我只關心體制內的關鍵人物,那些 帶動整個團隊水漲船高的人們。往往我們發現,某些人的高度代表了整個團隊的高度,個體的影響力代表了整個團隊的影響力,某個人的水平代表了整個團隊的水 平。支付寶、淘寶、騰訊、百度、盛大,都是如此。而我們作為普通的個體,正是要勵志成為這種人,成為真真用技術推動用戶體驗前進的尖刀人物。

            這時我想起了很多人在知乎上的問題,關于跳槽、關于轉行、關于創業、關于各種UED團隊。我想,讀得懂我上面的文字,你心理也許會有自己的答案。

            【歸宿】

            最后,還有一個不得不說的問題,即歸屬問題,前端開發應當歸屬于UED還是技術部門?應當說,當前Web前端技術的價值體現在“用戶體驗“上。 是用戶體驗這塊陣地最后一道坎。也就是說,前端工程師應當重點考慮我所作的頁面的感官體驗。這是需要一些靈感和感性的,應當看到帥氣優雅的界面會心有所 動、或者實現一款精巧的小組件時萌生一陣快意。這種所見即所得的美妙編程體驗正是其他后端工程師無法體驗到的。因此,這種精確到像素級的精工雕琢雖然不直 接決定產品生死,但卻是提升產品品味和時尚感的要素。物質越來越豐富的今天,大眾的更高訴求不就是品味和時尚嗎?

            如果將前端歸到技術部門,一方面和“設計“離的更遠,代碼寫的規規矩矩但漸缺少了靈性,另一方面作為工程師又缺少計算機專業課的功底,才真正喪 失了優勢所在,如果有一天,前端工程師的平均水平足夠高,清一色的計算機科班出身,似乎更合適歸入到技術部門。所以,Web前端工程師是“工程師“,需要 科學嚴謹的編程能力,但身處UED所應當具備的美感和靈性是萬不可被剝奪去的。

            還有一點,Web前端工程師作為UED之中最具實踐精神和邏輯思維的工種,是能夠將技術對設計的影響發揮到最大,可以催生出大量的創造和革新的,這一點也是傳統后端工程師所不具備的。

            第九日,前端技術體系

            現在越來越感覺到前端技術需要成體系的積累,一方面可以規范我們的前端技術培訓,另一方面,作為知識線索為新人做指引,省的走彎路,避免陷入奇技淫巧的深坑之中不能自拔。

            之前我整理了一下“前端技術知識結構”,羅列的比較散,但也基本表述清楚了我的觀點。今年上半年也在整個研發中心組織了一次前端技術培訓,對于前端技術的演化規律也有過整理,都放在了這個ppt中,希望對大家有所幫助。

          posted @ 2013-01-16 12:53 楊軍威 閱讀(298) | 評論 (0)編輯 收藏

          js面向對象

          【一】 面向對象的基本概念

            面向對象的英文全稱叫做Object Oriented,簡稱OO。OO其實包括OOA(Object Oriented Analysis,面向對象分析)、OOD(Object Oriented Design,面向對象設計)和OOP(Object Oriented Programming,面向對象的程序設計)。

            通常所說的面向對象是指OOP, OOP是一種圍繞真實世界的概念來組織模型的程序設計方法,它采用對象來描述問題空間的實體。在使用計算機解決問題時,對象是作為計算機模擬真實世界的一個抽象,一個對象就是一個物理實體或邏輯實體,它反映了系統為之保存信息和(或)與它交互的能力。使其具有自己的屬性和行為, 從而簡化對復雜事物的描述,更有利于工程的可維護性和擴展性。

            OOP同結構化程序設計相比最大的區別就在于: 前者首先關心的是所要處理的數據,而后者首先關心的是功能。

            【二】 面向對象三個基本特征

            封裝 (Encapsulation) 將數據以及相關的操作組織在一起,成為獨立的構件。外部無法直接訪問這些封裝了的數據,從而保證了這些數據的正確性。封裝的目的是為了內部數據表現形式和實現細節的隱藏,信息隱藏是為了減少系統各部分間的依賴性,各部分間必須通過明確的通道傳送信息,也就是對象間的接口.這樣一來,隱藏了部分內部的細節,極大方便系統的開發,維護和擴展。

            繼承 (Inheritance) 繼承是一種聯結類的層次模型,并且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。一個新類可以從現有的類中派生,這個過程稱為類的繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的基類那里繼承方法和實例變量,并且派生類可以修改或增加新的方法使之更適合特殊的需求。繼承性很好地解決了軟件的可重用性問題。

            多態 (Polymorphism) 多態是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是允許類與類之間相同方法名的指針得以調用, 這樣很好地解決了應用程序函數同名問題。實現多態,有二種方式,覆蓋,重載。

            【三】 Javascript 面向對象

            javascript本身是一種基于對象(object-based)的語言,我們日常編碼過程中用到的所有東西幾乎都是對象(Number, String, Boolean, etc.)。但是,相對于一些流行的面向對象語言(C++, C#, java),它又不是一種真正的面向對象編程(OOP)語言,因為它的語法中沒有class的概念。

            Keyword: class, object, `this`, closure, constructor, prototype

            幾種對象封裝的方法

          • 繼承
          • 多態體現

            之一、幾種對象封裝的方法

            1. 對象封裝 – 原始模式

            假定我們把貓看成一個對象,它有”name”和”color”兩個屬性, “etc” 行為。

          var Cat = {     name: ''     color: '',     eat: function() {} }; 

            現在,我們需要根據這個原型對象的規格(schema),生成兩個實例對象。

          function eat() {     console.log('I\'m eta fish'); } var cat1 = {name: 'Kitty', color: 'white', eat: eat}; var cat2 = {name: 'Smokey', color: 'black', eat: eat};  // var cat3, cat4 ,... 

            不方便創建多個實例對象,擴展性差, 實例(cat1, cat2)之間找不到聯系。…

            2. 對象封裝 – 構造函數模式

            “構造函數”,就是一個普通函數,但是內部使用了 `this` 變量。對函數使用 `new` 運算符,就能生成實例,并且 `this` 變量會綁定在實例對象上。

            使用構造器創建出來的對象會有一個 `constructor` 屬性,指向它們的構造函數。

            `Class` 只是一個模板,創建出來的來實例都是由模板生成。

            比如,貓的原型對象現在可以這樣寫:

          function Cat(name,color){     this.name = name;     this.color = color;     this.eat = function() { console.log('eat fish'); }; } var cat1 = new Cat('Kitty', 'black'); console.log(cat1.name); // Kitty console.log(cat1 instanceof Cat); // TRUE // 這時 cat1 實例會自動含有一個 `constructor` 屬性,指向它們的構造函數 `Cat`。 var cat2 = Cat('Smokey', 'white'); console.log(cat2); // undefined 

            3. 對象封裝 – Prototype 模式

            `prototype` 是 `Function` 對象的一個屬性,這個屬性指向另一個對象。 這個對象的所有屬性和方法,都會被構造函數的實例繼承。

            同時 `prototype` 又存在一個指向構造函數的引用 `constructor`,這樣就成功的構成一個循環引用的原型鏈結構。

            我們可以把那些不變的屬性和方法,直接定義在 `prototype` 對象上, 節省內存開銷。

          function Cat(name, color) {     this.name = name;     this.color = color; } Cat.prototype.type = 'mammal'; Cat.prototype.eat = function() { console.log('eat fish'); }; var cat1 = new Cat('Kitty', 'white'); var cat2 = new Cat('Smokey', 'black'); console.log(cat1.type); // mammal console.log(cat1.eta === cat2.eta);     // TRUE, same reference console.log(cat1.constructor === Cat)   // TRUE, from Person.prototype 

            之二、繼承 (Inheritance)

            將持有共性特點的屬性或行為抽象出一個基本類, 可以按不同層次結構的業務分組抽象出多個基礎類。

            Cat, Bird

            1. 繼承 – 構造函數綁定

            使用call或apply方法,將父對象的構造函數綁定在子對象上。

          function Animal() {     this.species = 'animal';     this.sleep = function() { console.log('I\'m sleep at night'); }; } function Cat(name, color) {     this.name = name;     this.color = color; } 

            讓`Cat` 繼承 `Animal` 的特性:

          /** @class Cat */ function Cat(name, color) {     Animal.apply(this);     this.name = name;     this.color = color; } var cat1 = new Cat('Kitty', 'white'); cat1.sleep(); // I am sleep at night 

            2. 繼承 – 原型鏈繼承

            如果”貓”的prototype對象,指向一個Animal的實例,那么所有”貓”的實例,就能繼承Animal了。

          /** @class Cat */ function Cat(name, color) {     this.name = name;     this.color = color; } Cat.prototype = new Animal; Cat.prototype.eta = function() { console.log('fish is my delicious'); }; 

            它相當于完全刪除了prototype 對象原先的值,然后賦予一個新值

          // 任何一個prototype對象都有一個constructor屬性,指向它的構造函數 Cat.prototype.constructor = Cat; // fix prototype chains var cat = new Cat('Kitty', 'fish'); cat.eat();      // fish is my delicious cat.sleep();    // I'm sleep at night' console.log(cat instanceof Cat);    // TRUE console.log(cat instanceof Animal); // TRUE 

            需要創建父類實列來實現 `prototype` 繼承

            3. 繼承 (Inheritance) – 利用空對象作為中介實現原型繼承

          var F = function() {}; F.prototype = Animal.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat; 

            我們將上面的方法,封裝成一個函數,便于使用。

          function extend(ctor, superctor, px) {     if (!superctor || !ctor) throw Error('extend failed, verify dependencies');     var F = function() {};     F.prototype = superctor.prototype;     ctor.prototype = new F();     ctor.prototype.constructor = ctor;     ctor.superclass = superctor.prototype; // cache super class proto reference.     if (px) { // extend class implements         for (var k in px) {             if (px.hasOwnProperty(k)) ctor.prototype[k] = px[k];         }     }     return ctor; } 

            4 繼承 – 借住工具方法實現繼承

          /** @class Mammal */ extend(Cat, Animal, {     eat: function() {         Cat.superclass.eat.call(this); // call super method         console.log('Also i like some ofther food, such as beef and more.');     } }); var cat = new Cat('Smokey', 'fish'); cat.sleep(); cat.eat(); console.log(cat instanceof Animal); console.log(cat instanceof Cat); 

            之三、多態

            1. 多態 – 通過重寫原型方法來實現方法重名調用

          /** @class Cat */ extend(Cat, Animal, {     eat: function() {         Cat.superclass.eat.call(this); // call super method         console.log('Also i like some ofther food, such as beef and more.');     } }); 

            2. 多態 (Polymorphism) – 原型繼承 `prototype` 鏈上的方法、屬性查找

            【四】總結 Summary

            Constructor

            Prototype

            Inheritance

          posted @ 2013-01-16 12:52 楊軍威 閱讀(347) | 評論 (0)編輯 收藏

          僅列出標題
          共43頁: First 上一頁 32 33 34 35 36 37 38 39 40 下一頁 Last 

          導航

          統計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 六枝特区| 江城| 古蔺县| 陇川县| 五大连池市| 洪泽县| 花莲市| 龙门县| 甘孜县| 闽侯县| 罗江县| 江陵县| 仪陇县| 镇坪县| 佛山市| 育儿| 无为县| 聊城市| 合川市| 博客| 天津市| 虹口区| 海原县| 浙江省| 柳林县| 湘西| 灵石县| 双柏县| 松溪县| 称多县| 七台河市| 酒泉市| 镶黄旗| 东莞市| 辛集市| 景德镇市| 株洲市| 绥江县| 石屏县| 晋州市| 城步|