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

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

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

    2009年2月22日

    “又是一年畢業時 ”,看到一批批學子離開人生的象牙塔,走上各自的工作崗位;想想自己也曾經意氣風發、躊躇滿志,不覺感嘆萬千……本文是自己工作6年的經歷沉淀或者經驗提煉,希望對所有的軟件工程師們有所幫助,早日實現自己的人生目標。本文主要是關于軟件開發人員如何提高自己的軟件專業技術方面的具體建議,前面幾點旨在確定大的方向,算是廢話吧。
    '\ i'E*o5?5g6]#g(y)n8L1、分享第一條經驗:“學歷代表過去、能力代表現在、學習力代表未來。”其實這是一個來自國外教育領域的一個研究結果。相信工作過幾年、十幾年的朋友對這個道理有些體會吧。但我相信這一點也很重要:“重要的道理明白太晚將抱憾終生!”所以放在每一條,讓剛剛畢業的朋友們早點看到哈! TechWeb-技術社區7N9{'t%a+M
    2、一定要確定自己的發展方向,并為此目的制定可行的計劃。不要說什么,“我剛畢業,還不知道將來可能做什么?”,“跟著感覺走,先做做看”。因為,這樣的觀點會通過你的潛意識去暗示你的行為無所事事、碌碌無為。一直做技術,將來成為專家級人物?向管理方向走,成為職業經理人?先熟悉行業和領域,將來自立門戶?還是先在行業里面混混,過幾年轉行做點別的?這很重要,它將決定你近幾年、十年內“做什么事情才是在做正確的事情!”。 8R(X1}1g R/D$Z*K$y5U
    3、軟件開發團隊中,技術不是萬能的,但沒有技術是萬萬不能的!在技術型團隊中,技術與人品同等重要,當然長相也比較重要哈,尤其在MM比較多的團隊中。在軟件項目團隊中,技術水平是受人重視和尊重的重要砝碼。無論你是做管理、系統分析、設計、編碼,還是產品管理、測試、文檔、實施、維護,多少你都要有技術基礎。算我孤陋寡聞,我還真沒有親眼看到過一個外行帶領一個軟件開發團隊成功地完成過軟件開發項目,哪怕就一個,也沒有看到。倒是曾經看到過一個“高學歷的牛人”(非技術型)帶一堆人做完過一個項目,項目交付的第二天,項目組成員扔下一句“再也受不了啦!”四分五裂、各奔東西。那個項目的“成功度”大家可想而知了。 tech.techweb.com.cn&F,q9R;K/u.@
    4、詳細制定自己軟件開發專業知識學習計劃,并注意及時修正和調整(軟件開發技術變化實在太快)。請牢記:“如果一個軟件開發人員在1、2年內都沒有更新過自己的知識,那么,其實他已經不再屬于這個行業了。”不要告訴自己沒有時間。來自時間管理領域的著名的“三八原則”告誡我們:另外的那8小時如何使用將決定你的人生成敗!本人自畢業以來,平均每天實際學習時間超過2小時。 +j&G1B p7U/? g
    5、書籍是人類進步的階梯,對軟件開發人員尤其如此。書籍是學習知識的最有效途徑,不要過多地指望在工作中能遇到“世外高人”,并不厭其煩地教你。對于花錢買書,我個人經驗是:千萬別買國內那幫人出的書!我買的那些家伙出的書,100%全部后悔了,無一本例外。更氣憤的是,這些書在二手市場的地攤上都很難賣掉。“擁有書籍并不表示擁有知識;擁有知識并不表示擁有技能;擁有技能并不表示擁有文化;擁有文化并不表示擁有智慧。”只有將書本變成的自己智慧,才算是真正擁有了它。
    ,U5p1B&q9s4u程序開發,操作系統,服務器,源碼下載,Linux,Unix,BSD,PHP,Apach,asp,下載,源碼,黑客,安全,技術社區,技術論壇6、不要僅局限于對某項技術的表面使用上,哪怕你只是偶爾用一、二次。“對任何事物不究就里”是任何行業的工程師所不應該具備的素質。開發Windows應用程序,看看Windows程序的設計、加載、執行原理,分析一下PE文件格式,試試用SDK開發從頭開發一個Windows應用程序;用VC++、 Delphi、Java、.Net開發應用程序,花時間去研究一下MFC、VCL、J2EE、.Net它們框架設計或者源碼;除了會用J2EE、 JBoss、Spring、Hibernate, http://www.bt285.cn 等等優秀的開源產品或者框架,抽空看看大師們是如何抽象、分析、設計和實現那些類似問題的通用解決方案的。試著這樣做做,你以后的工作將會少遇到一些讓你不明就里、一頭霧水的問題,因為,很多東西你“知其然且知其所以然”!
    $Z.F7B.I2u1X7、在一種語言上編程,但別為其束縛了思想。“代碼大全”中說:“深入一門語言編程,不要浮于表面”。深入一門語言開發還遠遠不足,任何編程語言的存在都有其自身的理由,所以也沒有哪門語言是“包治百病”的“靈丹妙藥”。編程語言對開發人員解決具體問題的思路和方式的影響與束縛的例子俯拾皆是。我的經驗是:用面對對象工具開發某些關鍵模塊時,為什么不可以借鑒C、C51、匯編的模塊化封裝方式?用傳統的桌面開發工具(目前主要有VC++、Delphi)進行系統體統結構設計時,為什么不可以參考來自Java社區的IoC、AOP設計思想,甚至借鑒像Spring、Hibernate、JBoss等等優秀的開源框架?在進行類似于實時通信、數據采集等功能的設計、實現時,為什么不可以引用來自實時系統、嵌入式系統的優秀的體系框架與模式?為什么一切都必須以個人、團隊在當然開發語言上的傳統或者經驗來解決問題???“他山之石、可以攻玉”。 程序開發,操作系統,服務器,源碼下載,Linux,Unix,BSD,PHP,Apach,asp,下載,源碼,黑客,安全,技術社區,技術論壇:q8~7X.v'p&B/S
    8、養成總結與反思的習慣,并有意識地提煉日常工作成果,形成自己的個人源碼庫、解決某類問題的通用系統體系結構、甚至進化為框架。眾所周知,對軟件開發人員而言,有、無經驗的一個顯著區別是:無經驗者完成任何任務時都從頭開始,而有經驗者往往通過重組自己的可復用模塊、類庫來解決問題(其實這個結論不應該被局限在軟件開發領域、可以延伸到很多方面)。這并不是說,所有可復用的東西都必須自己實現,別人成熟的通過測試的成果也可以收集、整理、集成到自己的知識庫中。但是,最好還是自己實現,這樣沒有知識產權、版權等問題,關鍵是自己實現后能真正掌握這個知識點,擁有這個技能。
    9M2s*m+V.W"?*[*W3v)l+O9、理論與實踐并重,內外雙修。工程師的內涵是:以工程師的眼光觀察、分析事物和世界。一個合格的軟件工程師,是真正理解了軟件產品的本質及軟件產品研發的思想精髓的人(個人觀點、歡迎探討)。掌握軟件開發語言、應用語言工具解決工作中的具體問題、完成目標任務是軟件工程師的主要工作,但從軟件工程師這個角度來看,這只是外在的東西,并非重要的、本質的工作。學習、掌握軟件產品開發理論知識、軟件開發方法論,并在實踐中理解、應用軟件產品的分析、設計、實現思想來解決具體的軟件產品研發問題,才是真正的軟件工程師的工作。站在成熟理論與可靠方法論的高度思考、分析、解決問題,并在具體實踐中驗證和修正這些思想與方式,最終形成自己的理論體系和實用方法論。
    6l)d+H,S7~(B+`10、心態有多開放,視野就有多開闊。不要抱著自己的技術和成果,等到它們都已經過時變成垃圾了,才拿出來丟人現眼。請及時發布自己的研究成果:開發的產品、有創意的設計或代碼,公布出來讓大家交流或者使用,你的成果才有進化和升華的機會。想想自己2000年間開發的那些Windows系統工具,5、6年之后的今天,還是那個樣子,今天流行的好多Windows系統工具都比自己的晚,但進化得很好,且有那么多用戶在使用。并且,不要保守自己的技術和思想,盡可能地與人交流與分享,或者傳授給開發團隊的成員。“與人交換蘋果之后,每個人還是只有一個蘋果;但交換思想之后,每個人都擁有兩種思想”,道理大家都懂,但有多少人真正能做到呢? :n4K*z,@0l#t$?&]"r
    11、盡量參加開源項目的開發、或者與朋友共同研制一些自己的產品,千萬不要因為沒有錢賺而不做。網絡早已不再只是“虛擬世界”,網上有很多的開源項目、合作開發項目、外包項目,這都是涉獵工作以外的知識的絕好機會,并且能夠結識更廣的人緣。不要因為工作是做ERP,就不去學習和了解嵌入式、實時、通信、網絡等方面的技術,反過來也是一樣。如果當別人拿著合同找你合作,你卻這也不會,那也不熟時,你將后悔莫及。
    &_8[,U&x'N4{12、書到用時方恨少,不要將自己的知識面僅僅局限于技術方面。諾貝爾經濟學獎得主西蒙教授的研究結果表明: “對于一個有一定基礎的人來說,他只要真正肯下功夫,在6個月內就可以掌握任何一門學問。”教育心理學界為感謝西蒙教授的研究成果,故命名為西蒙學習法。可見,掌握一門陌生的學問遠遠沒有想象的那么高難、深奧。多方吸取、廣泛涉獵。極力夯實自己的影響圈、盡量擴大自己的關注圈。財務、經濟、稅務、管理等等知識,有空花時間看看,韜光養晦、未雨綢繆。
    3z&W3]3c1s3h13、本文的總結與反思: 8w0b/e(y:E4y3n3Q3|"|+{-k
    A:不要去做技術上的高手,除非你的目標如此。雖然本文是關于提高軟件開發知識的建議,做技術的高手是我一向都不贊同的。你可以提高自己的專業知識,但能勝任工作即止。
    4w#v8I5?(f,q-A程序開發,操作系統,服務器,源碼下載,Linux,Unix,BSD,PHP,Apach,asp,下載,源碼,黑客,安全,技術社區,技術論壇B:提高軟件知識和技術只是問題的表面,本質是要提高自己認識問題、分析問題、解決問題的思想高度。軟件專業知識的很多方法和原理,可以很容易地延伸、應用到生活的其它方面。 TechWeb-技術社區:H:e7P"C/V,v-g
    C:在能勝任工作的基礎上,立即去涉獵其它領域的專業知識,豐富自己的知識體系、提高自己的綜合素質,尤其是那些目標不在技術方面的朋友。
    posted @ 2009-04-05 15:53 wang9354| 編輯 收藏

    我們通常在開發web應用過程中,展現層Action的單元測試經常被我們忽視了,主要原因是:

    1、Action層的業務邏輯比較簡單。大家潛意識認為這一部分的代碼不重要。

    2、Action層難以模擬http請求傳遞參數,需要依賴web容器,因此給單元測試編寫帶來一定的難度。

    我寫了一個簡單的Action單元測試用例,供大家參考。基于struts的mock和webwork的ActionProxyFactory都可以進行Action的單元測試。我個人比較傾向與ActionProxyFactory做單元測試。其實寫action單元測試非常簡單,大致分為三步就可以完成單元測試:

    一、設置ActionContext上下文參數

    將表單傳遞的請求參數添加到map中

    二、創建Action動態代理對象

    通過public abstract ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) throws Exception 創建action代理對象。

    三、junit斷言執行結果

    assertEquals(testAction.login(),”success”)

    詳細用例參考:

    public class TestActionTest extends BaseCaseTest{

    private ActionProxy proxy = null;

    private IVoucherService voucherService;

    @Before

    public void setUp() throws Exception {

    IMocksControl control = EasyMock.createControl();

    voucherService = control.createMock(IVoucherService.class);

    Map<String, Object> params = new HashMap<String, Object>();

    params.put(”loginId”,”test”);

    params.put(”password”,”111111″);

    params.put(”website”,” http://www.bt285.cn  ″);

    params.put(”name”,”小說″);

    params.put(”voucherService”, voucherService);

    Map extraContext = new HashMap();

    extraContext.put(ActionContext.PARAMETERS,params);

    try {

    proxy = ActionProxyFactory.getFactory().createActionProxy(”/ http://www.5a520.cn user”, “testAction”, extraContext);

    proxy.setExecuteResult(false);

    assertEquals(proxy.execute(),”success”);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    @Test

    public void testLogin() {

     

    TestAction testAction = (TestAction) proxy.getAction();

    assertEquals(testAction.login(),”success”);

    }

    }

    注:創建代理action一定要執行proxy.execute()方法,否則參數不能夠增加到actionContext上下文中。因為proxy.execute()中會執行 invocation.invoke()核心方法,遍歷執行action中所有的攔截器,包括其中的參數攔截器

    posted @ 2009-03-21 23:24 wang9354| 編輯 收藏
     

    某天在服務器上的網頁打不開了,頻繁報以下錯誤。

    2007-3-18 1:08:26 org.apache.tomcat.util.threads.ThreadPool logFull
    嚴重: All threads (150) are currently busy, waiting. Increase maxThreads (150) or check the servlet status

    在網上找了些回答,以下是我覺得正確的回答:
    1.我想你的部分資源沒有釋放,積壓卡死的
    2.連接池問題
    3.應該是服務器端響應request的線程的處理時間過長導致的

    分析:
    當時使用網站的人數只有2個人,不可能答到到了并發線程150的上線。所以應該不是數據庫的問題。
    通過對出錯的提示判斷,應該是連接池使用不合理造成的,或者根本沒設置連接池。和數據庫連接的部分是使用Spring的數據源JDBC連的,如下:
    <beans>
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <!-- driver for MySQL-->
            <property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value></property>
            <property name="url"><value>jdbc:mysql:// www.bt285.cn :3306/test?useUnicode=true&amp;characterEncoding=UTF8</value></property>
            <property name="username"><value>test</value></property>
            <property name="password"><value>test</value></property>      
    </beans>

    問題應該出現在Spring的DriverManagerDataSource上,它負責管理這些連接的。
    下邊是對DriverManagerDataSource 的解釋
    DriverManagerDataSource in Spring Framework

       javax.sql Interface DataSource

    Implementation of SmartDataSource that configures a plain old JDBC Driver via
    bean properties, and returns a new Connection every time.

    Useful for test or standalone environments outside of a J2EE container, either
    as a DataSource bean in a respective ApplicationContext, or in conjunction with
    a simple JNDI environment. Pool-assuming Connection.close() calls will simply
    close the connection, so any DataSource-aware persistence code should work.

    In a J2EE container, it is recommended to use a JNDI DataSource provided by the
    container. Such a DataSource can be exported as a DataSource bean in an
    ApplicationContext via JndiObjectFactoryBean, for seamless switching to and from
    a local DataSource bean like this class.

    If you need a "real" connection pool outside of a J2EE container, consider
    Apache's Jakarta Commons DBCP. Its BasicDataSource is a full connection pool
    bean, supporting the same basic properties as this class plus specific settings.
    It can be used as a replacement for an instance of this class just by changing
    the class name of the bean definition to
    "org.apache.commons.dbcp.BasicDataSource".

    -----------------------------------------------
    Many Jakarta projects support interaction with a relational database. Creating a
    new connection for each user can be time consuming (often requiring multiple
    seconds of clock time), in order to perform a database transaction that might
    take milliseconds. Opening a connection per user can be unfeasible in a
    publicly-hosted Internet application where the number of simultaneous users can
    be very large. Accordingly, developers often wish to share a "pool" of open
    connections between all of the application's current users. The number of users
    actually performing a request at any given time is usually a very small
    percentage of the total number of active users, and during request processing is
    the only time that a database connection is required. The application itself
    logs into the DBMS, and handles any user account issues internally.

    There are several Database Connection Pools already available, both within
    Jakarta products and elsewhere. This Commons package provides an opportunity to
    coordinate the efforts required to create and maintain an efficient,
    feature-rich package under the ASF license.

    The commons-dbcp package relies on code in the commons-pool package to provide
    the underlying object pool mechanisms that it utilizes.

    Applications can use the commons-dbcp component directly or through the existing
    interface of their container / supporting framework. For example the Tomcat
    servlet container presents a DBCP DataSource as a JNDI Datasource. James (Java
    Apache Mail Enterprise Server) has integrated DBCP into the Avalon framework. A
    Avalon-style datasource is created by wrapping the DBCP implementation. The
    pooling logic of DBCP and the configuration found in Avalon's excalibur code is
    what was needed to create an integrated reliable DataSource.

    看完了解釋,事實上是因為DriverManagerDataSource建立連接是只要有連接就新建一個connection,根本沒有連接池的作用。改為以下開源的連接池會好點。
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName">
    <value>org.hsqldb.jdbcDriver</value>
    </property>
    <property name="url">
    <value>jdbc:hsqldb:hsql:// www.5a520.cn :9001</value>
    </property>
    <property name="username">
    <value>sa</value>
    </property>
    <property name="password">
    <value></value>
    </property>
    </bean>

    測試通過,問題消除,如果沒有搜索引擎找答案不會這么快解決問題。

    posted @ 2009-03-19 13:03 wang9354| 編輯 收藏

    Nexus介紹

    Nexus 是Maven倉庫管理器,如果你使用Maven,你可以從Maven中央倉庫 下載所需要的構件(artifact),但這通常不是一個好的做法,你應該在本地架設一個Maven倉庫服務器,在代理遠程倉庫的同時維護本地倉庫,以節省帶寬和時間,Nexus就可以滿足這樣的需要。此外,他還提供了強大的倉庫管理功能,構件搜索功能,它基于REST,友好的UI是一個extjs的REST客戶端,它占用較少的內存,基于簡單文件系統而非數據庫。這些優點使其日趨成為最流行的Maven倉庫管理器。

     

    下載和安裝

    你可以從http://nexus.sonatype.org/downloads/  或是http://www.5a520.cn 下載最新版本的Nexus,筆者使用的是1.3.0版本。

    Nexus提供了兩種安裝方式,一種是內嵌Jetty的bundle,只要你有JRE就能直接運行。第二種方式是WAR,你只須簡單的將其發布到web容器中即可使用。

     

    Bundle方式安裝

    解壓nexus-webapp-1.3.0-bundle.zip 至任意目錄,如D:\dev_tools ,然后打開CMD,cd至目錄D:\dev_tools\nexus-webapp-1.3.0\bin\jsw\windows-x86-32 ,運行Nexus.bat 。你會看到Nexus的啟動日志,當你看到“Started SelectChannelConnector@0.0.0.0:8081”之后,說明Nexus啟動成功了,然后打開瀏覽器,訪問http://127.0.0.1:8081/nexus,你會看到如下的頁面:

    要停止Nexus,Ctrl+C即可,此外InstallNexus.bat可以用來將Nexus安裝成一個windows服務,其余的腳本則對應了啟動,停止,暫停,恢復,卸載Nexus服務。

     

    WAR方式安裝

    你需要有一個能運行的web容器,這里以Tomcat為例,加入Tomcat的安裝目錄位于D:\dev_tools\apache-tomcat-6.0.18 ,首先我們將下載的nexus-webapp-1.3.0.war 重命名為nexus.war ,然后復制到D:\dev_tools\apache-tomcat-6.0.18\webapps\nexus.war ,然后啟動CMD,cd到D:\dev_tools\apache-tomcat-6.0.18\bin\ 目錄,運行startup.bat 。一切OK,現在可以打開瀏覽器訪問http://www.bt285.cn :8080/nexus,你會得到和上圖一樣的界面。

     

    代理外部Maven倉庫

    登陸

    要管理Nexus,你首先需要以管理員身份登陸,點擊界面右上角的login,輸入默認的登錄名和密碼:admin/admin123,登陸成功后,你會看到左邊的導航欄增加了很多內容:

    這里,可以管理倉庫,配置Nexus系統,管理任務,管理用戶,角色,權限,查看系統的RSS源,管理及查看系統日志,等等。你會看到Nexus的功能十分豐富和強大,本文,筆者只介紹一些最基本的管理和操作。

     

    代理Maven中央倉庫

    點擊左邊導航欄的Repositories,界面的主面板會顯示所有一個所有倉庫及倉庫組的列表,你會看到它們的Type字段的值有group,hosted,proxy,virtual。這里我們不關心virtual,只介紹下另外三種類型:

    • hosted,本地倉庫,通常我們會部署自己的構件到這一類型的倉庫。
    • proxy,代理倉庫,它們被用來代理遠程的公共倉庫,如maven中央倉庫。
    • group,倉庫組,用來合并多個hosted/proxy倉庫,通常我們配置maven依賴倉庫組。

    由此我們知道,我們需要配置一個Maven中央倉庫的proxy,其實Nexus已經內置了Maven Central,但我們需要做一些配置。點擊倉庫列表中的Maven Central,你會注意到它的Policy是release,這說明它不會代理遠程倉庫的snapshot構件,這是有原因的,遠程倉庫的snapshot版本構件不穩定且不受你控制,使用這樣的構件含有潛在的風險。然后我們發現主面板下方有三個Tab,分別為Browse,Configuration和Mirrors,我們點擊Configuration進行配置,你現在需要關心的是兩個配置項:“Remote Storage Location”為遠程倉庫的地址,對于Maven Central來說是http://repo1.maven.org/maven2/;“Download Remote Indexes”顧名思義是指是否下載遠程索引文件,Maven Central的該字段默認為False,這是為了防止大量Nexus無意識的去消耗中央倉庫的帶寬(中央倉庫有大量的構件,其索引文件也很大)。這里我們需要將其設置為True,然后點擊Save。在Nexus下載的中央倉庫索引文件之后,我們就可以在本地搜索中央倉庫的所有構件。下圖展示了我們剛才所涉及的配置:

     

    添加一個代理倉庫

    這里我們再舉一個例子,我們想要代理Sonatype的公共倉庫,其地址為:http://repository.sonatype.org/content/groups/public/。步驟如下,在Repositories面板的上方,點擊Add,然后選擇Proxy Repository,在下方的配置部分,我們填寫如下的信息:Repository ID - sonatype;Repository Name - Sonatype Repository;Remote Storage Location - http://repository.sonatype.org/content/groups/public/。其余的保持默認值,需要注意的是Repository Policy,我們不想代理snapshot構件,原因前面已經描述。然后點擊Save。配置頁面如下:

     

    管理本地Maven倉庫

    Nexus預定義了3個本地倉庫,分別為Releases,Snapshots,和3rd Party。這三個倉庫都有各自明確的目的。Releases用于部署我們自己的release構件,Snapshots用于部署我們自己的snapshot構件,而3rd Party用于部署第三方構件,有些構件如Oracle的JDBC驅動,我們不能從公共倉庫下載到,我們就需要將其部署到自己的倉庫中。

    當然你也可以創建自己的本地倉庫,步驟和創建代理倉庫類似,點擊Repository面板上方的Add按鈕,然后選擇Hosted Repository,然后在下方的配置面板中輸入id和name,注意這里我們不再需要填寫遠程倉庫地址,Repository Type則為不可修改的hosted,而關于Repository Policy,你可以根據自己的需要選擇Release或者Snapshot,如圖:

     

    管理Maven倉庫組

    Nexus中倉庫組的概念是Maven沒有的,在Maven看來,不管你是hosted也好,proxy也好,或者group也好,對我都是一樣的,我只管根據groupId,artifactId,version等信息向你要構件。為了方便Maven的配置,Nexus能夠將多個倉庫,hosted或者proxy合并成一個group,這樣,Maven只需要依賴于一個group,便能使用所有該group包含的倉庫的內容。

    Nexus預定義了“Public Repositories”和“Public Snapshot Repositories”兩個倉庫組,前者默認合并所有預定義的Release倉庫,后者默認合并所有預定義的Snapshot倉庫。我們在本文前面的部分創建了一個名為“Sonatype Repository”的倉庫,現在將其合并到“Public Repositories”中。

    點擊倉庫列表中的“Public Repositories”,然后選擇下方的"Configuration" Tab,在配置面板中,將右邊“Avaiable Repositories”中的“Sonatype Repository”拖拽到左邊的“Ordered Group Repository”中,如圖:

    創建倉庫組和創建proxy及hosted倉庫類似,這里不再贅述。需要注意的是format字段需要填寫“maven2”,添加你感興趣的倉庫即可。

     

    搜索構件

    在浩大的Maven倉庫中一下下點擊鏈接,瀏覽路徑以尋找感興趣的構件是一件很郁悶的事情。Nexus基于nexus-indexer提供構件搜索功能,要想對倉庫進行搜索,無論是hosted,proxy,或者group,你都必須確認索引文件存在。這一點對于代理倉庫尤其重要,有些遠程倉庫可能根本就沒有索引,所以你無法搜索這些代理倉庫。有些遠程倉庫的遠程索引非常大,如中央倉庫達到了70M左右,那么第一次下載索引需要花很多時間,所以要期望得到搜索結果,確保看到如下的文件:

    一旦你的Nexus擁有了本地或者遠程倉庫的索引文件,你就可以享受Nexus的構件搜索功能了。不論登陸與否,你都可以使用關鍵字進行模糊搜索,比如我在左邊導航欄上部的搜索框內輸入junit,然后點擊搜索按鈕,右邊立刻會分頁顯示500多條的junit相關構件信息。如果你了解更多的信息,你也可以通過限定groupId,artifactId,version進行搜索,點擊導航欄中的“Advanced Search”,點擊右邊所有頁面左上角的下拉框,選擇“GAV Search”。筆者這里輸入junit:junit:4.4,然后回車:

    選擇一項搜索結果,在頁面下方會顯示“Artifact Information”的面板,你可以點擊"artifact"或者"pom"下載對應文件,而該面板右邊更顯示了一個Maven依賴配置,你可以直接復制該配置到Maven POM中,這是個十分方便的特性。

    此外,值得一提的是,Nexus還支持基于classname的搜索,你只需點擊搜索頁面右上角的下拉框,選擇“Classname Search”,然后輸入類名即可,這里我不再贅述。

     

    配置Maven使用Nexus

    默認情況下,Maven依賴于中央倉庫,這是為了能讓Maven開箱即用,但僅僅這么做明顯是錯誤的,這會造成大量的時間及帶寬的浪費。既然文章的前面已經介紹了如何安裝和配置Nexus,現在我們就要配置Maven來使用本地的Nexus,以節省時間和帶寬資源。

    我們可以將Repository配置到POM中,但一般來說這不是很好的做法,原因很簡單,你需要為所有的Maven項目重復該配置。因此,這里我將Repository的配置放到$user_home/.m2/settings.xml中:

    Xml代碼 復制代碼
    1. <settings>  
    2. ...   
    3. <profiles>  
    4.   <profile>  
    5.     <id>dev</id>  
    6.     <repositories>  
    7.       <repository>  
    8.         <id>local-nexus</id>  
    9.         <url>http://127.0.0.1:8080/nexus/content/groups/public/</url>  
    10.         <releases>  
    11.           <enabled>true</enabled>  
    12.         </releases>  
    13.         <snapshots>  
    14.           <enabled>true</enabled>  
    15.         </snapshots>  
    16.       </repository>  
    17.     </repositories>  
    18.   </profile>  
    19. </profiles>  
    20. <activeProfiles>  
    21.   <activeProfile>dev</activeProfile>  
    22. </activeProfiles>  
    23. ...   
    24. </settings>  

    由于我們不能直接在settings.xml中插入<repositories>元素,這里我們編寫了一個profile,并添加了一個profile并使用<activeProfile>元素自動將這個profile激活。這里的local-nexus倉庫指向了剛才我們配置的Nexus中“Public Repositories”倉庫組,也就是說,所有該倉庫組包含的倉庫都能供我們使用。此外,我們通過<releases>和<snapshots>元素激活了Maven對于倉庫所有類型構件下載的支持,當然你也可以調節該配置,比如說禁止Maven從Nexus下載snapshot構件。

    使用該配置,Maven就會從你的Nexus服務器下載構件了,速度和從Central下載可不是一個數量級的。

     

    部署構件至Nexus

    Nexus提供了兩種方式來部署構件,你可以從UI直接上傳,也可以配置Maven部署構件。

     

    通過Nexus UI部署

    有時候有個jar文件你無法從公共Maven倉庫找到,但是你能從其它得到這個jar文件(甚至是POM),那么你完全可以將這個文件部署到Nexus中,使其成為標準流程的一部分。步驟如下:

    點擊左邊導航欄的"Repository",在右邊的倉庫列表中選擇一個倉庫,如“3rd Party”,然后會看到頁面下方有四個tab,選擇最后一個“Upload”,你會看到構件上傳界面。選擇你要上傳的構件,并指定POM,(或者手工編寫GAV等信息),最后點擊Upload,該構件就直接被部署到了Nexus的"3rd Party"倉庫中。如圖:


    通過Maven部署

    更常見的用例是:團隊在開發一個項目的各個模塊,為了讓自己開發的模塊能夠快速讓其他人使用,你會想要將snapshot版本的構件部署到Maven倉庫中,其他人只需要在POM添加一個對于你開發模塊的依賴,就能隨時拿到最新的snapshot。

    以下的pom.xml配置和settings.xml能讓你通過Maven自動化部署構件:

    pom.xml

    Xml代碼 復制代碼
    1. <project>  
    2. ...   
    3. <distributionManagement>  
    4.   <repository>  
    5.     <id>nexus-releases</id>  
    6.       <name>Nexus Release Repository</name>  
    7.       <url>http://127.0.0.1:8080/nexus/content/repositories/releases/</url>  
    8.   </repository>  
    9.   <snapshotRepository>  
    10.     <id>nexus-snapshots</id>  
    11.     <name>Nexus Snapshot Repository</name>  
    12.     <url>http://127.0.0.1:8080/nexus/content/repositories/snapshots/</url>  
    13.   </snapshotRepository>  
    14. </distributionManagement>  
    15. ...   
    16. </project>  

    settings.xml

    Xml代碼 復制代碼
    1. <settings>  
    2. ...   
    3. <servers>  
    4.   <server>  
    5.     <id>nexus-releases</id>  
    6.     <username>admin</username>  
    7.     <password>admin123</password>  
    8.   </server>  
    9.   <server>  
    10.     <id>nexus-snapshots</id>  
    11.     <username>admin</username>  
    12.     <password>admin123</password>  
    13.   </server>     
    14. </servers>  
    15. ...   
    16. </settings>  

    這里我們配置所有的snapshot版本構件部署到Nexus的Snapshots倉庫中, 所有的release構件部署到Nexus的Releases倉庫中。由于部署需要登陸,因為我們在settings.xml中配置對應Repository id的用戶名和密碼。

    然后,在項目目錄中執行mvn deploy ,你會看到maven將項目構件部署到Nexus中,瀏覽Nexus對應的倉庫,就可以看到剛才部署的構件。當其他人構建其項目時,Maven就會從Nexus尋找依賴并下載。

     

    總結

    本文介紹強大的倉庫管理器——Nexus,包括如何下載安裝Nexus,配置Nexus代理中央倉庫,管理Nexus的代理倉庫,本地倉庫,以及倉庫組。并幫助你了解如何通過Nexus搜索構件。最后,如何在Maven中配置Nexus倉庫,以及如何部署構件到Nexus倉庫中。這些都是Nexus中最基本也是最常用的功能。隨著使用的深入,你會發現Nexus還有很多其它的特性,如用戶管理,角色權限管理等等。

    Nexus的OSS版本是完全開源的,如果你有興趣,你可以學習其源碼,甚至自己實現一個REST客戶端。

    馬上擁抱Nexus吧,它是免費的!

    posted @ 2009-03-16 18:51 wang9354| 編輯 收藏

    對于一個能夠訪問源代碼的經驗豐富的Java開發人員來說,任何程序都可以被看作是博物館里透明的模型。類似線程轉儲(dump)、方法調用跟蹤、斷點、切面(profiling)統計表等工具可以讓我們了解程序目前正在執行什么操作、剛才做了什么操作、未來將做什么操作。但是在產品環境中情況就沒有那么明顯了,這些工具一般是不能夠使用的,或最多只能由受過訓練的開發者使用。支持團隊和最終用戶也需要知道在某個時刻應用程序正在執行什么操作。

      為了填補這個空缺,我們已經發明了一些簡單的替代品,例如日志文件(典型情況下用于服務器處理)和狀態條(用于GUI應用程序)。但是,由于這些工具只能捕捉和報告可用信息的一個很小的子集,并且通常必須把這些信息用容易理解的方式表現出來,所以程序員趨向于把它們明確地編寫到應用程序中。而這些代碼會纏繞著應用程序的業務邏輯,當開發者試圖調試或了解核心功能的時候,他們必須"圍繞這些代碼工作",而且還要記得功能發生改變后更新這些代碼。我們希望實現的真正功能是把狀態報告集中在某個位置,把單個狀態消息作為元數據(metadata)來管理。

      在本文中我將考慮使用嵌入GUI應用程序中的狀態條組件的情形。我將介紹多種實現這種狀態報告的不同方法,從傳統的硬編碼習慣開始。隨后我會介紹Java 1.5的大量新特性,包括注解(annotation)和運行時字節碼重構(instrumentation)。
    狀態管理器(StatusManager)

      我的主要目標是建立一個可以嵌入GUI應用程序的JStatusBar Swing組件。圖1顯示了一個簡單的Jframe中狀態條的樣式。



     
    圖1.我們動態生成的狀態條


      由于我不希望直接在業務邏輯中引用任何GUI組件,我將建立一個StatusManager(狀態管理器)來充當狀態更新的入口點。實際的通知會被委托給StatusState對象,因此以后可以擴展它以支持多個并發的線程。圖2顯示了這種安排。



     
    圖2. StatusManager和JstatusBar


      現在我必須編寫代碼調用StatusManager的方法來報告應用程序的進程。典型情況下,這些方法調用都分散地貫穿于try-finally代碼塊中,通常每個方法一個調用。

    /*http://www.bt285.cn */

    public void connectToDB (String url) {
     StatusManager.push("Connecting to database");
     try {
      ...
     } finally {
      StatusManager.pop();
     }
    }


      這些代碼實現了我們所需要功能,但是在代碼庫中數十次、甚至于數百次地復制這些代碼之后,它看起來就有些混亂了。此外,如果我們希望用一些其它的方式訪問這些消息該怎么辦呢?在本文的后面部分中,我將定義一個用戶友好的異常處理程序,它共享了相同的消息。問題是我把狀態消息隱藏在方法的實現之中了,而沒有把消息放在消息所屬的接口中。

      面向屬性編程

      我真正想實現的操作是把對StatusManager的引用都放到代碼外面的某個地方,并簡單地用我們的消息標記這個方法。接著我可以使用代碼生成(code-generation)或運行時反省(introspection)來執行真正的工作。XDoclet項目把這種方法歸納為面向屬性編程(Attribute-Oriented Programming),它還提供了一個框架組件,可以把自定義的類似Javadoc的標記轉換到源代碼之中。

      但是,JSR-175包含了這樣的內容,Java 1.5為了包含真實代碼中的這些屬性提供了一種結構化程度更高的格式。這些屬性被稱為"注解(annotations)",我們可以使用它們為類、方法、字段或變量定義提供元數據。它們必須被顯式聲明,并提供一組可以包含任意常量值(包括原語、字符串、枚舉和類)的名稱-值對(name-value pair)。

      注解(Annotations)

      為了處理狀態消息,我希望定義一個包含字符串值的新注解。注解的定義非常類似接口的定義,但是它用@interface關鍵字代替了interface,并且只支持方法(盡管它們的功能更像字段):

    public @interface Status {
     String value();
    }


      與接口類似,我把@interface放入一個叫做Status.java的文件中,并把它導入到任何需要引用它的文件中。

      對我們的字段來說,value可能是個奇怪的名稱。類似message的名稱可能更適合;但是,value對于Java來說具有特殊的意義。它允許我們使用@Status("...")代替@Status(value="...")來定義注解,這明顯更加簡捷。

      我現在可以使用下面的代碼定義自己的方法:

    @Status("Connecting to database")
    public void connectToDB (String url) {
    ...
    }


      請注意,我們在編譯這段代碼的時候必須使用-source 1.5選項。如果你使用Ant而不是直接使用javac命令行建立應用程序,那么你需要使用Ant 1.6.1以上版本。

      作為類、方法、字段和變量的補充,注解也可以用于為其它的注解提供元數據。特別地,Java引入了少量注解,你可以使用這些注解來定制你自己的注解的工作方式。我們用下面的代碼重新定義自己的注解:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Status {
    String value();
    }


      @Target注解定義了@Status注解可以引用什么內容。理想情況下,我希望標記大塊的代碼,但是它的選項只有方法、字段、類、本地變量、參數和其它注解。我只對代碼感興趣,因此我選擇了METHOD(方法)。
     
      @Retention注解允許我們指定Java什么時候可以自主地拋棄消息。它可能是SOURCE(在編譯時拋棄)、CLASS(在類載入時拋棄)或RUNTIME(不拋棄)。我們先選擇SOURCE,但是在本文后部我們會更新它。

     

    重構源代碼

      現在我的消息都被編碼放入元數據中了,我必須編寫一些代碼來通知狀態監聽程序。假設在某個時候,我繼續把connectToDB方法保存源代碼控件中,但是卻沒有對StatusManager的任何引用。但是,在編譯這個類之前,我希望加入一些必要的調用。也就是說,我希望自動地插入try-finally語句和push/pop調用。

      XDoclet框架組件是一種Java源代碼生成引擎,它使用了類似上述的注解,但是把它們存儲在Java源代碼的注釋(comment)中。XDoclet生成整個Java類、配置文件或其它建立的部分的時候非常完美,但是它不支持對已有Java類的修改,而這限制了重構的有效性。作為代替,我可以使用分析工具(例如JavaCC或ANTLR,它提供了分析Java源代碼的語法基礎),但是這需要花費大量精力。

      看起來沒有什么可以用于Java代碼的源代碼重構的很好的工具。這類工具可能有市場,但是你在本文的后面部分可以看到,字節碼重構可能是一種更強大的技術。 重構字節碼

      不是重構源代碼然后編譯它,而是編譯原始的源代碼,然后重構它所產生的字節碼。這樣的操作可能比源代碼重構更容易,也可能更加復雜,而這依賴于需要的準確轉換。字節碼重構的主要優點是代碼可以在運行時被修改,不需要使用編譯器。

      盡管Java的字節碼格式相對簡單,我還是希望使用一個Java類庫來執行字節碼的分析和生成(這可以把我們與未來Java類文件格式的改變隔離開來)。我選擇了使用Jakarta的Byte Code Engineering Library(字節碼引擎類庫,BCEL),但是我還可以選用CGLIB、ASM或SERP。

      由于我將使用多種不同的方式重構字節碼,我將從聲明重構的通用接口開始。它類似于執行基于注解重構的簡單框架組件。這個框架組件基于注解,將支持類和方法的轉換,因此該接口有類似下面的定義:

    public interface Instrumentor
    {
     public void instrumentClass (ClassGen classGen,Annotation a);
     public void instrumentMethod (ClassGen classGen,MethodGen methodGen,Annotation a);
    }


      ClassGen和MethodGen都是BCEL類,它們使用了Builder模式(pattern)。也就是說,它們為改變其它不可變的(immutable)對象、以及可變的和不可變的表現(representation)之間的轉換提供了方法。

      現在我需要為接口編寫實現,它必須用恰當的StatusManager調用更換@Status注解。前面提到,我希望把這些調用包含在try-finally代碼塊中。請注意,要達到這個目標,我們所使用的注解必須用@Retention(RetentionPolicy.CLASS)進行標記,它指示Java編譯器在編譯過程中不要拋棄注解。由于在前面我把@Status聲明為@Retention(RetentionPolicy.SOURCE)的,我必須更新它。

      在這種情況下,重構字節碼明顯比重構源代碼更復雜。其原因在于try-finally是一種僅僅存在于源代碼中的概念。Java編譯器把try-finally代碼塊轉換為一系列的try-catch代碼塊,并在每一個返回之前插入對finally代碼塊的調用。因此,為了把try-finally代碼塊添加到已有的字節碼中,我也必須執行類似的事務。

      下面是表現一個普通方法調用的字節碼,它被StatusManager更新環繞著:

    0: ldc #2; //字符串消息
    2: invokestatic #3; //方法StatusManager.push:(LString;)V
    5: invokestatic #4; //方法 doSomething:()V
    8: invokestatic #5; //方法 StatusManager.pop:()V
    11: return


      下面是相同的方法調用,但是位于try-finally代碼塊中,因此,如果它產生了異常會調用StatusManager.pop():

    0: ldc #2; //字符串消息
    2: invokestatic #3; //方法 StatusManager.push:(LString;)V
    5: invokestatic #4; //方法 doSomething:()V
    8: invokestatic #5; //方法 StatusManager.pop:()V
    11: goto 20
    14: astore_0
    15: invokestatic #5; //方法 StatusManager.pop:()V
    18: aload_0
    19: athrow
    20: return

    Exception table:
    from to target type
    5 8 14 any
    14 15 14 any


      你可以發現,為了實現一個try-finally,我必須復制一些指令,并添加了幾個跳轉和異常表記錄。幸運的是,BCEL的InstructionList類使這種工作相當簡單。

      在運行時重構字節碼

      現在我擁有了一個基于注解修改類的接口和該接口的具體實現了,下一步是編寫調用它的實際框架組件。實際上我將編寫少量的框架組件,先從運行時重構所有類的框架組件開始。由于這種操作會在build過程中發生,我決定為它定義一個Ant事務。build.xml文件中的重構目標的聲明應該如下:

    <instrument class="com.pkg.OurInstrumentor">
    <fileset dir="$(classes.dir)">
    <include name="**/*.class"/>
    </fileset>
    </instrument>


      為了實現這種事務,我必須定義一個實現org.apache.tools.ant.Task接口的類。我們的事務的屬性和子元素(sub-elements)都是通過set和add方法調用傳遞進來的。我們調用執行(execute)方法來實現事務所要執行的工作--在示例中,就是重構<fileset>中指定的類文件。

    public class InstrumentTask extends Task {
     ...
     public void setClass (String className) { ... }
     public void addFileSet (FileSet fileSet) { ... }

     public void execute () throws BuildException {
      Instrumentor inst = getInstrumentor();

      try {
       DirectoryScanner ds =fileSet.getDirectoryScanner(project);
       // Java 1.5 的"for" 語法
       for (String file : ds.getIncludedFiles()) {
        instrumentFile(inst, file);
       }
      } catch (Exception ex) {
       throw new BuildException(ex);
      }
     }
     ...
    }


      用于該項操作的BCEL 5.1版本有一個問題--它不支持分析注解。我可以載入正在重構的類并使用反射(reflection)查看注解。但是,如果這樣,我就不得不使用RetentionPolicy.RUNTIME來代替RetentionPolicy.CLASS。我還必須在這些類中執行一些靜態的初始化,而這些操作可能載入本地類庫或引入其它的依賴關系。幸運的是,BCEL提供了一種插件(plugin)機制,它允許客戶端分析字節碼屬性。我編寫了自己的AttributeReader的實現(implementation),在出現注解的時候,它知道如何分析插入字節碼中的RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations屬性。BCEL未來的版本應該會包含這種功能而不是作為插件提供。

      編譯時刻的字節碼重構方法顯示在示例代碼的code/02_compiletime目錄中。

      但是這種方法有很多缺陷。首先,我必須給建立過程增加額外的步驟。我不能基于命令行設置或其它編譯時沒有提供的信息來決定打開或關閉重構操作。如果重構的或沒有重構的代碼需要同時在產品環境中運行,那么就必須建立兩個單獨的.jars文件,而且還必須決定使用哪一個。

     

    在類載入時重構字節碼

      更好的方法可能是延遲字節碼重構操作,直到字節碼被載入的時候才進行重構。使用這種方法的時候,重構的字節碼不用保存起來。我們的應用程序啟動時刻的性能可能會受到影響,但是你卻可以基于自己的系統屬性或運行時配置數據來控制進行什么操作。

      Java 1.5之前,我們使用定制的類載入程序可能實現這種類文件維護操作。但是Java 1.5中新增加的java.lang.instrument程序包提供了少數附加的工具。特別地,它定義了ClassFileTransformer的概念,在標準的載入過程中我們可以使用它來重構一個類。

      為了在適當的時候(在載入任何類之前)注冊ClassFileTransformer,我需要定義一個premain方法。Java在載入主類(main class)之前將調用這個方法,并且它傳遞進來對Instrumentation對象的引用。我還必須給命令行增加-javaagent參數選項,告訴Java我們的premain方法的信息。這個參數選項把我們的agent class(代理類,它包含了premain方法)的全名和任意字符串作為參數。在例子中我們把Instrumentor類的全名作為參數(它必須在同一行之中):

    -javaagent:boxpeeking.instrument.InstrumentorAdaptor=
    boxpeeking.status.instrument.StatusInstrumentor


      現在我已經安排了一個回調(callback),它在載入任何含有注解的類之前都會發生,并且我擁有Instrumentation對象的引用,可以注冊我們的ClassFileTransformer了:

    public static void premain (String className,
    Instrumentation i)
    throws ClassNotFoundException,
    InstantiationException,
    IllegalAccessException
    {
     Class instClass = Class.forName(className);
     Instrumentor inst = (Instrumentor)instClass.newInstance();
     i.addTransformer(new InstrumentorAdaptor(inst));
    }


      我們在此處注冊的適配器將充當上面給出的Instrumentor接口和Java的ClassFileTransformer接口之間的橋梁。

    public class InstrumentorAdaptor
    implements ClassFileTransformer
    {
     public byte[] transform (ClassLoader cl,String className,Class classBeingRedefined,
    ProtectionDomain protectionDomain,byte[] classfileBuffer)
     {
      try {
       ClassParser cp =new ClassParser(new ByteArrayInputStream(classfileBuffer),className + ".java");
       JavaClass jc = cp.parse();

       ClassGen cg = new ClassGen(jc);

       for (Annotation an : getAnnotations(jc.getAttributes())) {
        instrumentor.instrumentClass(cg, an);
       }

       for (org.apache.bcel.classfile.Method m : cg.getMethods()) {
        for (Annotation an : getAnnotations(m.getAttributes())) {
         ConstantPoolGen cpg =cg.getConstantPool();
         MethodGen mg =new MethodGen(m, className, cpg);
         instrumentor.instrumentMethod(cg, mg, an);
         mg.setMaxStack();
         mg.setMaxLocals();
         cg.replaceMethod(m, mg.getMethod());
        }
       }
       JavaClass jcNew = cg.getJavaClass();
       return jcNew.getBytes();
      } catch (Exception ex) {
       throw new RuntimeException("instrumenting " + className, ex);
      }
     }
     ...
    }


      這種在啟動時重構字節碼的方法位于在示例的/code/03_startup目錄中。

      異常的處理

      文章前面提到,我希望編寫附加的代碼使用不同目的的@Status注解。我們來考慮一下一些額外的需求:我們的應用程序必須捕捉所有的未處理異常并把它們顯示給用戶。但是,我們不是提供Java堆棧跟蹤,而是顯示擁有@Status注解的方法,而且還不應該顯示任何代碼(類或方法的名稱或行號等等)。

      例如,考慮下面的堆棧跟蹤信息:

    java.lang.RuntimeException: Could not load data for symbol IBM
    at boxpeeking.code.YourCode.loadData(Unknown Source)
    at boxpeeking.code.YourCode.go(Unknown Source)
    at boxpeeking.yourcode.ui.Main+2.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:566)
    Caused by: java.lang.RuntimeException: Timed out
    at boxpeeking.code.YourCode.connectToDB(Unknown Source)
    ... 更多信息


      這將導致圖1中所示的GUI彈出框,上面的例子假設你的YourCode.loadData()、YourCode.go()和YourCode.connectToDB()都含有@Status注解。請注意,異常的次序是相反的,因此用戶最先得到的是最詳細的信息。

               
               圖3.顯示在錯誤對話框中的堆棧跟蹤信息

      為了實現這些功能,我必須對已有的代碼進行稍微的修改。首先,為了確保在運行時@Status注解是可以看到的,我就必須再次更新@Retention,把它設置為@Retention(RetentionPolicy.RUNTIME)。請記住,@Retention控制著JVM什么時候拋棄注解信息。這樣的設置意味著注解不僅可以被編譯器插入字節碼中,還能夠使用新的Method.getAnnotation(Class)方法通過反射來進行訪問。

      現在我需要安排接收代碼中沒有明確處理的任何異常的通知了。在Java 1.4中,處理任何特定線程上未處理異常的最好方法是使用ThreadGroup子類并給該類型的ThreadGroup添加自己的新線程。但是Java 1.5提供了額外的功能。我可以定義UncaughtExceptionHandler接口的一個實例,并為任何特定的線程(或所有線程)注冊它。

      請注意,在例子中為特定異常注冊可能更好,但是在Java 1.5.0beta1(#4986764)中有一個bug,它使這樣操作無法進行。但是為所有線程設置一個處理程序是可以工作的,因此我就這樣操作了。

      現在我們擁有了一種截取未處理異常的方法了,并且這些異常必須被報告給用戶。在GUI應用程序中,典型情況下這樣的操作是通過彈出一個包含整個堆棧跟蹤信息或簡單消息的模式對話框來實現的。在例子中,我希望在產生異常的時候顯示一個消息,但是我希望提供堆棧的@Status描述而不是類和方法的名稱。為了實現這個目的,我簡單地在Thread的StackTraceElement數組中查詢,找到與每個框架相關的java.lang.reflect.Method對象,并查詢它的堆棧注解列表。不幸的是,它只提供了方法的名稱,沒有提供方法的特征量(signature),因此這種技術不支持名稱相同的(但@Status注解不同的)重載方法。

      實現這種方法的示例代碼可以在peekinginside-pt2.tar.gz文件的/code/04_exceptions目錄中找到。

     

    取樣(Sampling)

      我現在有辦法把StackTraceElement數組轉換為@Status注解堆棧。這種操作比表明看到的更加有用。Java 1.5中的另一個新特性--線程反省(introspection)--使我們能夠從當前正在運行的線程中得到準確的StackTraceElement數組。有了這兩部分信息之后,我們就可以構造JstatusBar的另一種實現。StatusManager將不會在發生方法調用的時候接收通知,而是簡單地啟動一個附加的線程,讓它負責在正常的間隔期間抓取堆棧跟蹤信息和每個步驟的狀態。只要這個間隔期間足夠短,用戶就不會感覺到更新的延遲。

      下面使"sampler"線程背后的代碼,它跟蹤另一個線程的經過:

    class StatusSampler implements Runnable
    {
     private Thread watchThread;

     public StatusSampler (Thread watchThread)
     {
      this.watchThread = watchThread;
     }

     public void run ()
     {
      while (watchThread.isAlive()) {
       // 從線程中得到堆棧跟蹤信息
       StackTraceElement[] stackTrace =watchThread.getStackTrace();
       // 從堆棧跟蹤信息中提取狀態消息
       List<Status> statusList =StatusFinder.getStatus(stackTrace);
       Collections.reverse(statusList);
       // 用狀態消息建立某種狀態
       StatusState state = new StatusState();
       for (Status s : statusList) {
        String message = s.value();
        state.push(message);
       }

       // 更新當前的狀態
       StatusManager.setState(watchThread,state);
       //休眠到下一個周期
       try {
        Thread .sleep(SAMPLING_DELAY);
       } catch (InterruptedException ex) {}
      }

      //狀態復位
      StatusManager.setState(watchThread,new StatusState());
     }
    }

      與增加方法調用、手動或通過重構相比,取樣對程序的侵害性(invasive)更小。我根本不需要改變建立過程或命令行參數,或修改啟動過程。它也允許我通過調整SAMPLING_DELAY來控制占用的開銷。不幸的是,當方法調用開始或結束的時候,這種方法沒有明確的回調。除了狀態更新的延遲之外,沒有原因要求這段代碼在那個時候接收回調。但是,未來我能夠增加一些額外的代碼來跟蹤每個方法的準確的運行時。通過檢查StackTraceElement是可以精確地實現這樣的操作的。

      通過線程取樣實現JStatusBar的代碼可以在peekinginside-pt2.tar.gz文件的/code/05_sampling目錄中找到。

      在執行過程中重構字節碼

      通過把取樣的方法與重構組合在一起,我能夠形成一種最終的實現,它提供了各種方法的最佳特性。默認情況下可以使用取樣,但是應用程序的花費時間最多的方法可以被個別地進行重構。這種實現根本不會安裝ClassTransformer,但是作為代替,它會一次一個地重構方法以響應取樣過程中收集到的數據。

      為了實現這種功能,我將建立一個新類InstrumentationManager,它可以用于重構和不重構獨立的方法。它可以使用新的Instrumentation.redefineClasses方法來修改空閑的類,同時代碼則可以不間斷執行。前面部分中增加的StatusSampler線程現在有了額外的職責,它把任何自己"發現"的@Status方法添加到集合中。它將周期性地找出最壞的冒犯者并把它們提供給InstrumentationManager以供重構。這允許應用程序更加精確地跟蹤每個方法的啟動和終止時刻。

      前面提到的取樣方法的一個問題是它不能區分長時間運行的方法與在循環中多次調用的方法。由于重構會給每次方法調用增加一定的開銷,我們有必要忽略頻繁調用的方法。幸運的是,我們可以使用重構解決這個問題。除了簡單地更新StatusManager之外,我們將維護每個重構的方法被調用的次數。如果這個數值超過了某個極限(意味著維護這個方法的信息的開銷太大了),取樣線程將會永遠地取消對該方法的重構。

      理想情況下,我將把每個方法的調用數量存儲在重構過程中添加到類的新字段中。不幸的是,Java 1.5中增加的類轉換機制不允許這樣操作;它不能增加或刪除任何字段。作為代替,我將把這些信息存儲在新的CallCounter類的Method對象的靜態映射中。

      這種混合的方法可以在示例代碼的/code/06_dynamic目錄中找到。

      概括

      圖4提供了一個矩形,它顯示了我給出的例子相關的特性和代價。


    圖4.重構方法的分析

      你可以發現,動態的(Dynamic)方法是各種方案的良好組合。與使用重構的所有示例類似,它提供了方法開始或終止時刻的明確的回調,因此你的應用程序可以準確地跟蹤運行時并立即為用戶提供反饋信息。但是,它還能夠取消某種方法的重構(它被過于頻繁地調用),因此它不會受到其它的重構方案遇到的性能問題的影響。它沒有包含編譯時步驟,并且它沒有增加類載入過程中的額外的工作。

      未來的趨勢

      我們可以給這個項目增加大量的附件特性,使它更加適用。其中最有用的特性可能是動態的狀態信息。我們可以使用新的java.util.Formatter類把類似printf的模式替換(pattern substitution)用于@Status消息中。例如,我們的connectToDB(String url)方法中的@Status("Connecting to %s")注解可以把URL作為消息的一部分報告給用戶。

      在源代碼重構的幫助下,這可能顯得微不足道,因為我將使用的Formatter.format方法使用了可變參數(Java 1.5中增加的"魔術"功能)。重構過的版本類似下面的情形:

    /* http://www.5a520.cn */

    public void connectToDB (String url) {
     Formatter f = new Formatter();
     String message = f.format("Connecting to %s", url);

     StatusManager.push(message);
     try {
      ...
     } finally {
      StatusManager.pop();
     }
    }


      不幸的是,這種"魔術"功能是完全在編譯器中實現的。在字節碼中,Formatter.format把Object[]作為參數,編譯器明確地添加代碼來包裝每個原始的類型并裝配該數組。如果BCEL沒有加緊彌補,而我又需要使用字節碼重構,我將不得不重新實現這種邏輯。

      由于它只能用于重構(這種情況下方法參數是可用的)而不能用于取樣,你可能希望在啟動的時候重構這些方法,或最少使動態實現偏向于任何方法的重構,還可以在消息中使用替代模式。

      你還可以跟蹤每個重構的方法調用的啟動次數,因此你還可以更加精確地報告每個方法的運行次數。你甚至于可以保存這些次數的歷史統計數據,并使用它們形成一個真正的進度條(代替我使用的不確定的版本)。這種能力將賦予你在運行時重構某種方法的一個很好的理由,因為跟蹤任何獨立的方法的開銷都是很能很明顯的。

      你可以給進度條增加"調試"模式,它不管方法調用是否包含@Status注解,報告取樣過程中出現的所有方法調用。這對于任何希望調試死鎖或性能問題的開發者來說都是無價之寶。實際上,Java 1.5還為死鎖(deadlock)檢測提供了一個可編程的API,在應用程序鎖住的時候,我們可以使用該API把進程條變成紅色。

      本文中建立的基于注解的重構框架組件可能很有市場。一個允許字節碼在編譯時(通過Ant事務)、啟動時(使用ClassTransformer)和執行過程中(使用Instrumentation)進行重構的工具對于少量其它新項目來說毫無疑問地非常有價值。

      總結

      在這幾個例子中你可以看到,元數據編程(meta-programming)可能是一種非常強大的技術。報告長時間運行的操作的進程僅僅是這種技術的應用之一,而我們的JStatusBar僅僅是溝通這些信息的一種媒介。我們可以看到,Java 1.5中提供的很多新特性為元數據編程提供了增強的支持。特別地,把注解和運行時重構組合在一起為面向屬性的編程提供了真正動態的形式。我們可以進一步使用這些技術,使它的功能超越已有的框架組件(例如XDoclet提供的框架組件的功能)。
    posted @ 2009-03-12 20:35 wang9354| 編輯 收藏
    最近搞了一個java發送傳真程序,在網上搜了半天,沒找到具體的例子,最后找到了國外的開源技術jacob,才解決了這個問題。

     

           環境要求:windows xp系統,jdk 1.4(不要太高或太低,不然會發生與dll不匹配),運行java程序的機器需要有貓及驅動(一般的貓都會支持fax功能),jacob版本1.9(最好使用這個版本,其他版本會報錯),xp本身能夠通過貓發送傳真(確認一下環境可以發送傳真即可)

     

           實現功能:java jni調用本地jacob.dll,jacob.dll中封裝好的接口調用本地服務(如excel、outlook、vbscript等)faxserver.faxserver.1,實現服務器端發送傳真。

     

           步驟:

                 1 將jacob.dll文件拷貝到windows/system32下

                 2 創建java項目,將jacob.jar,jacob.dll放到項目lib路徑下,同時將他們也放到jdk的lib路徑下

                 3 編寫程序(可參考jacob官方的api文檔 http://www.5a520.cn ),如下:

     

    import com.jacob.activeX.ActiveXComponent;
    import com.jacob.com.ComThread;
    import com.jacob.com.Dispatch;
    import com.jacob.com.DispatchEvents;
    import com.jacob.com.Variant;

    public class faxtest {

     public void sendFax(String filename,Sring faxnumber) {
      ActiveXComponent objFax = new ActiveXComponent("FaxServer.FaxServer.1");//這個名字一般要與注冊表里fax服務名匹配對了


      Dispatch faxObject = objFax.getObject();

      Dispatch.call(faxObject, "Connect", "");
      Dispatch doc = Dispatch.call(faxObject, "CreateDocument", filename)
        .toDispatch();
      Dispatch.put(doc, "RecipientName", "someone");
      Dispatch.put(doc, "FaxNumber", faxnumber); //注意電話號碼的格式
      Dispatch.put(doc, "DisplayName", "zhupan");
      Dispatch.call(doc, "Send");
      Dispatch.call(faxObject, "DisConnect");
     }

     public static void main(String[] args) {

      try {
       faxtest faxDocumentProperties = new faxtest();
       faxDocumentProperties.sendFax(" http://www.bt285.cn /WW.doc","028886666");
       System.out.print("ok fax transfer successfully  !");
      } catch (Exception e) {
       System.out.println(e);
      }
     }

    }

     

     4 調試,如果報錯"no progid"異常,一般問題都是jdk與dll不匹配,或者傳真服務名稱(FaxServer.FaxServer.1)不匹配。

    posted @ 2009-03-10 16:27 wang9354| 編輯 收藏
    方法一:
    最簡單的方式就是在JSP頁面的開始部分使用如下的頭部信息
    <%response.setContentType("application/msexcel");
      response.setHeader("Content-disposition","attachment; filename=excelname.xls");%>
    <html xmlns:o="urn:schemas-microsoft-com:office:office"
    xmlns:x="urn:schemas-microsoft-com:office:excel"
    xmlns="http://www.w3.org/TR/REC-html40">

    <head>
    <meta http-equiv=Content-Type content="text/html; charset=GBK">
    <meta name=ProgId content=Excel.Sheet>
    <meta name=Generator content="Microsoft Excel 11">
    </head>
    在導出按鈕中,直接轉到要導出的頁面。設置為如上的頭部信息就可以。

    方法二
    使用script

    button  onclick事件調用下面js 方法
    然后你要導出的table定義個id=viewtable.這樣就只導出這個table的內容。操作按鈕在table外,不會導出來的。

    <script  language=javascript>
    function  printToExcel()  {
    window.clipboardData.setData( "Text ",document.all( 'viewtable ').outerHTML);
    try
    {
    var  ExApp  =  new  ActiveXObject( "Excel.Application ")
    var  ExWBk  =  ExApp.workbooks.add()
    var  ExWSh  =  ExWBk.worksheets(1)
    ExApp.DisplayAlerts  =  false
    ExApp.visible  =  true
    }
    catch(e)
    {
    alert( "您的電腦沒有安裝Microsoft  Excel軟件!需要從 return  false
    }
    ExWBk.worksheets(1).Paste;
    }
    </script>


    方法三、方法四:
    使用POI-JXL等插件
         摘要: 不用復雜的代碼,就可以讓您的JavaBeans自己控制到XML文件的相互轉化。本文展示了怎樣通過JOX來實現從JavaBeans到XML文件的相互轉換。    為了靈活的滿足Web應用和Web services需求的變化,Java和XML的輕便性和可擴展性使它們成為解決這一問題的理想選擇。SAX (Simple API ...  閱讀全文
    posted @ 2009-03-02 20:42 wang9354| 編輯 收藏

     

    以下內容總結自《Effective Java》。
    1.何時需要重寫equals()
    當一個類有自己特有的“邏輯相等”概念(不同于對象身份的概念)。
    2.設計equals()
    [1]使用instanceof操作符檢查“實參是否為正確的類型”。
    [2]對于類中的每一個“關鍵域”,檢查實參中的域與當前對象中對應的域值。
    [2.1]對于非float和double類型的原語類型域,使用==比較;
    [2.2]對于對象引用域,遞歸調用equals方法;
    [2.3]對于float域,使用Float.floatToIntBits(afloat)轉換為int,再使用==比較;
    [2.4]對于double域,使用Double.doubleToLongBits(adouble) 轉換為int,再使用==比較;
    [2.5]對于數組域,調用Arrays.equals方法。
    3.當改寫equals()的時候,總是要改寫hashCode()
    根據一個類的equals方法(改寫后),兩個截然不同的實例有可能在邏輯上是相等的,但是,根據Object.hashCode方法,它們僅僅是兩個對象。因此,違反了“相等的對象必須具有相等的散列碼”。
    4.設計hashCode()
    [1]把某個非零常數值,例如17,保存在int變量result中;
    [2]對于對象中每一個關鍵域f(指equals方法中考慮的每一個域):
    [2.1]boolean型,計算(f ? 0 : 1);
    [2.2]byte,char,short型,計算(int);
    [2.3]long型,計算(int) (f ^ (f>>>32));
    [2.4]float型,計算Float.floatToIntBits(afloat);
    [2.5]double型,計算Double.doubleToLongBits(adouble)得到一個long,再執行[2.3];
    [2.6]對象引用,遞歸調用它的hashCode方法;
    [2.7]數組域,對其中每個元素調用它的hashCode方法。
    [3]將上面計算得到的散列碼保存到int變量c,然后執行 result=37*result+c;
    [4]返回result。
    5.示例
    下面的這個類遵循上面的設計原則,重寫了類的equals()和hashCode()。
    package com.zj.unit;
    import java.util.Arrays;
     
    /**wangdei http://www.bt285.cn 
     *
    */

    public class Unit {
        
    private short ashort;
        
    private char achar;
        
    private byte abyte;
        
    private boolean abool;
        
    private long along;
        
    private float afloat;
        
    private double adouble;
        
    private Unit aObject;
        
    private int[] ints;
        
    private Unit[] units;
     
        
    public boolean equals(Object o) {
           
    if (!(o instanceof Unit))
               
    return false;
           Unit unit 
    = (Unit) o;
           
    return unit.ashort == ashort
                  
    && unit.achar == achar
                  
    && unit.abyte == abyte
                  
    && unit.abool == abool
                  
    && unit.along == along
                  
    && Float.floatToIntBits(unit.afloat) == Float
                         .floatToIntBits(afloat)
                  
    && Double.doubleToLongBits(unit.adouble) == Double
                         .doubleToLongBits(adouble)
                  
    && unit.aObject.equals(aObject) 
    && equalsInts(unit.ints)
                  
    && equalsUnits(unit.units);
        }

     
        
    private boolean equalsInts(int[] aints) {
           
    return Arrays.equals(ints, aints);
        }

     
        
    private boolean equalsUnits(Unit[] aUnits) {
           
    return Arrays.equals(units, aUnits);
        }

     
        
    public int hashCode() {
           
    int result = 17;
           result 
    = 37 * result + (int) ashort;
           result 
    = 37 * result + (int) achar;
           result 
    = 37 * result + (int) abyte;
           result 
    = 37 * result + (abool ? 0 : 1);
           result 
    = 37 * result + (int) (along ^ (along >>> 32));
           result 
    = 37 * result + Float.floatToIntBits(afloat);
           
    long tolong = Double.doubleToLongBits(adouble);
           result 
    = 37 * result + (int) (tolong ^ (tolong >>> 32));
           result 
    = 37 * result + aObject.hashCode();
           result 
    = 37 * result + intsHashCode(ints);
           result 
    = 37 * result + unitsHashCode(units);
           
    return result;
        }

     
        
    private int intsHashCode(int[] aints) {
           
    int result = 17;
           
    for (int i = 0; i < aints.length; i++)
               result 
    = 37 * result + aints[i];
           
    return result;
        }

     
        
    private int unitsHashCode(Unit[] aUnits) {
           
    int result = 17;
           
    for (int i = 0; i < aUnits.length; i++)
               result 
    = 37 * result + aUnits[i].hashCode();
           
    return result;
        }

    }
    posted @ 2009-02-25 20:26 wang9354| 編輯 收藏

    雖然在很多方面,JavaScript可用于改進您的網頁并提高您的訪問網站的效率,但是也有幾件事的JavaScript不能做到的。其中的一些限制是由于該腳本瀏覽器窗口運行,因此無法訪問服務器,而另一些則是出于安全性的考慮,以阻止網頁篡改您的計算機。對于這些局限性還沒有可以解決的辦法,而任何抱怨其電腦不能通過JavaScript執行下列任務的人,是因為沒有對所要做的事情考慮周全。

      沒有服務器端腳本的幫助,JavaScript就不能在服務器上寫文件

      使用Ajax,JavaScript可以向服務器發送請求。這個請求可以用XML或純文本的方式讀取文件,但是它不能寫文件,除非被服務器調用的文件以腳本方式運行才能寫文件。比如 http://www.bt285.cn/content.php?id=1196863 這張甜性澀愛下載頁面是用json獲取的,但是在此頁面里不能直接寫入數據。
      JavaScript不能訪問數據庫

      除非你使用Ajax,并且服務器端腳本為你執行數據庫訪問

      JavaScript不能從用戶處讀取或寫文件

      盡管JavaScript在用戶端計算機上運行,而該用戶端也正在瀏覽網頁,但仍不允許對任何網頁本身以外的數據進行訪問。這樣做是出于安全的考慮,因為其他網頁有可能更新您的計算機并且非法安裝上我們都不清楚的東西。唯一例外的是所謂的cookies文件,它是小文本文件,可以由JavaScript寫入和讀取。該瀏覽器限制對Cookie的訪問,所以一個給定的網頁只能訪問該網頁所創造的cookie。

      如果沒有打開窗口,JavaScript不能選擇窗口

      該項限制同樣出于安全性的考慮

      JavaScript不能訪問網頁

      盡管不同的網頁可以在同一時間打開,可以在單獨的瀏覽器中或者同一個瀏覽器的不同窗體中打開。在網頁上運行的JavaScript從屬于一個網頁,因此不能訪問來自不同域名中不同網頁的信息。這一限制有助于確保你的隱私信息不會被其他同時打開網頁的人共享。而唯一能訪問來自另一域名的文件的方法是對你的服務器進行Ajax調用,并卻具備一個可以訪問其他域名的服務器端腳本。

      JavaScript不能保護你的頁面資源和圖像

      頁面上的任何圖像都是分開下載到電腦上的,所以我們在看網頁的時候,就已經擁有了所有圖像的備份。而對于網頁上真正的HTML資源,也同樣如此。網頁需要解密所有加密的網頁,以顯示該網頁。而一個加密的我那個也可能要求按順序依次激活JavaScript,以達到依次解密再顯示出來的目的。一旦網頁被解密,任何知道該方法的人都能輕易保存解密的網頁資源備份,比如 http://www.5a520.cn/s_c1vvs30vvf5a6Y6Lev6aOO5rWB 這張官路風流最新章節520頁面,一但下載到客戶端,那些這張頁面所相當的js,css,jpg等http連接也下載到你本地了。

    posted @ 2009-02-22 11:17 wang9354| 編輯 收藏

    導航

    <2009年2月>
    25262728293031
    1234567
    891011121314
    15161718192021
    22232425262728
    1234567

    統計

    常用鏈接

    留言簿(2)

    隨筆檔案

    友情鏈接

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 国产免费一级高清淫曰本片| 午夜影院免费观看| 亚洲无线码在线一区观看| 午夜免费福利视频| 亚洲欧美黑人猛交群| 国产成人亚洲综合无| 亚洲精品国产精品乱码不卡√| 精品无码国产污污污免费网站| 亚洲国产一区二区三区在线观看| 久久影院亚洲一区| 美女视频黄免费亚洲| 久久久久久亚洲av成人无码国产| 妞干网手机免费视频| 抽搐一进一出gif免费视频| 亚洲精品电影天堂网| 亚洲精品无码av天堂| 日本最新免费网站| 亚洲国产日韩女人aaaaaa毛片在线| www国产亚洲精品久久久日本| 国产亚洲福利一区二区免费看| 亚洲AV无码一区二区乱子伦| 午夜两性色视频免费网站| 久久香蕉国产线看免费| 羞羞网站免费观看| 亚洲伊人久久大香线蕉| 亚洲精品无码mv在线观看网站| 日韩在线a视频免费播放| 最近免费中文在线视频| 国产免费MV大全视频网站 | 疯狂做受xxxx高潮视频免费| 亚洲av无码一区二区三区乱子伦 | www视频免费看| 最近2019中文免费字幕在线观看| 色偷偷噜噜噜亚洲男人| 亚洲av片不卡无码久久| 亚洲福利在线观看| 亚洲色婷婷六月亚洲婷婷6月| 免费中文字幕在线| 麻豆国产人免费人成免费视频| 999国内精品永久免费视频| 久久aa毛片免费播放嗯啊|