zoukankan      html  css  js  c++  java
  • 热部署原理和实例

    在web应用开发的过程中,我们时时刻刻都在使用热部署。热部署的目的很简单,就是为了节省应用开发和发布的时间。比如,我们在使用Tomcat或者Jboss等应用服务器开发应用时,我们经常会开启热部署功能。热部署,简单点来说,就是我们将打包好的应用直接替换掉原有的应用,不用关闭或者重启Web服务器,一切就是这么简单。那么,热部署到底是如何实现的呢?在本文中,我将写一个实例,这个实例就是一个容器应用,允许用户发布自己的应用,同时支持热部署。

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

    有了总体实现思路之后,我们可以想到如下几个需要完成的目标:

    1、定义一个用户自定义应用程序的接口,这是因为,我们需要在容器应用中去加载用户自定义的应用程序。 
    2、我们还需要一个配置文件,让用户去配置他们的应用程序。 
    3、应用启动的时候,加载所有已有的用户自定义应用程序。 
    4、为了支持热部署,我们需要一个监听器,来监听应用发布目录中每个文件的变动。这样,当某个应用重新部署之后,我们就可以得到通知,进而进行热部署处理。

    实现部分:

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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);
    	}
    </string></url></url>

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <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标签就表示一个应用程序,每一个应用程序,需要配置名称和那个实现了IApplication接口的类的完整路径和名称。 
    有了这个配置文件,我们需要对其进行解析,在这个例子中,我使用的是xstream,很简单,你可以下载源码,然后看看就知道了。这里略过。这里需要提一下:每个应用的名称(name),是至关重要的,因为该例子中,我们的发布目录是整个项目发布目录下的applications目录,这是所有用户自定义应用程序发布的目录。而用户发布一个应用程序,需要首先在该目录下新建一个和这里配置的name一样名称的文件夹,然后将打包好的应用发布到该文件夹中。(你必须这样做,否则在这个例子中,你会发布失败)。 
    好了,现在加载jar的方法和配置都有了,下面将是整个例子的核心部分,对,就是应用程序管理类,这个类就是要完成对每一个用户自定义应用程序的管理和维护。首先要做的,就是如何加载一个应用程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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();
    		}
    	}

    可以看到,这个方法接收两个参数,一个是基本路径,一个是应用程序配置。基本路径其实就是项目发布目录的地址,而AppConfig其实就是配置文件中app标签的一个实体映射,这个方法从指定的配置目录中加载指定的类,然后调用该应用的init方法,完成用户自定义应用程序的初始化。最后将,该加载的应用放入内存中。 
    现在,所有的准备工作,都已经完成了。接下来,在整个应用程序启动的时候,我们需要加载所有的用户自定义应用程序,所以,我们在ApplicationManager中添加一个方法:

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

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

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

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

    1
    2
    3
    4
    <app>
    		<name>TestApplication1</name>
    		<file>com.ijavaboy.app.TestApplication1</file>
    	</app>

    接下来,进入到本文的核心部分了,接下来我们的任务,就全部集中在热部署上了,其实,也许现在你还觉得热部署很神秘,但是,我相信一分钟之后,你就不会这么想了。要实现热部署,我们之前说过,需要一个监听器,来监听发布目录applications,这样当某个应用程序的jar文件改变时,我们可以进行热部署处理。其实,要实现目录文件改变的监听,有很多种方法,这个例子中我使用的是apache的一个开源虚拟文件系统——common-vfs。如果你对其感兴趣,你可以访问 这里 。这里,我们继承其FileListener接口,实现fileChanged 即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void fileChanged(FileChangeEvent event) throws Exception {
     
    		String ext = event.getFile().getName().getExtension();
    		if(!&quot;jar&quot;.equalsIgnoreCase(ext)){
    			return;
    		}
     
    		String name = event.getFile().getName().getParent().getBaseName();
     
    		ApplicationManager.getInstance().reloadApplication(name);
     
    	}

    当某个文件改变的时候,该方法会被回调。所以,我们在这个方法中调用了ApplicationManager的reloadApplication方法,重新加载该应用程序。

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

    重现加载应用程序时,我们首先从内存中删除该应用程序,然后调用原来应用程序的destory方法,最后按照配置重新创建该应用程序实例。到这里,你还觉得热部署很玄妙很高深吗?一切就是如此简单。好了,言归正传,为了让我们自定义的监听接口可以有效工作起来,我们还需要指定它要监听的目录:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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(&quot;Now to listen &quot; + monitoredDir.getName().getPath());
     
    		} catch (FileSystemException e) {
    			e.printStackTrace();
    		}
    	}

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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();
    			}
    		}
    	}

    好了,到这里,一切都要结束了。现在,你已经很明白热部署是怎么一回事了,对吗?不明白?OK,还有最后一招,去看看源码吧!

    源码我已经放到了GitHub上面了,地址: https://github.com/chenjie19891104/ijavaboy/tree/master/AppLoader ,欢迎下载使用,你拥有一切的权利对其进行修改。

    最后,如果本文有什么地方说的不准确,欢迎指正,谢谢!

  • 相关阅读:
    Ubuntu升级软件和ubuntu升级系统的命令
    ASP 中如何根据数据库中取出的值来判定 checkbox或radio 的状态是否为选中
    C# 根据年、月、周、星期获得日期等
    鼠标右击事件
    【原创】VB利用堆栈实现算术表达式计算
    【算法】VB6实现哈夫曼编码生成的类
    【算法】VB 24点计算
    【算法】VB实现后缀表达式转中缀表达式
    C#操作Excel替换关键字 Johan
    C#递归遍历文件夹下的文件 Johan
  • 原文地址:https://www.cnblogs.com/printN/p/6574202.html
Copyright © 2011-2022 走看看