一個(gè)小型的網(wǎng)站,比如個(gè)人網(wǎng)站,可以使用最簡(jiǎn)單的html靜態(tài)頁(yè)面就實(shí)現(xiàn)了,配合一些圖片達(dá)到美化效果,所有的頁(yè)面均存放在一個(gè)目錄下,這樣的網(wǎng)站對(duì)系統(tǒng)架構(gòu)、性能的要求都很簡(jiǎn)單,隨著互聯(lián)網(wǎng)業(yè)務(wù)的不斷豐富,網(wǎng)站相關(guān)的技術(shù)經(jīng)過(guò)這些年的發(fā)展,已經(jīng)細(xì)分到很細(xì)的方方面面,尤其對(duì)于大型網(wǎng)站來(lái)說(shuō),所采用的技術(shù)更是涉及面非常廣,從硬件到軟件、編程語(yǔ)言、數(shù)據(jù)庫(kù)、WebServer、防火墻等各個(gè)領(lǐng)域都有了很高的要求,已經(jīng)不是原來(lái)簡(jiǎn)單的html靜態(tài)網(wǎng)站所能比擬的。
創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都做網(wǎng)站、成都網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的臨洮網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
大型網(wǎng)站,比如門(mén)戶(hù)網(wǎng)站。在面對(duì)大量用戶(hù)訪問(wèn)、高并發(fā)請(qǐng)求方面,基本的解決方案集中在這樣幾個(gè)環(huán)節(jié):使用高性能的服務(wù)器、高性能的數(shù)據(jù)庫(kù)、高效率的編程語(yǔ)言、還有高性能的Web容器。但是除了這幾個(gè)方面,還沒(méi)法根本解決大型網(wǎng)站面臨的高負(fù)載和高并發(fā)問(wèn)題。
上面提供的幾個(gè)解決思路在一定程度上也意味著更大的投入,并且這樣的解決思路具備瓶頸,沒(méi)有很好的擴(kuò)展性,下面我從低成本、高性能和高擴(kuò)張性的角度來(lái)說(shuō)說(shuō)我的一些經(jīng)驗(yàn)。
1、HTML靜態(tài)化
其實(shí)大家都知道,效率最高、消耗最小的就是純靜態(tài)化的html頁(yè)面,所以我們盡可能使我們的網(wǎng)站上的頁(yè)面采用靜態(tài)頁(yè)面來(lái)實(shí)現(xiàn),這個(gè)最簡(jiǎn)單的方法其實(shí)也是最有效的方法。但是對(duì)于大量?jī)?nèi)容并且頻繁更新的網(wǎng)站,我們無(wú)法全部手動(dòng)去挨個(gè)實(shí)現(xiàn),于是出現(xiàn)了我們常見(jiàn)的信息發(fā)布系統(tǒng)CMS,像我們常訪問(wèn)的各個(gè)門(mén)戶(hù)站點(diǎn)的新聞?lì)l道,甚至他們的其他頻道,都是通過(guò)信息發(fā)布系統(tǒng)來(lái)管理和實(shí)現(xiàn)的,信息發(fā)布系統(tǒng)可以實(shí)現(xiàn)最簡(jiǎn)單的信息錄入自動(dòng)生成靜態(tài)頁(yè)面,還能具備頻道管理、權(quán)限管理、自動(dòng)抓取等功能,對(duì)于一個(gè)大型網(wǎng)站來(lái)說(shuō),擁有一套高效、可管理的CMS是必不可少的。
除了門(mén)戶(hù)和信息發(fā)布類(lèi)型的網(wǎng)站,對(duì)于交互性要求很高的社區(qū)類(lèi)型網(wǎng)站來(lái)說(shuō),盡可能的靜態(tài)化也是提高性能的必要手段,將社區(qū)內(nèi)的帖子、文章進(jìn)行實(shí)時(shí)的靜態(tài)化,有更新的時(shí)候再重新靜態(tài)化也是大量使用的策略,像Mop的大雜燴就是使用了這樣的策略,網(wǎng)易社區(qū)等也是如此。
同時(shí),html靜態(tài)化也是某些緩存策略使用的手段,對(duì)于系統(tǒng)中頻繁使用數(shù)據(jù)庫(kù)查詢(xún)但是內(nèi)容更新很小的應(yīng)用,可以考慮使用html靜態(tài)化來(lái)實(shí)現(xiàn),比如論壇中論壇的公用設(shè)置信息,這些信息目前的主流論壇都可以進(jìn)行后臺(tái)管理并且存儲(chǔ)再數(shù)據(jù)庫(kù)中,這些信息其實(shí)大量被前臺(tái)程序調(diào)用,但是更新頻率很小,可以考慮將這部分內(nèi)容進(jìn)行后臺(tái)更新的時(shí)候進(jìn)行靜態(tài)化,這樣避免了大量的數(shù)據(jù)庫(kù)訪問(wèn)請(qǐng)求。
2、圖片服務(wù)器分離
大家知道,對(duì)于Web服務(wù)器來(lái)說(shuō),不管是Apache、IIS還是其他容器,圖片是最消耗資源的,于是我們有必要將圖片與頁(yè)面進(jìn)行分離,這是基本上大型網(wǎng)站都會(huì)采用的策略,他們都有獨(dú)立的圖片服務(wù)器,甚至很多臺(tái)圖片服務(wù)器。這樣的架構(gòu)可以降低提供頁(yè)面訪問(wèn)請(qǐng)求的服務(wù)器系統(tǒng)壓力,并且可以保證系統(tǒng)不會(huì)因?yàn)閳D片問(wèn)題而崩潰,在應(yīng)用服務(wù)器和圖片服務(wù)器上,可以進(jìn)行不同的配置優(yōu)化,比如apache在配置ContentType的時(shí)候可以盡量少支持,盡可能少的LoadModule,保證更高的系統(tǒng)消耗和執(zhí)行效率。
3、數(shù)據(jù)庫(kù)集群和庫(kù)表散列
大型網(wǎng)站都有復(fù)雜的應(yīng)用,這些應(yīng)用必須使用數(shù)據(jù)庫(kù),那么在面對(duì)大量訪問(wèn)的時(shí)候,數(shù)據(jù)庫(kù)的瓶頸很快就能顯現(xiàn)出來(lái),這時(shí)一臺(tái)數(shù)據(jù)庫(kù)將很快無(wú)法滿(mǎn)足應(yīng)用,于是我們需要使用數(shù)據(jù)庫(kù)集群或者庫(kù)表散列。
在數(shù)據(jù)庫(kù)集群方面,很多數(shù)據(jù)庫(kù)都有自己的解決方案,Oracle、Sybase等都有很好的方案,常用的MySQL提供的Master/Slave也是類(lèi)似的方案,您使用了什么樣的DB,就參考相應(yīng)的解決方案來(lái)實(shí)施即可。
上面提到的數(shù)據(jù)庫(kù)集群由于在架構(gòu)、成本、擴(kuò)張性方面都會(huì)受到所采用DB類(lèi)型的限制,于是我們需要從應(yīng)用程序的角度來(lái)考慮改善系統(tǒng)架構(gòu),庫(kù)表散列是常用并且最有效的解決方案。我們?cè)趹?yīng)用程序中安裝業(yè)務(wù)和應(yīng)用或者功能模塊將數(shù)據(jù)庫(kù)進(jìn)行分離,不同的模塊對(duì)應(yīng)不同的數(shù)據(jù)庫(kù)或者表,再按照一定的策略對(duì)某個(gè)頁(yè)面或者功能進(jìn)行更小的數(shù)據(jù)庫(kù)散列,比如用戶(hù)表,按照用戶(hù)ID進(jìn)行表散列,這樣就能夠低成本的提升系統(tǒng)的性能并且有很好的擴(kuò)展性。sohu的論壇就是采用了這樣的架構(gòu),將論壇的用戶(hù)、設(shè)置、帖子等信息進(jìn)行數(shù)據(jù)庫(kù)分離,然后對(duì)帖子、用戶(hù)按照板塊和ID進(jìn)行散列數(shù)據(jù)庫(kù)和表,最終可以在配置文件中進(jìn)行簡(jiǎn)單的配置便能讓系統(tǒng)隨時(shí)增加一臺(tái)低成本的數(shù)據(jù)庫(kù)進(jìn)來(lái)補(bǔ)充系統(tǒng)性能。
4、緩存
緩存一詞搞技術(shù)的都接觸過(guò),很多地方用到緩存。網(wǎng)站架構(gòu)和網(wǎng)站開(kāi)發(fā)中的緩存也是非常重要。這里先講述最基本的兩種緩存。高級(jí)和分布式的緩存在后面講述。
架構(gòu)方面的緩存,對(duì)Apache比較熟悉的人都能知道Apache提供了自己的緩存模塊,也可以使用外加的Squid模塊進(jìn)行緩存,這兩種方式均可以有效的提高Apache的訪問(wèn)響應(yīng)能力。
網(wǎng)站程序開(kāi)發(fā)方面的緩存,Linux上提供的Memory Cache是常用的緩存接口,可以在web開(kāi)發(fā)中使用,比如用Java開(kāi)發(fā)的時(shí)候就可以調(diào)用MemoryCache對(duì)一些數(shù)據(jù)進(jìn)行緩存和通訊共享,一些大型社區(qū)使用了這樣的架構(gòu)。另外,在使用web語(yǔ)言開(kāi)發(fā)的時(shí)候,各種語(yǔ)言基本都有自己的緩存模塊和方法,PHP有Pear的Cache模塊,Java就更多了,.net不是很熟悉,相信也肯定有。
5、鏡像
鏡像是大型網(wǎng)站常采用的提高性能和數(shù)據(jù)安全性的方式,鏡像的技術(shù)可以解決不同網(wǎng)絡(luò)接入商和地域帶來(lái)的用戶(hù)訪問(wèn)速度差異,比如ChinaNet和EduNet之間的差異就促使了很多網(wǎng)站在教育網(wǎng)內(nèi)搭建鏡像站點(diǎn),數(shù)據(jù)進(jìn)行定時(shí)更新或者實(shí)時(shí)更新。在鏡像的細(xì)節(jié)技術(shù)方面,這里不闡述太深,有很多專(zhuān)業(yè)的現(xiàn)成的解決架構(gòu)和產(chǎn)品可選。也有廉價(jià)的通過(guò)軟件實(shí)現(xiàn)的思路,比如Linux上的rsync等工具。
6、負(fù)載均衡
負(fù)載均衡將是大型網(wǎng)站解決高負(fù)荷訪問(wèn)和大量并發(fā)請(qǐng)求采用的終極解決辦法。
負(fù)載均衡技術(shù)發(fā)展了多年,有很多專(zhuān)業(yè)的服務(wù)提供商和產(chǎn)品可以選擇,我個(gè)人接觸過(guò)一些解決方法,其中有兩個(gè)架構(gòu)可以給大家做參考。
1)硬件四層交換
第四層交換使用第三層和第四層信息包的報(bào)頭信息,根據(jù)應(yīng)用區(qū)間識(shí)別業(yè)務(wù)流,將整個(gè)區(qū)間段的業(yè)務(wù)流分配到合適的應(yīng)用服務(wù)器進(jìn)行處理。 第四層交換功能就象是虛IP,指向物理服務(wù)器。它傳輸?shù)臉I(yè)務(wù)服從的協(xié)議多種多樣,有HTTP、FTP、NFS、Telnet或其他協(xié)議。這些業(yè)務(wù)在物理服務(wù)器基礎(chǔ)上,需要復(fù)雜的載量平衡算法。在IP世界,業(yè)務(wù)類(lèi)型由終端TCP或UDP端口地址來(lái)決定,在第四層交換中的應(yīng)用區(qū)間則由源端和終端IP地址、TCP和UDP端口共同決定。
在硬件四層交換產(chǎn)品領(lǐng)域,有一些知名的產(chǎn)品可以選擇,比如Alteon、F5等,這些產(chǎn)品很昂貴,但是物有所值,能夠提供非常優(yōu)秀的性能和很靈活的管理能力。Yahoo中國(guó)當(dāng)初接近2000臺(tái)服務(wù)器使用了三四臺(tái)Alteon就搞定了。
2)軟件四層交換
大家知道了硬件四層交換機(jī)的原理后,基于OSI模型來(lái)實(shí)現(xiàn)的軟件四層交換也就應(yīng)運(yùn)而生,這樣的解決方案實(shí)現(xiàn)的原理一致,不過(guò)性能稍差。但是滿(mǎn)足一定量的壓力還是游刃有余的,有人說(shuō)軟件實(shí)現(xiàn)方式其實(shí)更靈活,處理能力完全看你配置的熟悉能力。
軟件四層交換我們可以使用Linux上常用的LVS來(lái)解決,LVS就是Linux Virtual Server,他提供了基于心跳線heartbeat的實(shí)時(shí)災(zāi)難應(yīng)對(duì)解決方案,提高系統(tǒng)的魯棒性,同時(shí)可供了靈活的虛擬VIP配置和管理功能,可以同時(shí)滿(mǎn)足多種應(yīng)用需求,這對(duì)于分布式的系統(tǒng)來(lái)說(shuō)必不可少。
一個(gè)典型的使用負(fù)載均衡的策略就是,在軟件或者硬件四層交換的基礎(chǔ)上搭建squid集群,這種思路在很多大型網(wǎng)站包括搜索引擎上被采用,這樣的架構(gòu)低成本、高性能還有很強(qiáng)的擴(kuò)張性,隨時(shí)往架構(gòu)里面增減節(jié)點(diǎn)都非常容易。這樣的架構(gòu)我準(zhǔn)備空了專(zhuān)門(mén)詳細(xì)整理一下和大家探討。
一:高并發(fā)高負(fù)載類(lèi)網(wǎng)站關(guān)注點(diǎn)之?dāng)?shù)據(jù)庫(kù)
沒(méi)錯(cuò),首先是數(shù)據(jù)庫(kù),這是大多數(shù)應(yīng)用所面臨的首個(gè)SPOF。尤其是Web2.0的應(yīng)用,數(shù)據(jù)庫(kù)的響應(yīng)是首先要解決的。
一般來(lái)說(shuō)MySQL是最常用的,可能最初是一個(gè)mysql主機(jī),當(dāng)數(shù)據(jù)增加到100萬(wàn)以上,那么,MySQL的效能急劇下降。常用的優(yōu)化措施是M-S(主-從)方式進(jìn)行同步復(fù)制,將查詢(xún)和操作和分別在不同的服務(wù)器上進(jìn)行操作。我推薦的是M-M-Slaves方式,2個(gè)主Mysql,多個(gè)Slaves,需要注意的是,雖然有2個(gè)Master,但是同時(shí)只有1個(gè)是Active,我們可以在一定時(shí)候切換。之所以用2個(gè)M,是保證M不會(huì)又成為系統(tǒng)的SPOF。
Slaves可以進(jìn)一步負(fù)載均衡,可以結(jié)合LVS,從而將select操作適當(dāng)?shù)钠胶獾讲煌膕laves上。
以上架構(gòu)可以抗衡到一定量的負(fù)載,但是隨著用戶(hù)進(jìn)一步增加,你的用戶(hù)表數(shù)據(jù)超過(guò)1千萬(wàn),這時(shí)那個(gè)M變成了SPOF。你不能任意擴(kuò)充Slaves,否則復(fù)制同步的開(kāi)銷(xiāo)將直線上升,怎么辦?我的方法是表分區(qū),從業(yè)務(wù)層面上進(jìn)行分區(qū)。最簡(jiǎn)單的,以用戶(hù)數(shù)據(jù)為例。根據(jù)一定的切分方式,比如id,切分到不同的數(shù)據(jù)庫(kù)集群去。
全局?jǐn)?shù)據(jù)庫(kù)用于meta數(shù)據(jù)的查詢(xún)。缺點(diǎn)是每次查詢(xún),會(huì)增加一次,比如你要查一個(gè)用戶(hù)nightsailer,你首先要到全局?jǐn)?shù)據(jù)庫(kù)群找到nightsailer對(duì)應(yīng)的cluster id,然后再到指定的cluster找到nightsailer的實(shí)際數(shù)據(jù)。
每個(gè)cluster可以用m-m方式,或者m-m-slaves方式。這是一個(gè)可以擴(kuò)展的結(jié)構(gòu),隨著負(fù)載的增加,你可以簡(jiǎn)單的增加新的mysql cluster進(jìn)去。
需要注意的是:
1、禁用全部auto_increment的字段
2、id需要采用通用的算法集中分配
3、要具有比較好的方法來(lái)監(jiān)控mysql主機(jī)的負(fù)載和服務(wù)的運(yùn)行狀態(tài)。如果你有30臺(tái)以上的mysql數(shù)據(jù)庫(kù)在跑就明白我的意思了。
4、不要使用持久性鏈接(不要用pconnect),相反,使用sqlrelay這種第三方的數(shù)據(jù)庫(kù)鏈接池,或者干脆自己做,因?yàn)閜hp4中mysql的鏈接池經(jīng)常出問(wèn)題。
二:高并發(fā)高負(fù)載網(wǎng)站的系統(tǒng)架構(gòu)之HTML靜態(tài)化
其實(shí)大家都知道,效率最高、消耗最小的就是純靜態(tài)化 的html頁(yè)面,所以我們盡可能使我們的網(wǎng)站上的頁(yè)面采用靜態(tài)頁(yè)面來(lái)實(shí)現(xiàn),這個(gè)最簡(jiǎn)單的方法其實(shí)也是 最有效的方法。但是對(duì)于大量?jī)?nèi)容并且頻繁更新的網(wǎng)站,我們無(wú)法全部手動(dòng)去挨個(gè)實(shí)現(xiàn),于是出現(xiàn)了我們常見(jiàn)的信息發(fā)布系統(tǒng)CMS,像我們常訪問(wèn)的各個(gè)門(mén)戶(hù)站點(diǎn) 的新聞?lì)l道,甚至他們的其他頻道,都是通過(guò)信息發(fā)布系統(tǒng)來(lái)管理和實(shí)現(xiàn)的,信息發(fā)布系統(tǒng)可以實(shí)現(xiàn)最簡(jiǎn)單的信息錄入自動(dòng)生成靜態(tài)頁(yè)面,還能具備頻道管理、權(quán)限 管理、自動(dòng)抓取等功能,對(duì)于一個(gè)大型網(wǎng)站來(lái)說(shuō),擁有一套高效、可管理的CMS是必不可少的。
除了門(mén)戶(hù)和信息發(fā)布類(lèi)型的網(wǎng)站,對(duì)于交互性要求很高的社區(qū)類(lèi)型網(wǎng)站來(lái)說(shuō),盡可能的靜態(tài)化也是提高性能的必要手段,將社區(qū)內(nèi)的帖子、文章進(jìn)行實(shí)時(shí)的靜態(tài)化,有更新的時(shí)候再重新靜態(tài)化也是大量使用的策略,像Mop的大雜燴就是使用了這樣的策略,網(wǎng)易社區(qū)等也是如此。
同時(shí),html靜態(tài)化也是某些緩存策略使用的手段,對(duì)于系統(tǒng)中頻繁使用數(shù)據(jù)庫(kù)查詢(xún)但是內(nèi)容更新很小的應(yīng)用,可以考慮使用html靜態(tài)化來(lái)實(shí)現(xiàn),比如論壇 中論壇的公用設(shè)置信息,這些信息目前的主流論壇都可以進(jìn)行后臺(tái)管理并且存儲(chǔ)再數(shù)據(jù)庫(kù)中,這些信息其實(shí)大量被前臺(tái)程序調(diào)用,但是更新頻率很小,可以考慮將這 部分內(nèi)容進(jìn)行后臺(tái)更新的時(shí)候進(jìn)行靜態(tài)化,這樣避免了大量的數(shù)據(jù)庫(kù)訪問(wèn)請(qǐng)求高并發(fā)。
網(wǎng)站HTML靜態(tài)化解決方案
當(dāng)一個(gè)Servlet資源請(qǐng)求到達(dá)WEB服務(wù)器之后我們會(huì)填充指定的JSP頁(yè)面來(lái)響應(yīng)請(qǐng)求:
HTTP請(qǐng)求---Web服務(wù)器---Servlet--業(yè)務(wù)邏輯處理--訪問(wèn)數(shù)據(jù)--填充JSP--響應(yīng)請(qǐng)求
HTML靜態(tài)化之后:
HTTP請(qǐng)求---Web服務(wù)器---Servlet--HTML--響應(yīng)請(qǐng)求
靜態(tài)訪求如下
Servlet:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if(request.getParameter("chapterId") != null){
String chapterFileName = "bookChapterRead_"+request.getParameter("chapterId")+".html";
String chapterFilePath = getServletContext().getRealPath("/") + chapterFileName;
File chapterFile = new File(chapterFilePath);
if(chapterFile.exists()){response.sendRedirect(chapterFileName);return;}//如果有這個(gè)文件就告訴瀏覽器轉(zhuǎn)向
INovelChapterBiz novelChapterBiz = new NovelChapterBizImpl();
NovelChapter novelChapter = novelChapterBiz.searchNovelChapterById(Integer.parseInt(request.getParameter("chapterId")));//章節(jié)信息
int lastPageId = novelChapterBiz.searchLastCHapterId(novelChapter.getNovelId().getId(), novelChapter.getId());
int nextPageId = novelChapterBiz.searchNextChapterId(novelChapter.getNovelId().getId(), novelChapter.getId());
request.setAttribute("novelChapter", novelChapter);
request.setAttribute("lastPageId", lastPageId);
request.setAttribute("nextPageId", nextPageId);
new CreateStaticHTMLPage().createStaticHTMLPage(request, response, getServletContext(),
chapterFileName, chapterFilePath, "/bookRead.jsp");
}
}
生成HTML靜態(tài)頁(yè)面的類(lèi):
package com.jb.y2t034.thefifth.web.servlet;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* 創(chuàng)建HTML靜態(tài)頁(yè)面
* 功能:創(chuàng)建HTML靜態(tài)頁(yè)面
* 時(shí)間:2009年1011日
* 地點(diǎn):home
* @author mavk
*
*/
public class CreateStaticHTMLPage {
/**
* 生成靜態(tài)HTML頁(yè)面的方法
* @param request 請(qǐng)求對(duì)象
* @param response 響應(yīng)對(duì)象
* @param servletContext Servlet上下文
* @param fileName 文件名稱(chēng)
* @param fileFullPath 文件完整路徑
* @param jspPath 需要生成靜態(tài)文件的JSP路徑(相對(duì)即可)
* @throws IOException
* @throws ServletException
*/
public void createStaticHTMLPage(HttpServletRequest request, HttpServletResponse response,ServletContext servletContext,String fileName,String fileFullPath,String jspPath) throws ServletException, IOException{
response.setContentType("text/html;charset=gb2312");//設(shè)置HTML結(jié)果流編碼(即HTML文件編碼)
RequestDispatcher rd = servletContext.getRequestDispatcher(jspPath);//得到JSP資源
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于從ServletOutputStream中接收資源
final ServletOutputStream servletOuputStream = new ServletOutputStream(){//用于從HttpServletResponse中接收資源
public void write(byte[] b, int off,int len){
byteArrayOutputStream.write(b, off, len);
}
public void write(int b){
byteArrayOutputStream.write(b);
}
};
final PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));//把轉(zhuǎn)換字節(jié)流轉(zhuǎn)換成字符流
HttpServletResponse httpServletResponse = new HttpServletResponseWrapper(response){//用于從response獲取結(jié)果流資源(重寫(xiě)了兩個(gè)方法)
public ServletOutputStream getOutputStream(){
return servletOuputStream;
}
public PrintWriter getWriter(){
return printWriter;
}
};
rd.include(request, httpServletResponse);//發(fā)送結(jié)果流
printWriter.flush();//刷新緩沖區(qū),把緩沖區(qū)的數(shù)據(jù)輸出
FileOutputStream fileOutputStream = new FileOutputStream(fileFullPath);
byteArrayOutputStream.writeTo(fileOutputStream);//把byteArrayOuputStream中的資源全部寫(xiě)入到fileOuputStream中
fileOutputStream.close();//關(guān)閉輸出流,并釋放相關(guān)資源
response.sendRedirect(fileName);//發(fā)送指定文件流到客戶(hù)端
}
}
實(shí)現(xiàn)方法請(qǐng)具體參照《Eclipse swt/Jface核心編程》
第21章 文本處理.
21.1 文本處理概述... 409
21.2 項(xiàng)目實(shí)戰(zhàn):JavaScript編輯器... 409
21.2.1 主窗口預(yù)覽... 409
21.2.2 項(xiàng)目文件結(jié)構(gòu)... 410
21.3 主窗口模塊... 411
21.3.1 代碼實(shí)現(xiàn)... 411
21.3.2 主窗口程序代碼分析... 414
21.3.3 啟動(dòng)主窗口程序... 416
21.4 代碼著色... 417
21.4.1 源代碼配置類(lèi)(SourceViewerConfiguration)... 417
21.4.2 基于規(guī)則的代碼掃描器類(lèi)(RuleBasedScanner)... 419
21.4.3 設(shè)置代碼掃描規(guī)則... 420
21.4.4 提取類(lèi)(Token)和文本屬性類(lèi)(TextAttribute)... 423
21.5 內(nèi)容輔助... 423
21.5.1 配置編輯器的內(nèi)容助手... 424
21.5.2 內(nèi)容輔助類(lèi)... 424
21.5.3 輔助建議類(lèi)(CompletionProposal)... 426
21.6 文檔的撤銷(xiāo)與重復(fù)... 427
21.6.1 文檔管理器對(duì)象(DefaultUndoManager)... 427
21.6.2 撤銷(xiāo)操作的實(shí)現(xiàn)... 427
21.6.3 恢復(fù)操作的實(shí)現(xiàn)... 428
21.7 查找與替換窗口... 429
21.7.1 窗口的界面設(shè)計(jì)... 429
21.7.2 查找功能的實(shí)現(xiàn)... 433
21.7.3 替換功能的實(shí)現(xiàn)... 434
21.8 首選項(xiàng)的對(duì)話(huà)框... 434
21.8.1 首選項(xiàng)頁(yè)面的代碼實(shí)現(xiàn)... 435
21.8.2 打開(kāi)首選項(xiàng)頁(yè)面的代碼... 436
21.9 文件的打開(kāi)、保存與打印... 437
21.9.1 打開(kāi)文件... 437
21.9.2 保存文件... 437
21.9.3 打印文件... 438
21.10 幫助對(duì)話(huà)框... 439
21.11 其他的一些工具類(lèi)... 440
21.11.1 事件管理類(lèi)... 440
21.11.2 資源管理類(lèi)... 441
21.11.3 程序中使用的常量... 443
21.12 本章小結(jié)... 444
看了樓上的發(fā)言,你有資格說(shuō)別人傻嗎?看清楚樓主的問(wèn)題好不好!樓主的意思是直接在java程序中配置實(shí)現(xiàn),而不是像你那么傻的手動(dòng)去實(shí)現(xiàn)!
轉(zhuǎn)載兩篇文章,結(jié)合起來(lái)可以實(shí)現(xiàn)java應(yīng)用程序開(kāi)機(jī)自動(dòng)啟動(dòng)
可能也有別的好方法,這只是其中的一種
思路就是將java應(yīng)用程序打包成.jar文件,然后轉(zhuǎn)成.exe,通過(guò)修改注冊(cè)表來(lái)增加刪除啟動(dòng)項(xiàng),即將安裝后的.exe執(zhí)行文件添加到注冊(cè)表中;
首先將java應(yīng)用程序打包成.jar文件,可以利用如下代碼找到.jar文件的絕對(duì)路徑,即也可以找到安裝后的.exe執(zhí)行文件
轉(zhuǎn)載:
對(duì)于Java程序,無(wú)論是未打包的還是打包的JAR或WAR文件,有時(shí)候都需要獲取它運(yùn)行所在目錄信息,如何做到這一點(diǎn)呢?
在Java處理的文件系統(tǒng)中,目錄的表示方式有兩種:
(1)絕對(duì)目錄,它以"/"為起始字符,代表從根目錄下開(kāi)始尋找給出的目錄,如/c:/java
(2)相對(duì)路徑,它以不帶“/”的目錄名表示,表示以當(dāng)前Java程序正在運(yùn)行的目錄作為起始目錄來(lái)尋找給出的目錄。如java/classes。在相對(duì)路徑中,有一些特定的字符,可以代表特的的目錄,比如,“.”代表當(dāng)前目錄,“..”代表當(dāng)前目錄的上一級(jí)目錄。在網(wǎng)上很多給出的例子中,就是利用"."作為目錄名,構(gòu)造File對(duì)象的實(shí)例,然后通過(guò)File對(duì)象的方法來(lái)獲取當(dāng)前程序運(yùn)行的目錄。
這種方法雖然簡(jiǎn)單,但有時(shí)不能正確的得出當(dāng)前程序的運(yùn)行目錄。原因在于,運(yùn)行Java程序不一定要進(jìn)入到該程序的類(lèi)文件或JAR文件所在的目錄,只要在運(yùn)行時(shí)指定了正確的類(lèi)路徑信息,就可以在任何目錄中運(yùn)行Java程序,此時(shí)利用這種方法只能得到發(fā)出運(yùn)行命令時(shí)所在的目錄信息。
從上面的分析可以看出,對(duì)于很多Java程序,尤其是WEB程序,利用當(dāng)前路徑的“.”表示法,都不能滿(mǎn)足要求。那么怎樣才能正確的得到運(yùn)行目錄信息呢?
在Web程序中,利用Servlet API可以獲得一些路徑信息,比如HttpServletRequest接口中定義的getRealPath方法,但類(lèi)似這些方法都依賴(lài)于Servlet環(huán)境,不便于程序的單元測(cè)試。
本文提供了一種只使用Java標(biāo)準(zhǔn)API的路徑探測(cè)方法,就是利用ClassLoader抽象類(lèi)。
利用java.lang.Class的getClassLoader方法,可以獲得給定類(lèi)的ClassLoader實(shí)例,它的getResource方法可以獲得當(dāng)前類(lèi)裝載器中的資源的位置,我們可以利用類(lèi)文件的名稱(chēng)作為要查找的資源,經(jīng)過(guò)處理后就可獲得當(dāng)前Java程序的運(yùn)行位置信息,其偽代碼如下:
獲得Class參數(shù)的所在的類(lèi)名
取得該類(lèi)所在的包名
將包名轉(zhuǎn)換為路徑
利用getResource得到當(dāng)前的類(lèi)文件所在URL
利用URL解析出當(dāng)前Java程序所在的路徑
具體代碼如下:
java代碼:
Java代碼
/**-----------------------------------------------------------------------
*getAppPath需要一個(gè)當(dāng)前程序使用的Java類(lèi)的class屬性參數(shù),它可以返回打包過(guò)的
*Java可執(zhí)行文件(jar,war)所處的系統(tǒng)目錄名或非打包Java程序所處的目錄
*@param cls為Class類(lèi)型
*@return 返回值為該類(lèi)所在的Java程序運(yùn)行的目錄
-------------------------------------------------------------------------*/
public static String getAppPath(Class cls){
//檢查用戶(hù)傳入的參數(shù)是否為空
if(cls==null)
throw new java.lang.IllegalArgumentException("參數(shù)不能為空!");
ClassLoader loader=cls.getClassLoader();
//獲得類(lèi)的全名,包括包名
String clsName=cls.getName()+".class";
//獲得傳入?yún)?shù)所在的包
Package pack=cls.getPackage();
String path="";
//如果不是匿名包,將包名轉(zhuǎn)化為路徑
if(pack!=null){
String packName=pack.getName();
//此處簡(jiǎn)單判定是否是Java基礎(chǔ)類(lèi)庫(kù),防止用戶(hù)傳入JDK內(nèi)置的類(lèi)庫(kù)
if(packName.startsWith("java.")||packName.startsWith("javax."))
throw new java.lang.IllegalArgumentException("不要傳送系統(tǒng)類(lèi)!");
//在類(lèi)的名稱(chēng)中,去掉包名的部分,獲得類(lèi)的文件名
clsName=clsName.substring(packName.length()+1);
//判定包名是否是簡(jiǎn)單包名,如果是,則直接將包名轉(zhuǎn)換為路徑,
if(packName.indexOf(".")0) path=packName+"/";
else{//否則按照包名的組成部分,將包名轉(zhuǎn)換為路徑
int start=0,end=0;
end=packName.indexOf(".");
while(end!=-1){
path=path+packName.substring(start,end)+"/";
start=end+1;
end=packName.indexOf(".",start);
}
path=path+packName.substring(start)+"/";
}
}
//調(diào)用ClassLoader的getResource方法,傳入包含路徑信息的類(lèi)文件名
java.net.URL url =loader.getResource(path+clsName);
//從URL對(duì)象中獲取路徑信息
String realPath=url.getPath();
//去掉路徑信息中的協(xié)議名"file:"
int pos=realPath.indexOf("file:");
if(pos-1) realPath=realPath.substring(pos+5);
//去掉路徑信息最后包含類(lèi)文件信息的部分,得到類(lèi)所在的路徑
pos=realPath.indexOf(path+clsName);
realPath=realPath.substring(0,pos-1);
//如果類(lèi)文件被打包到JAR等文件中時(shí),去掉對(duì)應(yīng)的JAR等打包文件名
if(realPath.endsWith("!"))
realPath=realPath.substring(0,realPath.lastIndexOf("/"));
/*------------------------------------------------------------
ClassLoader的getResource方法使用了utf-8對(duì)路徑信息進(jìn)行了編碼,當(dāng)路徑
中存在中文和空格時(shí),他會(huì)對(duì)這些字符進(jìn)行轉(zhuǎn)換,這樣,得到的往往不是我們想要
的真實(shí)路徑,在此,調(diào)用了URLDecoder的decode方法進(jìn)行解碼,以便得到原始的
中文及空格路徑
-------------------------------------------------------------*/
try{
realPath=java.net.URLDecoder.decode(realPath,"utf-8");
}catch(Exception e){throw new RuntimeException(e);}
return realPath;
}//getAppPath定義結(jié)束
//-----------------------------------------------------------------
該方法既可以用于JAR或WAR文件,也可以用于非JAR文件。但要注意以下2點(diǎn):
不要傳遞系統(tǒng)的類(lèi),作為getAppPath的參數(shù),如java.lang.String.class,當(dāng)然,也不要傳遞那些已經(jīng)位于JDK中的那些類(lèi),比如xml相關(guān)的一些類(lèi)等等。
要傳遞應(yīng)該是程序中主要的運(yùn)行類(lèi),不要傳遞程序中的支持類(lèi)庫(kù)中的類(lèi)文件,也就是那些第三方的類(lèi)庫(kù)中的類(lèi)文件,否則得到的將是那些類(lèi)庫(kù)的位置。
然后可以通過(guò)修改注冊(cè)表來(lái)增加開(kāi)機(jī)啟動(dòng)項(xiàng):
轉(zhuǎn)載:
需要修改的注冊(cè)表項(xiàng)
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run] 開(kāi)機(jī)自動(dòng)運(yùn)行程序
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce] 開(kāi)機(jī)自動(dòng)運(yùn)行程序 且 僅運(yùn)行一次
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices] 開(kāi)機(jī)自動(dòng)運(yùn)行服務(wù)
JDK 從1.4開(kāi)始提供操作 Windows 的 API 是 Preferences,因?yàn)檫@個(gè) API 也是跨平臺(tái)的,所功能比較弱,在 Win32 下只能用來(lái)操作 HKCU\Software\JavaSoft 和 HKLM\Software\JavaSoft 下及子節(jié)點(diǎn)的數(shù)據(jù)。
自由訪問(wèn)注冊(cè)表其他鍵的值光用 Java 是做不到的,必然方案就是 JNI,這里我使用的是Windows Registry API Native Interface 下的 registry-3.1.3.zip(包含源代碼)。可以利用它訪問(wèn)、修改、導(dǎo)出注冊(cè)表項(xiàng)到文件等。解開(kāi) registry-3.1.3.zip,在 bin 目錄中可以看到兩個(gè)文件 ICE_JNIRegistry.dll 和 registry.jar,動(dòng)態(tài)庫(kù)就是本地代碼實(shí)現(xiàn)。
com.ice.jni.registry.Registry.main() 就是 registry 的示例代碼,動(dòng)態(tài)庫(kù) ICE_JNIRegistry.dll 也是在這個(gè)類(lèi)的靜態(tài)塊中被加載的,記得要把 ICE_JNIRegistry.dll 放在它能夠被加載的位置上,比如你把 registry-3.1.3.zip 解壓到 c:\registry-3.1.3,在命令行下你可以進(jìn)入到這個(gè)目錄中,并執(zhí)行。
代碼:
Java代碼
package org.zh.ss.util;
import com.ice.jni.registry.*;
import java.text.SimpleDateFormat;
/** *//**
* java 操作注冊(cè)表
* @author 李志遠(yuǎn)
*/
public class RegeditTool {
static SimpleDateFormat shortDateFormat = new SimpleDateFormat("yyyy-MM-dd");
/** *//** */
/** *//** Creates a new instance of test */
// 把信息存儲(chǔ)到注冊(cè)表HKEY_LOCAL_MACHINE下的某個(gè)節(jié)點(diǎn)的某一變量中,有則修改,無(wú)則創(chuàng)建
public static boolean setValue(String folder, String subKeyNode,
String subKeyName, String subKeyValue) {
try {
RegistryKey software = Registry.HKEY_LOCAL_MACHINE
.openSubKey(folder);
RegistryKey subKey = software.createSubKey(subKeyNode, "");
subKey
.setValue(new RegStringValue(subKey, subKeyName,
subKeyValue));
subKey.closeKey();
return true;
} catch (NoSuchKeyException e) {
e.printStackTrace();
} catch (NoSuchValueException e) {
e.printStackTrace();
} catch (RegistryException e) {
e.printStackTrace();
}
return false;
}
// 刪除注冊(cè)表中某節(jié)點(diǎn)下的某個(gè)變量
public static boolean deleteValue(String folder, String subKeyNode,
String subKeyName) {
try {
RegistryKey software = Registry.HKEY_LOCAL_MACHINE
.openSubKey(folder);
RegistryKey subKey = software.createSubKey(subKeyNode, "");
subKey.deleteValue(subKeyName);
subKey.closeKey();
return true;
} catch (NoSuchKeyException e) {
System.out.println("NOsuchKey_delete");
} catch (NoSuchValueException e) {
System.out.println("NOsuchValue_delete");
} catch (RegistryException e) {
e.printStackTrace();
}
return false;
}
// 刪除注冊(cè)表中某節(jié)點(diǎn)下的某節(jié)點(diǎn)
public static boolean deleteSubKey(String folder, String subKeyNode) {
try {
RegistryKey software = Registry.HKEY_LOCAL_MACHINE
.openSubKey(folder);
software.deleteSubKey(subKeyNode);
software.closeKey();
return true;
} catch (NoSuchKeyException e) {
e.printStackTrace();
} catch (RegistryException e) {
e.printStackTrace();
}
return false;
}
// 打開(kāi)注冊(cè)表項(xiàng)并讀出相應(yīng)的變量名的值
public static String getValue(String folder, String subKeyNode,
String subKeyName) {
String value = "";
try {
RegistryKey software = Registry.HKEY_LOCAL_MACHINE
.openSubKey(folder);
RegistryKey subKey = software.openSubKey(subKeyNode);
value = subKey.getStringValue(subKeyName);
subKey.closeKey();
} catch (NoSuchKeyException e) {
value = "NoSuchKey";
// e.printStackTrace();
} catch (NoSuchValueException e) {
value = "NoSuchValue";
// e.printStackTrace();
} catch (RegistryException e) {
e.printStackTrace();
}
return value;
}
// 測(cè)試
public static void main(String[] args) {
setValue("SOFTWARE", "Microsoft\\Windows\\CurrentVersion\\Run", "test",
"C:\\1.exe");
}
}
這樣,就可以在軟件中添加,刪除,修改開(kāi)機(jī)啟動(dòng)項(xiàng)了
第一種實(shí)現(xiàn):繼承MethodFilterInterceptor
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;public class UserCheck2Interceptor extends MethodFilterInterceptor { public UserCheck2Interceptor()
{ //過(guò)濾不攔截的方法
this.setExcludeMethods("check");//如果是多個(gè)的方法可以用逗號(hào)隔開(kāi)
}
@Override
protected String doIntercept(ActionInvocation arg0) throws Exception
{ //防止不登陸地址訪問(wèn)
Userinfo user=(Userinfo)arg0.getInvocationContext().getSession().get("user");
String result="login";
if(user!=null)
{
//action的方法的結(jié)果
result=arg0.invoke();//調(diào)用action的具體方法
}
System.out.println("用戶(hù)校驗(yàn)!");
return result;
}}第二種實(shí)現(xiàn):實(shí)現(xiàn)接口Interceptor MethodFilterInterceptor 也實(shí)現(xiàn)了Interceptor 接口import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;public class UserCheckInterceptor implements Interceptor { public UserCheckInterceptor()
{
}
public void destroy()
{
}
public void init()
{
}
/*
* 實(shí)現(xiàn)攔截的功能(在調(diào)用action之前執(zhí)行該攔截器,對(duì)用戶(hù)的合法性進(jìn)行校驗(yàn),如果不合法直接轉(zhuǎn)到login.jsp,如果合法登入則繼續(xù)運(yùn)行)
*/
public String intercept(ActionInvocation arg0) throws Exception
{
Userinfo user=(Userinfo)arg0.getInvocationContext().getSession().get("user");
String result="login";
if(user!=null)
{
//action的方法的結(jié)果
result=arg0.invoke();//調(diào)用action的具體方法
}
System.out.println("用戶(hù)校驗(yàn)!");
return result;
}}
最后配置struts.xml?xml version="1.0" encoding="UTF-8"?
!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
" "
struts!--修改struts.xml 文件 是否會(huì)重新加載--
constant name="struts.configuration.xml.reload" value="true"/constant
constant name="struts.i18n.encoding" value="GBK"/constant
constant name="struts.custom.i18n.resources" value="globalMsg"/constant
!--自定義包作為默認(rèn)的--
package name="test" extends="struts-default"
interceptors !-- 自定義攔截器 -- !-- 實(shí)現(xiàn)Interceptor 接口的攔截器 --
interceptor name="usercheckInterceptor" class="test.interceptors.UserCheckInterceptor"/interceptor !-- 繼承MethodFilterInterceptor 的攔截器 --
interceptor name="usercheck2Interceptor" class="test.interceptors.UserCheck2Interceptor"/interceptor
interceptor-stack name="itfutureStack" !-- 使用usercheck2Interceptor攔截器 --
interceptor-ref name="usercheck2Interceptor"/interceptor-ref !-- 注意要引用原先struts2默認(rèn)的17個(gè)攔截器 --
interceptor-ref name="defaultStack"/interceptor-ref
/interceptor-stack
/interceptors !-- 設(shè)置此命名空間的默認(rèn)攔截器為用戶(hù)自定義的test攔截器 --
default-interceptor-ref name="test"/default-interceptor-ref
/package
/struts
使用的時(shí)候就直接繼承test命名空間就好了,建議使用繼承MethodFilterInterceptor 的攔截器,它實(shí)現(xiàn)了對(duì)不必要進(jìn)行攔截的方法的過(guò)濾.如登陸的方法