java 中 熱部署與卸載關係

今天發現早年在大象筆記中寫的一篇筆記,以前放在ijavaboy上的,如今它已經訪問不了了。前幾天又有同事在討論這個問題。這裏拿來分享一下。html

 

在web應用開發或者遊戲服務器開發的過程當中,咱們時時刻刻都在使用熱部署。熱部署的目的很簡單,就是爲了節省應用開發和發佈的時間。好比,咱們在使用Tomcat或者Jboss等應用服務器開發應用時,咱們常常會開啓熱部署功能。熱部署,簡單點來講,就是咱們將打包好的應用直接替換掉原有的應用,不用關閉或者重啓服務器,一切就是這麼簡單。那麼,熱部署究竟是如何實現的呢?在本文中,我將寫一個實例,這個實例就是一個容器應用,容許用戶發佈本身的應用,同時支持熱部署。java

 
在Java中,要實現熱部署,首先,你得明白,Java中類的加載方式。每個應用程序的類都會被ClassLoader加載,因此,要實現一個支持熱部署的應用,咱們能夠對每個用戶自定義的應用程序使用一個單獨的ClassLoader進行加載。而後,當某個用戶自定義的應用程序發生變化的時候,咱們首先銷燬原來的應用,而後使用一個新的ClassLoader來加載改變以後的應用。而全部其餘的應用程序不會受到一點干擾。先看一下,該應用的設計圖:
 
 
 
有了整體實現思路以後,咱們能夠想到以下幾個須要完成的目標:
 
一、定義一個用戶自定義應用程序的接口,這是由於,咱們須要在容器應用中去加載用戶自定義的應用程序。
二、咱們還須要一個配置文件,讓用戶去配置他們的應用程序。
三、應用啓動的時候,加載全部已有的用戶自定義應用程序。
四、爲了支持熱部署,咱們須要一個監聽器,來監聽應用發佈目錄中每一個文件的變更。這樣,當某個應用從新部署以後,咱們就能夠獲得通知,進而進行熱部署處理。
 
實現部分:
 
首先,咱們定義一個接口,每個用戶自定義的程序中都必須包含惟一一個實現了該接口的類。代碼以下:
[java]  view plain  copy
 
  1. public interface IApplication {  
  2.   
  3.         public void init();  
  4.          
  5.         public void execute();  
  6.          
  7.         public void destory();  
  8.          
  9. }  


在這個例子中,每個用戶自定義的應用程序,都必須首先打包成一個jar文件,而後發佈到一個指定的目錄,按照指定的格式,而後首次發佈的時候,還須要將應用的配置添加到配置文件中。因此,首先,咱們須要定義一個能夠加載指定目錄jar文件的類:
[java]  view plain  copy
 
  1.  public ClassLoader createClassLoader(ClassLoader parentClassLoader, String... folders) {  
  2.   
  3.        List<URL> jarsToLoad = new ArrayList<URL>();  
  4.         for (String folder : folders) {  
  5.               List<String> jarPaths = scanJarFiles(folder);  
  6.   
  7.                for (String jar : jarPaths) {  
  8.   
  9.                      try {  
  10.                            File file = new File(jar);  
  11.                            jarsToLoad.add(file.toURI().toURL());  
  12.   
  13.                     } catch (MalformedURLException e) {  
  14.                            e.printStackTrace();  
  15.                     }  
  16.               }  
  17.        }  
  18.   
  19.        URL[] urls = new URL[jarsToLoad.size()];  
  20.        jarsToLoad.toArray(urls);  
  21.   
  22.         return new URLClassLoader(urls, parentClassLoader);  
  23. }  


 
這個方法很簡單,就是從多個目錄中掃描jar文件,而後返回一個新的URLClassLoader實例。至於scanJarFiles方法,你能夠隨後下載本文的源碼。而後,咱們須要定義一個配置文件,用戶須要將他們自定義的應用程序信息配置在這裏,這樣,該容器應用隨後就根據這個配置文件來加載全部的應用程序:
[html]  view plain  copy
 
  1. <apps>  
  2.         <app>  
  3.                <name> TestApplication1</name >  
  4.                <file> com.ijavaboy.app.TestApplication1</file >  
  5.         </app>  
  6.         <app>  
  7.                <name> TestApplication2</name >  
  8.                <file> com.ijavaboy.app.TestApplication2</file >  
  9.         </app>  
  10. </apps>  


這個配置是XML格式的,每個app標籤就表示一個應用程序,每個應用程序,須要配置名稱和那個實現了IApplication接口的類的完整路徑和名稱。
有了這個配置文件,咱們須要對其進行解析,在這個例子中,我使用的是xstream,很簡單,你能夠下載源碼,而後看看就知道了。這裏略過。這裏須要提一下:每一個應用的名稱(name),是相當重要的,由於該例子中,咱們的發佈目錄是整個項目發佈目錄下的applications目錄,這是全部用戶自定義應用程序發佈的目錄。而用戶發佈一個應用程序,須要首先在該目錄下新建一個和這裏配置的name同樣名稱的文件夾,而後將打包好的應用發佈到該文件夾中。(你必須這樣作,不然在這個例子中,你會發布失敗)。
好了,如今加載jar的方法和配置都有了,下面將是整個例子的核心部分,對,就是應用程序管理類,這個類就是要完成對每個用戶自定義應用程序的管理和維護。首先要作的,就是如何加載一個應用程序:
 
[java]  view plain  copy
 
  1. public void createApplication(String basePath, AppConfig config){  
  2.       String folderName = basePath + GlobalSetting. JAR_FOLDER + config.getName();  
  3.       ClassLoader loader = this.jarLoader .createClassLoader(ApplicationManager. class.getClassLoader(), folderName);  
  4.         
  5.        try {  
  6.              Class<?> appClass = loader. loadClass(config.getFile());  
  7.                
  8.              IApplication app = (IApplication)appClass.newInstance();  
  9.                
  10.              app.init();  
  11.                
  12.               this.apps .put(config.getName(), app);  
  13.                
  14.       } catch (ClassNotFoundException e) {  
  15.              e.printStackTrace();  
  16.       } catch (InstantiationException e) {  
  17.              e.printStackTrace();  
  18.       } catch (IllegalAccessException e) {  
  19.              e.printStackTrace();  
  20.       }  


能夠看到,這個方法接收兩個參數,一個是基本路徑,一個是應用程序配置。基本路徑其實就是項目發佈目錄的地址,而AppConfig其實就是配置文件中app標籤的一個實體映射,這個方法從指定的配置目錄中加載指定的類,而後調用該應用的init方法,完成用戶自定義應用程序的初始化。最後將,該加載的應用放入內存中。
如今,全部的準備工做,都已經完成了。接下來,在整個應用程序啓動的時候,咱們須要加載全部的用戶自定義應用程序,因此,咱們在ApplicationManager中添加一個方法:
 
[java]  view plain  copy
 
  1.  public void loadAllApplications(String basePath){  
  2.          
  3.         for(AppConfig config : this.configManager.getConfigs()){  
  4.                this.createApplication(basePath, config);  
  5.        }  
  6. }  


這個方法,就是將用戶配置的全部應用程序加載到該容器應用中來。好了,如今咱們是否是須要寫兩個獨立的應用程序試試效果了,要寫這個應用程序,首先咱們新建一個java應用程序,而後引用這個例子項目,或者將該例子項目打包成一個jar文件,而後引用到這個獨立的應用中來,由於這個獨立的應用程序中,必需要包含一個實現了IApplication接口的類。咱們來看看這個例子包含的一個獨立應用的樣子:
[java]  view plain  copy
 
  1. public class TestApplication1 implements IApplication{  
  2.   
  3.         @Override  
  4.         public void init() {  
  5.               System. out.println("TestApplication1-->init" );  
  6.        }  
  7.   
  8.         @Override  
  9.         public void execute() {  
  10.               System. out.println("TestApplication1-->do something" );  
  11.        }  
  12.   
  13.         @Override  
  14.         public void destory() {  
  15.               System. out.println("TestApplication1-->destoryed" );  
  16.        }  
  17.   
  18. }  


 
是否是很簡單?對,就是這麼簡單。你能夠照這個樣子,再寫一個獨立應用。接下來,你還須要在applications.xml中進行配置,很簡單,就是在apps標籤中增長以下代碼:
[html]  view plain  copy
 
  1. <app>  
  2.        <name> TestApplication1</name >  
  3.        <file> com.ijavaboy.app.TestApplication1</file >  
  4. </app>  
 
接下來,進入到本文的核心部分了,接下來咱們的任務,就所有集中在熱部署上了,其實,也許如今你還以爲熱部署很神祕,可是,我相信一分鐘以後,你就不會這麼想了。要實現熱部署,咱們以前說過,須要一個監聽器,來監聽發佈目錄applications,這樣當某個應用程序的jar文件改變時,咱們能夠進行熱部署處理。其實,要實現目錄文件改變的監聽,有不少種方法,這個例子中我使用的是apache的一個開源虛擬文件系統——common-vfs。若是你對其感興趣,你能夠訪問 http://commons.apache.org/proper/commons-vfs/。這裏,咱們繼承其FileListener接口,實現fileChanged 便可:
 
[java]  view plain  copy
 
  1. public void fileChanged (FileChangeEvent event) throws Exception {  
  2.   
  3.       String ext = event.getFile().getName().getExtension();  
  4.        if(!"jar" .equalsIgnoreCase(ext)){  
  5.               return;  
  6.       }  
  7.         
  8.       String name = event.getFile().getName().getParent().getBaseName();  
  9.         
  10.       ApplicationManager. getInstance().reloadApplication(name);  
  11.         


當某個文件改變的時候,該方法會被回調。因此,咱們在這個方法中調用了ApplicationManager的reloadApplication方法,重現加載該應用程序。
 
[java]  view plain  copy
 
  1. public void reloadApplication (String name){  
  2.       IApplication oldApp = this.apps .remove(name);  
  3.         
  4.        if(oldApp == null){  
  5.               return;  
  6.       }  
  7.         
  8.       oldApp.destory();     //call the destroy method in the user's application  
  9.         
  10.       AppConfig config = this.configManager .getConfig(name);  
  11.        if(config == null){  
  12.               return;  
  13.       }  
  14.         
  15.       createApplication(getBasePath(), config);  


重現加載應用程序時,咱們首先從內存中刪除該應用程序,而後調用原來應用程序的destory方法,最後按照配置從新建立該應用程序實例。
到這裏,你還以爲熱部署很玄妙很高深嗎?一切就是如此簡單。好了,言歸正傳,爲了讓咱們自定義的監聽接口能夠有效工做起來,咱們還須要指定它要監聽的目錄:
 
[java]  view plain  copy
 
  1.  public void initMonitorForChange(String basePath){  
  2.         try {  
  3.                this.fileManager = VFS.getManager();  
  4.                 
  5.               File file = new File(basePath + GlobalSetting.JAR_FOLDER);  
  6.               FileObject monitoredDir = this.fileManager .resolveFile(file.getAbsolutePath());  
  7.               FileListener fileMonitorListener = new JarFileChangeListener();  
  8.                this.fileMonitor = new DefaultFileMonitor(fileMonitorListener);  
  9.                this.fileMonitor .setRecursive(true);  
  10.                this.fileMonitor .addFile(monitoredDir);  
  11.                this.fileMonitor .start();  
  12.               System. out.println("Now to listen " + monitoredDir.getName().getPath());  
  13.                 
  14.        } catch (FileSystemException e) {  
  15.               e.printStackTrace();  
  16.        }  
  17. }  


這裏,就是初始化監聽器的地方,咱們使用VFS的DefaultFileMonitor完成監聽。而監聽的目錄,就是應用發佈目錄applications。接下來,爲了讓整個應用程序能夠持續的運行而不會結束,咱們修改下啓動方法:
[java]  view plain  copy
 
  1.  public static void main(String[] args){  
  2.          
  3.        Thread t = new Thread(new Runnable() {  
  4.                 
  5.                @Override  
  6.                public void run() {  
  7.                     ApplicationManager manager = ApplicationManager.getInstance();  
  8.                     manager.init();  
  9.               }  
  10.        });  
  11.          
  12.        t.start();  
  13.          
  14.         while(true ){  
  15.                try {  
  16.                     Thread. sleep(300);  
  17.               } catch (InterruptedException e) {  
  18.                     e.printStackTrace();  
  19.               }  
  20.        }  
  21. }  


好了,到這裏,一切都要結束了。如今,你已經很明白熱部署是怎麼一回事了,對嗎?不明白?OK,還有最後一招,去看看源碼吧!
 
源碼我已經放到了GitHub上面了,地址: https://github.com/chenjie19891104/ijavaboy/tree/master/AppLoader,歡迎下載使用,你擁有一切的權利對其進行修改。
 
最後,若是本文有什麼地方說的不許確,歡迎指正,謝謝!
相關文章
相關標籤/搜索