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

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

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

    2009年3月6日

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

    我們通常在開發(fā)web應(yīng)用過程中,展現(xiàn)層Action的單元測試經(jīng)常被我們忽視了,主要原因是:

    1、Action層的業(yè)務(wù)邏輯比較簡單。大家潛意識(shí)認(rèn)為這一部分的代碼不重要。

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

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

    一、設(shè)置ActionContext上下文參數(shù)

    將表單傳遞的請求參數(shù)添加到map中

    二、創(chuàng)建Action動(dòng)態(tài)代理對象

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

    三、junit斷言執(zhí)行結(jié)果

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

    詳細(xì)用例參考:

    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”);

    }

    }

    注:創(chuàng)建代理action一定要執(zhí)行proxy.execute()方法,否則參數(shù)不能夠增加到actionContext上下文中。因?yàn)閜roxy.execute()中會(huì)執(zhí)行 invocation.invoke()核心方法,遍歷執(zhí)行action中所有的攔截器,包括其中的參數(shù)攔截器

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

    某天在服務(wù)器上的網(wǎng)頁打不開了,頻繁報(bào)以下錯(cuò)誤。

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

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

    分析:
    當(dāng)時(shí)使用網(wǎng)站的人數(shù)只有2個(gè)人,不可能答到到了并發(fā)線程150的上線。所以應(yīng)該不是數(shù)據(jù)庫的問題。
    通過對出錯(cuò)的提示判斷,應(yīng)該是連接池使用不合理造成的,或者根本沒設(shè)置連接池。和數(shù)據(jù)庫連接的部分是使用Spring的數(shù)據(jù)源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>

    問題應(yīng)該出現(xiàn)在Spring的DriverManagerDataSource上,它負(fù)責(zé)管理這些連接的。
    下邊是對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.

    看完了解釋,事實(shí)上是因?yàn)镈riverManagerDataSource建立連接是只要有連接就新建一個(gè)connection,根本沒有連接池的作用。改為以下開源的連接池會(huì)好點(diǎn)。
    <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>

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

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

    Nexus介紹

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

     

    下載和安裝

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

    Nexus提供了兩種安裝方式,一種是內(nèi)嵌Jetty的bundle,只要你有JRE就能直接運(yùn)行。第二種方式是WAR,你只須簡單的將其發(fā)布到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 ,運(yùn)行Nexus.bat 。你會(huì)看到Nexus的啟動(dòng)日志,當(dāng)你看到“Started SelectChannelConnector@0.0.0.0:8081”之后,說明Nexus啟動(dòng)成功了,然后打開瀏覽器,訪問http://127.0.0.1:8081/nexus,你會(huì)看到如下的頁面:

    要停止Nexus,Ctrl+C即可,此外InstallNexus.bat可以用來將Nexus安裝成一個(gè)windows服務(wù),其余的腳本則對應(yīng)了啟動(dòng),停止,暫停,恢復(fù),卸載Nexus服務(wù)。

     

    WAR方式安裝

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

     

    代理外部Maven倉庫

    登陸

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

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

     

    代理Maven中央倉庫

    點(diǎn)擊左邊導(dǎo)航欄的Repositories,界面的主面板會(huì)顯示所有一個(gè)所有倉庫及倉庫組的列表,你會(huì)看到它們的Type字段的值有g(shù)roup,hosted,proxy,virtual。這里我們不關(guān)心virtual,只介紹下另外三種類型:

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

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

     

    添加一個(gè)代理倉庫

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

     

    管理本地Maven倉庫

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

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

     

    管理Maven倉庫組

    Nexus中倉庫組的概念是Maven沒有的,在Maven看來,不管你是hosted也好,proxy也好,或者group也好,對我都是一樣的,我只管根據(jù)groupId,artifactId,version等信息向你要構(gòu)件。為了方便Maven的配置,Nexus能夠?qū)⒍鄠€(gè)倉庫,hosted或者proxy合并成一個(gè)group,這樣,Maven只需要依賴于一個(gè)group,便能使用所有該group包含的倉庫的內(nèi)容。

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

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

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

     

    搜索構(gòu)件

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

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

    選擇一項(xiàng)搜索結(jié)果,在頁面下方會(huì)顯示“Artifact Information”的面板,你可以點(diǎn)擊"artifact"或者"pom"下載對應(yīng)文件,而該面板右邊更顯示了一個(gè)Maven依賴配置,你可以直接復(fù)制該配置到Maven POM中,這是個(gè)十分方便的特性。

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

     

    配置Maven使用Nexus

    默認(rèn)情況下,Maven依賴于中央倉庫,這是為了能讓Maven開箱即用,但僅僅這么做明顯是錯(cuò)誤的,這會(huì)造成大量的時(shí)間及帶寬的浪費(fèi)。既然文章的前面已經(jīng)介紹了如何安裝和配置Nexus,現(xiàn)在我們就要配置Maven來使用本地的Nexus,以節(jié)省時(shí)間和帶寬資源。

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

    Xml代碼 復(fù)制代碼
    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>元素,這里我們編寫了一個(gè)profile,并添加了一個(gè)profile并使用<activeProfile>元素自動(dòng)將這個(gè)profile激活。這里的local-nexus倉庫指向了剛才我們配置的Nexus中“Public Repositories”倉庫組,也就是說,所有該倉庫組包含的倉庫都能供我們使用。此外,我們通過<releases>和<snapshots>元素激活了Maven對于倉庫所有類型構(gòu)件下載的支持,當(dāng)然你也可以調(diào)節(jié)該配置,比如說禁止Maven從Nexus下載snapshot構(gòu)件。

    使用該配置,Maven就會(huì)從你的Nexus服務(wù)器下載構(gòu)件了,速度和從Central下載可不是一個(gè)數(shù)量級(jí)的。

     

    部署構(gòu)件至Nexus

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

     

    通過Nexus UI部署

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

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


    通過Maven部署

    更常見的用例是:團(tuán)隊(duì)在開發(fā)一個(gè)項(xiàng)目的各個(gè)模塊,為了讓自己開發(fā)的模塊能夠快速讓其他人使用,你會(huì)想要將snapshot版本的構(gòu)件部署到Maven倉庫中,其他人只需要在POM添加一個(gè)對于你開發(fā)模塊的依賴,就能隨時(shí)拿到最新的snapshot。

    以下的pom.xml配置和settings.xml能讓你通過Maven自動(dòng)化部署構(gòu)件:

    pom.xml

    Xml代碼 復(fù)制代碼
    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代碼 復(fù)制代碼
    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版本構(gòu)件部署到Nexus的Snapshots倉庫中, 所有的release構(gòu)件部署到Nexus的Releases倉庫中。由于部署需要登陸,因?yàn)槲覀冊趕ettings.xml中配置對應(yīng)Repository id的用戶名和密碼。

    然后,在項(xiàng)目目錄中執(zhí)行mvn deploy ,你會(huì)看到maven將項(xiàng)目構(gòu)件部署到Nexus中,瀏覽Nexus對應(yīng)的倉庫,就可以看到剛才部署的構(gòu)件。當(dāng)其他人構(gòu)建其項(xiàng)目時(shí),Maven就會(huì)從Nexus尋找依賴并下載。

     

    總結(jié)

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

    Nexus的OSS版本是完全開源的,如果你有興趣,你可以學(xué)習(xí)其源碼,甚至自己實(shí)現(xiàn)一個(gè)REST客戶端。

    馬上擁抱Nexus吧,它是免費(fèi)的!

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

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

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

      在本文中我將考慮使用嵌入GUI應(yīng)用程序中的狀態(tài)條組件的情形。我將介紹多種實(shí)現(xiàn)這種狀態(tài)報(bào)告的不同方法,從傳統(tǒng)的硬編碼習(xí)慣開始。隨后我會(huì)介紹Java 1.5的大量新特性,包括注解(annotation)和運(yùn)行時(shí)字節(jié)碼重構(gòu)(instrumentation)。
    狀態(tài)管理器(StatusManager)

      我的主要目標(biāo)是建立一個(gè)可以嵌入GUI應(yīng)用程序的JStatusBar Swing組件。圖1顯示了一個(gè)簡單的Jframe中狀態(tài)條的樣式。



     
    圖1.我們動(dòng)態(tài)生成的狀態(tài)條


      由于我不希望直接在業(yè)務(wù)邏輯中引用任何GUI組件,我將建立一個(gè)StatusManager(狀態(tài)管理器)來充當(dāng)狀態(tài)更新的入口點(diǎn)。實(shí)際的通知會(huì)被委托給StatusState對象,因此以后可以擴(kuò)展它以支持多個(gè)并發(fā)的線程。圖2顯示了這種安排。



     
    圖2. StatusManager和JstatusBar


      現(xiàn)在我必須編寫代碼調(diào)用StatusManager的方法來報(bào)告應(yīng)用程序的進(jìn)程。典型情況下,這些方法調(diào)用都分散地貫穿于try-finally代碼塊中,通常每個(gè)方法一個(gè)調(diào)用。

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

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


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

      面向?qū)傩跃幊?/strong>

      我真正想實(shí)現(xiàn)的操作是把對StatusManager的引用都放到代碼外面的某個(gè)地方,并簡單地用我們的消息標(biāo)記這個(gè)方法。接著我可以使用代碼生成(code-generation)或運(yùn)行時(shí)反省(introspection)來執(zhí)行真正的工作。XDoclet項(xiàng)目把這種方法歸納為面向?qū)傩跃幊蹋ˋttribute-Oriented Programming),它還提供了一個(gè)框架組件,可以把自定義的類似Javadoc的標(biāo)記轉(zhuǎn)換到源代碼之中。

      但是,JSR-175包含了這樣的內(nèi)容,Java 1.5為了包含真實(shí)代碼中的這些屬性提供了一種結(jié)構(gòu)化程度更高的格式。這些屬性被稱為"注解(annotations)",我們可以使用它們?yōu)轭悺⒎椒ā⒆侄位蜃兞慷x提供元數(shù)據(jù)。它們必須被顯式聲明,并提供一組可以包含任意常量值(包括原語、字符串、枚舉和類)的名稱-值對(name-value pair)。

      注解(Annotations)

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

    public @interface Status {
     String value();
    }


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

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

      我現(xiàn)在可以使用下面的代碼定義自己的方法:

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


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

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

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


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

     

    重構(gòu)源代碼

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

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

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

      不是重構(gòu)源代碼然后編譯它,而是編譯原始的源代碼,然后重構(gòu)它所產(chǎn)生的字節(jié)碼。這樣的操作可能比源代碼重構(gòu)更容易,也可能更加復(fù)雜,而這依賴于需要的準(zhǔn)確轉(zhuǎn)換。字節(jié)碼重構(gòu)的主要優(yōu)點(diǎn)是代碼可以在運(yùn)行時(shí)被修改,不需要使用編譯器。

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

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

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


      ClassGen和MethodGen都是BCEL類,它們使用了Builder模式(pattern)。也就是說,它們?yōu)楦淖兤渌豢勺兊模╥mmutable)對象、以及可變的和不可變的表現(xiàn)(representation)之間的轉(zhuǎn)換提供了方法。

      現(xiàn)在我需要為接口編寫實(shí)現(xiàn),它必須用恰當(dāng)?shù)腟tatusManager調(diào)用更換@Status注解。前面提到,我希望把這些調(diào)用包含在try-finally代碼塊中。請注意,要達(dá)到這個(gè)目標(biāo),我們所使用的注解必須用@Retention(RetentionPolicy.CLASS)進(jìn)行標(biāo)記,它指示Java編譯器在編譯過程中不要拋棄注解。由于在前面我把@Status聲明為@Retention(RetentionPolicy.SOURCE)的,我必須更新它。

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

      下面是表現(xiàn)一個(gè)普通方法調(diào)用的字節(jié)碼,它被StatusManager更新環(huán)繞著:

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


      下面是相同的方法調(diào)用,但是位于try-finally代碼塊中,因此,如果它產(chǎn)生了異常會(huì)調(diào)用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


      你可以發(fā)現(xiàn),為了實(shí)現(xiàn)一個(gè)try-finally,我必須復(fù)制一些指令,并添加了幾個(gè)跳轉(zhuǎn)和異常表記錄。幸運(yùn)的是,BCEL的InstructionList類使這種工作相當(dāng)簡單。

      在運(yùn)行時(shí)重構(gòu)字節(jié)碼

      現(xiàn)在我擁有了一個(gè)基于注解修改類的接口和該接口的具體實(shí)現(xiàn)了,下一步是編寫調(diào)用它的實(shí)際框架組件。實(shí)際上我將編寫少量的框架組件,先從運(yùn)行時(shí)重構(gòu)所有類的框架組件開始。由于這種操作會(huì)在build過程中發(fā)生,我決定為它定義一個(gè)Ant事務(wù)。build.xml文件中的重構(gòu)目標(biāo)的聲明應(yīng)該如下:

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


      為了實(shí)現(xiàn)這種事務(wù),我必須定義一個(gè)實(shí)現(xiàn)org.apache.tools.ant.Task接口的類。我們的事務(wù)的屬性和子元素(sub-elements)都是通過set和add方法調(diào)用傳遞進(jìn)來的。我們調(diào)用執(zhí)行(execute)方法來實(shí)現(xiàn)事務(wù)所要執(zhí)行的工作--在示例中,就是重構(gòu)<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);
      }
     }
     ...
    }


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

      編譯時(shí)刻的字節(jié)碼重構(gòu)方法顯示在示例代碼的code/02_compiletime目錄中。

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

     

    在類載入時(shí)重構(gòu)字節(jié)碼

      更好的方法可能是延遲字節(jié)碼重構(gòu)操作,直到字節(jié)碼被載入的時(shí)候才進(jìn)行重構(gòu)。使用這種方法的時(shí)候,重構(gòu)的字節(jié)碼不用保存起來。我們的應(yīng)用程序啟動(dòng)時(shí)刻的性能可能會(huì)受到影響,但是你卻可以基于自己的系統(tǒng)屬性或運(yùn)行時(shí)配置數(shù)據(jù)來控制進(jìn)行什么操作。

      Java 1.5之前,我們使用定制的類載入程序可能實(shí)現(xiàn)這種類文件維護(hù)操作。但是Java 1.5中新增加的java.lang.instrument程序包提供了少數(shù)附加的工具。特別地,它定義了ClassFileTransformer的概念,在標(biāo)準(zhǔn)的載入過程中我們可以使用它來重構(gòu)一個(gè)類。

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

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


      現(xiàn)在我已經(jīng)安排了一個(gè)回調(diào)(callback),它在載入任何含有注解的類之前都會(huì)發(fā)生,并且我擁有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));
    }


      我們在此處注冊的適配器將充當(dāng)上面給出的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);
      }
     }
     ...
    }


      這種在啟動(dòng)時(shí)重構(gòu)字節(jié)碼的方法位于在示例的/code/03_startup目錄中。

      異常的處理

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

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

    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)
    ... 更多信息


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

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

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

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

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

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

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

     

    取樣(Sampling)

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

      下面使"sampler"線程背后的代碼,它跟蹤另一個(gè)線程的經(jīng)過:

    class StatusSampler implements Runnable
    {
     private Thread watchThread;

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

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

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

      //狀態(tài)復(fù)位
      StatusManager.setState(watchThread,new StatusState());
     }
    }

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

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

      在執(zhí)行過程中重構(gòu)字節(jié)碼

      通過把取樣的方法與重構(gòu)組合在一起,我能夠形成一種最終的實(shí)現(xiàn),它提供了各種方法的最佳特性。默認(rèn)情況下可以使用取樣,但是應(yīng)用程序的花費(fèi)時(shí)間最多的方法可以被個(gè)別地進(jìn)行重構(gòu)。這種實(shí)現(xiàn)根本不會(huì)安裝ClassTransformer,但是作為代替,它會(huì)一次一個(gè)地重構(gòu)方法以響應(yīng)取樣過程中收集到的數(shù)據(jù)。

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

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

      理想情況下,我將把每個(gè)方法的調(diào)用數(shù)量存儲(chǔ)在重構(gòu)過程中添加到類的新字段中。不幸的是,Java 1.5中增加的類轉(zhuǎn)換機(jī)制不允許這樣操作;它不能增加或刪除任何字段。作為代替,我將把這些信息存儲(chǔ)在新的CallCounter類的Method對象的靜態(tài)映射中。

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

      概括

      圖4提供了一個(gè)矩形,它顯示了我給出的例子相關(guān)的特性和代價(jià)。


    圖4.重構(gòu)方法的分析

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

      未來的趨勢

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

      在源代碼重構(gòu)的幫助下,這可能顯得微不足道,因?yàn)槲覍⑹褂玫腇ormatter.format方法使用了可變參數(shù)(Java 1.5中增加的"魔術(shù)"功能)。重構(gòu)過的版本類似下面的情形:

    /* 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();
     }
    }


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

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

      你還可以跟蹤每個(gè)重構(gòu)的方法調(diào)用的啟動(dòng)次數(shù),因此你還可以更加精確地報(bào)告每個(gè)方法的運(yùn)行次數(shù)。你甚至于可以保存這些次數(shù)的歷史統(tǒng)計(jì)數(shù)據(jù),并使用它們形成一個(gè)真正的進(jìn)度條(代替我使用的不確定的版本)。這種能力將賦予你在運(yùn)行時(shí)重構(gòu)某種方法的一個(gè)很好的理由,因?yàn)楦櫲魏为?dú)立的方法的開銷都是很能很明顯的。

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

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

      總結(jié)

      在這幾個(gè)例子中你可以看到,元數(shù)據(jù)編程(meta-programming)可能是一種非常強(qiáng)大的技術(shù)。報(bào)告長時(shí)間運(yùn)行的操作的進(jìn)程僅僅是這種技術(shù)的應(yīng)用之一,而我們的JStatusBar僅僅是溝通這些信息的一種媒介。我們可以看到,Java 1.5中提供的很多新特性為元數(shù)據(jù)編程提供了增強(qiáng)的支持。特別地,把注解和運(yùn)行時(shí)重構(gòu)組合在一起為面向?qū)傩缘木幊烫峁┝苏嬲齽?dòng)態(tài)的形式。我們可以進(jìn)一步使用這些技術(shù),使它的功能超越已有的框架組件(例如XDoclet提供的框架組件的功能)。
    posted @ 2009-03-12 20:35 wang9354| 編輯 收藏
    最近搞了一個(gè)java發(fā)送傳真程序,在網(wǎng)上搜了半天,沒找到具體的例子,最后找到了國外的開源技術(shù)jacob,才解決了這個(gè)問題。

     

           環(huán)境要求:windows xp系統(tǒng),jdk 1.4(不要太高或太低,不然會(huì)發(fā)生與dll不匹配),運(yùn)行java程序的機(jī)器需要有貓及驅(qū)動(dòng)(一般的貓都會(huì)支持fax功能),jacob版本1.9(最好使用這個(gè)版本,其他版本會(huì)報(bào)錯(cuò)),xp本身能夠通過貓發(fā)送傳真(確認(rèn)一下環(huán)境可以發(fā)送傳真即可)

     

           實(shí)現(xiàn)功能:java jni調(diào)用本地jacob.dll,jacob.dll中封裝好的接口調(diào)用本地服務(wù)(如excel、outlook、vbscript等)faxserver.faxserver.1,實(shí)現(xiàn)服務(wù)器端發(fā)送傳真。

     

           步驟:

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

                 2 創(chuàng)建java項(xiàng)目,將jacob.jar,jacob.dll放到項(xiàng)目lib路徑下,同時(shí)將他們也放到j(luò)dk的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");//這個(gè)名字一般要與注冊表里fax服務(wù)名匹配對了


      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); //注意電話號(hào)碼的格式
      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 調(diào)試,如果報(bào)錯(cuò)"no progid"異常,一般問題都是jdk與dll不匹配,或者傳真服務(wù)名稱(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>
    在導(dǎo)出按鈕中,直接轉(zhuǎn)到要導(dǎo)出的頁面。設(shè)置為如上的頭部信息就可以。

    方法二
    使用script

    button  onclick事件調(diào)用下面js 方法
    然后你要導(dǎo)出的table定義個(gè)id=viewtable.這樣就只導(dǎo)出這個(gè)table的內(nèi)容。操作按鈕在table外,不會(huì)導(dǎo)出來的。

    <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等插件

    導(dǎo)航

    <2009年3月>
    22232425262728
    1234567
    891011121314
    15161718192021
    22232425262728
    2930311234

    統(tǒng)計(jì)

    • 隨筆 - 12
    • 文章 - 0
    • 評(píng)論 - 0
    • 引用 - 0

    常用鏈接

    留言簿(2)

    隨筆檔案

    友情鏈接

    搜索

    •  

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 亚洲AV无码一区二区三区牲色| 亚洲国产成人综合精品| 国产2021精品视频免费播放| 亚洲激情电影在线| 日韩一级视频免费观看| 产传媒61国产免费| 久久精品国产亚洲AV电影| 成人性生活免费视频| 三级黄色免费观看| 亚洲三级在线视频| 国产精品亚洲w码日韩中文| 亚洲成人在线免费观看| 污网站在线免费观看| 亚洲欧洲日产国码在线观看| 国产亚洲精品线观看动态图| 青娱分类视频精品免费2| 一个人看的免费观看日本视频www| 亚洲欧洲尹人香蕉综合| 亚洲免费视频观看| 亚洲国产a∨无码中文777| 日本免费一区二区三区最新| 免费在线观看日韩| 午夜性色一区二区三区免费不卡视频 | 全亚洲最新黄色特级网站| 免费人成毛片动漫在线播放| 国产精品亚洲综合一区在线观看| 亚洲日本在线免费观看| 中文字幕精品三区无码亚洲| 国产AV无码专区亚洲AVJULIA | 久久久久久A亚洲欧洲AV冫| 亚洲成a人片在线观看无码| 亚洲不卡视频在线观看| 亚洲av无码一区二区三区人妖 | 亚洲av片不卡无码久久| 亚洲国产综合AV在线观看| 四虎永久在线精品免费一区二区| 中文字幕免费不卡二区| 久99久无码精品视频免费播放| 国产成人va亚洲电影| 91在线免费观看| 人妻在线日韩免费视频|