热部署的目的是为了节省应用开发和发布的时间。比如,在使用tomcat或者JBoss等应用服务器开发应用时,经常会开启热部署功能。热部署简单点来说,就是我们将打包好的应用直接替换掉原有的应用,不用关闭或者重启服务器。热部署到底是如何实现的呢?

在java中,要实现热部署,首先,你得明白,java中类的加载方式。每一个应用程序的类都会被ClassLoader加载,所以,要实现一个支持热部署的应用,我们可以对每一个用户自定义的应用程序使用一个单独的ClassLoader进行加载。然后,当某个用户自定义的应用程序发生变化的时候,我们首先销毁原来的应用,然后使用新的ClassLoader来加载改变之后的应用。而所有其他的应用程序不会受到一点干扰。

java项目的热部署 java项目热部署实现方式_加载


1、定义一个用户自定义应用程序的接口,这是因为,我们需要在容器应用中去加载用户自定义的应用程序。


2、我们还需要一个配置文件,让用户去配置他们的应用程序。


3、应用启动的时候,加载所有已有的用户自定义应用程序。


4、为了支持热部署,我们需要一个监听器,来监听应用发布目录中每个文件的变动。这样,当某个应用重新部署之后,我们就可以得到通知,进而进行热部署处理。


实现部分:




首先,我们定义一个接口,每一个用户自定义的程序中都必须包含唯一一个实现了该接口的类。代码如下:


1. public interface IApplication {  
2.   
3. public void init();  
4.          
5. public void execute();  
6.          
7. public void destory();  
8.          
9. }





在这个例子中,每一个用户自定义的应用程序,都必须首先打包成一个jar文件,然后发布到一个指定的目录,按照指定的格式,然后首次发布的时候,还需要将应用的配置添加到配置文件中。所以,首先,我们需要定义一个可以加载指定目录jar文件的类:



    1.  public ClassLoader createClassLoader(ClassLoader parentClassLoader, String... folders) {  
    2.   
    3. new ArrayList<URL>();  
    4. for (String folder : folders) {  
    5.               List<String> jarPaths = scanJarFiles(folder);  
    6.   
    7. for (String jar : jarPaths) {  
    8.   
    9. try {  
    10. new File(jar);  
    11.                            jarsToLoad.add(file.toURI().toURL());  
    12.   
    13. catch (MalformedURLException e) {  
    14.                            e.printStackTrace();  
    15.                     }  
    16.               }  
    17.        }  
    18.   
    19. new URL[jarsToLoad.size()];  
    20.        jarsToLoad.toArray(urls);  
    21.   
    22. return new URLClassLoader(urls, parentClassLoader);  
    23. }








    这个方法很简单,就是从多个目录中扫描jar文件,然后返回一个新的URLClassLoader实例。至于scanJarFiles方法,你可以随后下载本文的源码。然后,我们需要定义一个配置文件,用户需要将他们自定义的应用程序信息配置在这里,这样,该容器应用随后就根据这个配置文件来加载所有的应用程序:



      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的方法和配置都有了,下面将是整个例子的核心部分,对,就是应用程序管理类,这个类就是要完成对每一个用户自定义应用程序的管理和维护。首先要做的,就是如何加载一个应用程序:





      1. public void createApplication(String basePath, AppConfig config){  
      2.       String folderName = basePath + GlobalSetting. JAR_FOLDER + config.getName();  
      3. 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中添加一个方法:





        1.  public void loadAllApplications(String basePath){  
        2.          
        3. for(AppConfig config : this.configManager.getConfigs()){  
        4. this.createApplication(basePath, config);  
        5.        }  
        6. }




        这个方法,就是将用户配置的所有应用程序加载到该容器应用中来。好了,现在我们是不是需要写两个独立的应用程序试试效果了,要写这个应用程序,首先我们新建一个java应用程序,然后引用这个例子项目,或者将该例子项目打包成一个jar文件,然后引用到这个独立的应用中来,因为这个独立的应用程序中,必须要包含一个实现了IApplication接口的类。我们来看看这个例子包含的一个独立应用的样子:


          1. public class TestApplication1 implements IApplication{  
          2.   
          3. @Override  
          4. public void init() {  
          5. "TestApplication1-->init" );  
          6.        }  
          7.   
          8. @Override  
          9. public void execute() {  
          10. "TestApplication1-->do something" );  
          11.        }  
          12.   
          13. @Override  
          14. public void destory() {  
          15. "TestApplication1-->destoryed" );  
          16.        }  
          17.   
          18. }







          是不是很简单?对,就是这么简单。你可以照这个样子,再写一个独立应用。接下来,你还需要在applications.xml中进行配置,很简单,就是在apps标签中增加如下代码:


            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  即可:






              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方法,重现加载该应用程序。




              1. public void reloadApplication (String name){  
              2. this.apps .remove(name);  
              3.         
              4. if(oldApp == null){  
              5. return;  
              6.       }  
              7.         
              8. //call the destroy method in the user's application  
              9.         
              10. this.configManager .getConfig(name);  
              11. if(config == null){  
              12. return;  
              13.       }  
              14.         
              15.       createApplication(getBasePath(), config);



              重现加载应用程序时,我们首先从内存中删除该应用程序,然后调用原来应用程序的destory方法,最后按照配置重新创建该应用程序实例。


              到这里,你还觉得热部署很玄妙很高深吗?一切就是如此简单。好了,言归正传,为了让我们自定义的监听接口可以有效工作起来,我们还需要指定它要监听的目录:





                1.  public void initMonitorForChange(String basePath){  
                2. try {  
                3. this.fileManager = VFS.getManager();  
                4.                 
                5. new File(basePath + GlobalSetting.JAR_FOLDER);  
                6. this.fileManager .resolveFile(file.getAbsolutePath());  
                7. new JarFileChangeListener();  
                8. this.fileMonitor = new DefaultFileMonitor(fileMonitorListener);  
                9. this.fileMonitor .setRecursive(true);  
                10. this.fileMonitor .addFile(monitoredDir);  
                11. this.fileMonitor .start();  
                12. "Now to listen " + monitoredDir.getName().getPath());  
                13.                 
                14. catch (FileSystemException e) {  
                15.               e.printStackTrace();  
                16.        }  
                17. }


                这里,就是初始化监听器的地方,我们使用VFS的DefaultFileMonitor完成监听。而监听的目录,就是应用发布目录applications。接下来,为了让整个应用程序可以持续的运行而不会结束,我们修改下启动方法:


                  1.  public static void main(String[] args){  
                  2.          
                  3. 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. 300);  
                  17. catch (InterruptedException e) {  
                  18.                     e.printStackTrace();  
                  19.               }  
                  20.        }  
                  21. }