【轉(zhuǎn)】Java服務(wù)器熱部署的實現(xiàn)原理

轉(zhuǎn)自:http://blog.csdn.net/chenjie19891104/article/details/42807959

在web應(yīng)用開發(fā)或者游戲服務(wù)器開發(fā)的過程中命辖,我們時時刻刻都在使用熱部署况毅。熱部署的目的很簡單,就是為了節(jié)省應(yīng)用開發(fā)和發(fā)布的時間尔艇。比如尔许,我們在使用Tomcat或者Jboss等應(yīng)用服務(wù)器開發(fā)應(yīng)用時,我們經(jīng)常會開啟熱部署功能终娃。熱部署味廊,簡單點來說,就是我們將打包好的應(yīng)用直接替換掉原有的應(yīng)用棠耕,不用關(guān)閉或者重啟服務(wù)器余佛,一切就是這么簡單。那么窍荧,熱部署到底是如何實現(xiàn)的呢辉巡?在本文中,我將寫一個實例搅荞,這個實例就是一個容器應(yīng)用红氯,允許用戶發(fā)布自己的應(yīng)用框咙,同時支持熱部署。

在Java中痢甘,要實現(xiàn)熱部署喇嘱,首先,你得明白塞栅,Java中類的加載方式者铜。每一個應(yīng)用程序的類都會被ClassLoader加載,所以放椰,要實現(xiàn)一個支持熱部署的應(yīng)用作烟,我們可以對每一個用戶自定義的應(yīng)用程序使用一個單獨的ClassLoader進(jìn)行加載。然后砾医,當(dāng)某個用戶自定義的應(yīng)用程序發(fā)生變化的時候拿撩,我們首先銷毀原來的應(yīng)用,然后使用一個新的ClassLoader來加載改變之后的應(yīng)用如蚜。而所有其他的應(yīng)用程序不會受到一點干擾压恒。先看一下,該應(yīng)用的設(shè)計圖:

有了總體實現(xiàn)思路之后错邦,我們可以想到如下幾個需要完成的目標(biāo):

  • 1探赫、定義一個用戶自定義應(yīng)用程序的接口,這是因為撬呢,我們需要在容器應(yīng)用中去加載用戶自定義的應(yīng)用程序伦吠。
  • 2、我們還需要一個配置文件魂拦,讓用戶去配置他們的應(yīng)用程序毛仪。
  • 3、應(yīng)用啟動的時候晨另,加載所有已有的用戶自定義應(yīng)用程序潭千。
  • 4谱姓、為了支持熱部署借尿,我們需要一個監(jiān)聽器,來監(jiān)聽?wèi)?yīng)用發(fā)布目錄中每個文件的變動屉来。這樣路翻,當(dāng)某個應(yīng)用重新部署之后,我們就可以得到通知茄靠,進(jìn)而進(jìn)行熱部署處理茂契。

實現(xiàn)部分:

首先,我們定義一個接口慨绳,每一個用戶自定義的程序中都必須包含唯一一個實現(xiàn)了該接口的類掉冶。代碼如下:

public interface IApplication {  
  
        public void init();  
         
        public void execute();  
         
        public void destory();  
         
}  

在這個例子中真竖,每一個用戶自定義的應(yīng)用程序,都必須首先打包成一個jar文件厌小,然后發(fā)布到一個指定的目錄恢共,按照指定的格式,然后首次發(fā)布的時候璧亚,還需要將應(yīng)用的配置添加到配置文件中讨韭。所以,首先癣蟋,我們需要定義一個可以加載指定目錄jar文件的類:

 public ClassLoader createClassLoader(ClassLoader parentClassLoader, String... folders) {  
  
       List<URL> jarsToLoad = new ArrayList<URL>();  
        for (String folder : folders) {  
              List<String> jarPaths = scanJarFiles(folder);  
  
               for (String jar : jarPaths) {  
  
                     try {  
                           File file = new File(jar);  
                           jarsToLoad.add(file.toURI().toURL());  
  
                    } catch (MalformedURLException e) {  
                           e.printStackTrace();  
                    }  
              }  
       }  
  
       URL[] urls = new URL[jarsToLoad.size()];  
       jarsToLoad.toArray(urls);  
  
        return new URLClassLoader(urls, parentClassLoader);  
}  

這個方法很簡單透硝,就是從多個目錄中掃描jar文件,然后返回一個新的URLClassLoader實例疯搅。至于scanJarFiles方法濒生,你可以隨后下載本文的源碼。然后幔欧,我們需要定義一個配置文件甜攀,用戶需要將他們自定義的應(yīng)用程序信息配置在這里,這樣琐馆,該容器應(yīng)用隨后就根據(jù)這個配置文件來加載所有的應(yīng)用程序:

<apps>  
        <app>  
               <name> TestApplication1</name >  
               <file> com.ijavaboy.app.TestApplication1</file >  
        </app>  
        <app>  
               <name> TestApplication2</name >  
               <file> com.ijavaboy.app.TestApplication2</file >  
        </app>  
</apps>  

這個配置是XML格式的规阀,每一個app標(biāo)簽就表示一個應(yīng)用程序,每一個應(yīng)用程序瘦麸,需要配置名稱和那個實現(xiàn)了IApplication接口的類的完整路徑和名稱谁撼。

有了這個配置文件,我們需要對其進(jìn)行解析滋饲,在這個例子中厉碟,我使用的是xstream,很簡單屠缭,你可以下載源碼箍鼓,然后看看就知道了。這里略過呵曹。這里需要提一下:每個應(yīng)用的名稱(name),是至關(guān)重要的款咖,因為該例子中,我們的發(fā)布目錄是整個項目發(fā)布目錄下的applications目錄奄喂,這是所有用戶自定義應(yīng)用程序發(fā)布的目錄铐殃。而用戶發(fā)布一個應(yīng)用程序,需要首先在該目錄下新建一個和這里配置的name一樣名稱的文件夾跨新,然后將打包好的應(yīng)用發(fā)布到該文件夾中富腊。(你必須這樣做,否則在這個例子中域帐,你會發(fā)布失敗)赘被。

好了是整,現(xiàn)在加載jar的方法和配置都有了,下面將是整個例子的核心部分民假,對贰盗,就是應(yīng)用程序管理類,這個類就是要完成對每一個用戶自定義應(yīng)用程序的管理和維護(hù)阳欲。首先要做的舵盈,就是如何加載一個應(yīng)用程序:

public void createApplication(String basePath, AppConfig config){  
      String folderName = basePath + GlobalSetting. JAR_FOLDER + config.getName();  
      ClassLoader loader = this.jarLoader .createClassLoader(ApplicationManager. class.getClassLoader(), folderName);  
        
       try {  
             Class<?> appClass = loader. loadClass(config.getFile());  
               
             IApplication app = (IApplication)appClass.newInstance();  
               
             app.init();  
               
              this.apps .put(config.getName(), app);  
               
      } catch (ClassNotFoundException e) {  
             e.printStackTrace();  
      } catch (InstantiationException e) {  
             e.printStackTrace();  
      } catch (IllegalAccessException e) {  
             e.printStackTrace();  
      }  

可以看到,這個方法接收兩個參數(shù)球化,一個是基本路徑秽晚,一個是應(yīng)用程序配置⊥灿蓿基本路徑其實就是項目發(fā)布目錄的地址赴蝇,而AppConfig其實就是配置文件中app標(biāo)簽的一個實體映射,這個方法從指定的配置目錄中加載指定的類巢掺,然后調(diào)用該應(yīng)用的init方法句伶,完成用戶自定義應(yīng)用程序的初始化。最后將陆淀,該加載的應(yīng)用放入內(nèi)存中考余。

現(xiàn)在,所有的準(zhǔn)備工作轧苫,都已經(jīng)完成了楚堤。接下來,在整個應(yīng)用程序啟動的時候含懊,我們需要加載所有的用戶自定義應(yīng)用程序身冬,所以,我們在ApplicationManager中添加一個方法:

 public void loadAllApplications(String basePath){  
         
        for(AppConfig config : this.configManager.getConfigs()){  
               this.createApplication(basePath, config);  
       }  
}  

這個方法岔乔,就是將用戶配置的所有應(yīng)用程序加載到該容器應(yīng)用中來酥筝。好了,現(xiàn)在我們是不是需要寫兩個獨立的應(yīng)用程序試試效果了雏门,要寫這個應(yīng)用程序嘿歌,首先我們新建一個java應(yīng)用程序,然后引用這個例子項目剿配,或者將該例子項目打包成一個jar文件搅幅,然后引用到這個獨立的應(yīng)用中來,因為這個獨立的應(yīng)用程序中呼胚,必須要包含一個實現(xiàn)了IApplication接口的類。我們來看看這個例子包含的一個獨立應(yīng)用的樣子:

public class TestApplication1 implements IApplication{  
  
        @Override  
        public void init() {  
              System. out.println("TestApplication1-->init" );  
       }  
  
        @Override  
        public void execute() {  
              System. out.println("TestApplication1-->do something" );  
       }  
  
        @Override  
        public void destory() {  
              System. out.println("TestApplication1-->destoryed" );  
       }  
  
}  

是不是很簡單息裸?對蝇更,就是這么簡單沪编。你可以照這個樣子,再寫一個獨立應(yīng)用年扩。接下來蚁廓,你還需要在applications.xml中進(jìn)行配置,很簡單厨幻,就是在apps標(biāo)簽中增加如下代碼:

<app>  
       <name> TestApplication1</name >  
       <file> com.ijavaboy.app.TestApplication1</file >  
</app>  

接下來相嵌,進(jìn)入到本文的核心部分了,接下來我們的任務(wù)况脆,就全部集中在熱部署上了饭宾,其實,也許現(xiàn)在你還覺得熱部署很神秘格了,但是看铆,我相信一分鐘之后,你就不會這么想了盛末。要實現(xiàn)熱部署弹惦,我們之前說過,需要一個監(jiān)聽器悄但,來監(jiān)聽發(fā)布目錄applications,這樣當(dāng)某個應(yīng)用程序的jar文件改變時棠隐,我們可以進(jìn)行熱部署處理。其實檐嚣,要實現(xiàn)目錄文件改變的監(jiān)聽宵荒,有很多種方法,這個例子中我使用的是apache的一個開源虛擬文件系統(tǒng)——common-vfs净嘀。如果你對其感興趣报咳,你可以訪問http://commons.apache.org/proper/commons-vfs/。這里挖藏,我們繼承其FileListener接口暑刃,實現(xiàn)fileChanged 即可:

public void fileChanged (FileChangeEvent event) throws Exception {  
  
      String ext = event.getFile().getName().getExtension();  
      if(!"jar" .equalsIgnoreCase(ext)){  
              return;  
      }  
        
      String name = event.getFile().getName().getParent().getBaseName();  
        
      ApplicationManager. getInstance().reloadApplication(name);  
}        

當(dāng)某個文件改變的時候,該方法會被回調(diào)膜眠。所以岩臣,我們在這個方法中調(diào)用了ApplicationManager的reloadApplication方法,重現(xiàn)加載該應(yīng)用程序宵膨。

public void reloadApplication (String name){  
      IApplication oldApp = this.apps .remove(name);  
        
       if(oldApp == null){  
              return;  
      }  
        
      oldApp.destory();     //call the destroy method in the user's application  
        
      AppConfig config = this.configManager .getConfig(name);  
       if(config == null){  
              return;  
      }  
        
      createApplication(getBasePath(), config);  
}

重現(xiàn)加載應(yīng)用程序時架谎,我們首先從內(nèi)存中刪除該應(yīng)用程序,然后調(diào)用原來應(yīng)用程序的destory方法辟躏,最后按照配置重新創(chuàng)建該應(yīng)用程序?qū)嵗?/p>

到這里谷扣,你還覺得熱部署很玄妙很高深嗎?一切就是如此簡單。好了会涎,言歸正傳裹匙,為了讓我們自定義的監(jiān)聽接口可以有效工作起來,我們還需要指定它要監(jiān)聽的目錄:

 public void initMonitorForChange(String basePath){  
        try {  
               this.fileManager = VFS.getManager();  
                
              File file = new File(basePath + GlobalSetting.JAR_FOLDER);  
              FileObject monitoredDir = this.fileManager .resolveFile(file.getAbsolutePath());  
              FileListener fileMonitorListener = new JarFileChangeListener();  
               this.fileMonitor = new DefaultFileMonitor(fileMonitorListener);  
               this.fileMonitor .setRecursive(true);  
               this.fileMonitor .addFile(monitoredDir);  
               this.fileMonitor .start();  
              System. out.println("Now to listen " + monitoredDir.getName().getPath());  
                
       } catch (FileSystemException e) {  
              e.printStackTrace();  
       }  
}  

這里末秃,就是初始化監(jiān)聽器的地方概页,我們使用VFS的DefaultFileMonitor完成監(jiān)聽。而監(jiān)聽的目錄练慕,就是應(yīng)用發(fā)布目錄applications惰匙。接下來,為了讓整個應(yīng)用程序可以持續(xù)的運(yùn)行而不會結(jié)束铃将,我們修改下啟動方法:

 public static void main(String[] args){  
         
       Thread t = new Thread(new Runnable() {  
                
               @Override  
               public void run() {  
                    ApplicationManager manager = ApplicationManager.getInstance();  
                    manager.init();  
              }  
       });  
         
       t.start();  
         
        while(true ){  
               try {  
                    Thread. sleep(300);  
              } catch (InterruptedException e) {  
                    e.printStackTrace();  
              }  
       }  
}  

好了项鬼,到這里,一切都要結(jié)束了◆锶現(xiàn)在秃臣,你已經(jīng)很明白熱部署是怎么一回事了,對嗎哪工?不明白奥此?OK,還有最后一招雁比,去看看源碼吧稚虎!

源碼我已經(jīng)放到了GitHub上面了,地址:https://github.com/chenjie19891104/ijavaboy/tree/master/AppLoader偎捎,歡迎下載使用蠢终,你擁有一切的權(quán)利對其進(jìn)行修改。

最后茴她,如果本文有什么地方說的不準(zhǔn)確寻拂,歡迎指正,謝謝丈牢!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祭钉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子己沛,更是在濱河造成了極大的恐慌慌核,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件申尼,死亡現(xiàn)場離奇詭異垮卓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)师幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門粟按,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事钾怔〖詈簦” “怎么了蒙挑?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵宗侦,是天一觀的道長。 經(jīng)常有香客問我忆蚀,道長矾利,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任馋袜,我火速辦了婚禮男旗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欣鳖。我一直安慰自己察皇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布泽台。 她就那樣靜靜地躺著什荣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怀酷。 梳的紋絲不亂的頭發(fā)上稻爬,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音蜕依,去河邊找鬼桅锄。 笑死,一個胖子當(dāng)著我的面吹牛样眠,可吹牛的內(nèi)容都是我干的友瘤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼檐束,長吁一口氣:“原來是場噩夢啊……” “哼辫秧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厢塘,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤茶没,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晚碾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抓半,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年格嘁,在試婚紗的時候發(fā)現(xiàn)自己被綠了笛求。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖探入,靈堂內(nèi)的尸體忽然破棺而出狡孔,到底是詐尸還是另有隱情,我是刑警寧澤蜂嗽,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布苗膝,位于F島的核電站,受9級特大地震影響植旧,放射性物質(zhì)發(fā)生泄漏辱揭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一病附、第九天 我趴在偏房一處隱蔽的房頂上張望问窃。 院中可真熱鬧,春花似錦完沪、人聲如沸域庇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽听皿。三九已至,卻和暖如春技健,著一層夾襖步出監(jiān)牢的瞬間写穴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工雌贱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留啊送,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓欣孤,卻偏偏與公主長得像馋没,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子降传,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理篷朵,服務(wù)發(fā)現(xiàn),斷路器婆排,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • 今天發(fā)現(xiàn)早年在大象筆記中寫的一篇筆記声旺,之前放在ijavaboy上的,現(xiàn)在它已經(jīng)訪問不了了段只。前幾天又有同事在討論這個...
    java大哥閱讀 690評論 0 0
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,803評論 6 342
  • 不要跟自己說坪创,這么難,我不可能做到的之類消極的話姐赡。 因為當(dāng)你說不可能的時候莱预,你大腦就自動放棄了思考。 問自己:我要...
    泉布閱讀 447評論 0 2