转自:http://blog.csdn.net/chenjie19891104/article/details/42807959

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

 
在Java中,要实现热部署,首先,你得明白,Java中类的加载方式。每一个应用程序的类都会被ClassLoader加载,所以,要实现一个支持热部署的应用,我们可以对每一个用户自定义的应用程序使用一个单独的ClassLoader进行加载。然后,当某个用户自定义的应用程序发生变化的时候,我们首先销毁原来的应用,然后使用一个新的ClassLoader来加载改变之后的应用。而所有其他的应用程序不会受到一点干扰。先看一下,该应用的设计图:
 
 
有了总体实现思路之后,我们可以想到如下几个需要完成的目标:
 
1、定义一个用户自定义应用程序的接口,这是因为,我们需要在容器应用中去加载用户自定义的应用程序。
2、我们还需要一个配置文件,让用户去配置他们的应用程序。
3、应用启动的时候,加载所有已有的用户自定义应用程序。
4、为了支持热部署,我们需要一个监听器,来监听应用发布目录中每个文件的变动。这样,当某个应用重新部署之后,我们就可以得到通知,进而进行热部署处理。
 
实现部分:
 
首先,我们定义一个接口,每一个用户自定义的程序中都必须包含唯一一个实现了该接口的类。代码如下:
  1. public interface IApplication {
  2. public void init();
  3. public void execute();
  4. public void destory();
  5. }
在这个例子中,每一个用户自定义的应用程序,都必须首先打包成一个jar文件,然后发布到一个指定的目录,按照指定的格式,然后首次发布的时候,还需要将应用的配置添加到配置文件中。所以,首先,我们需要定义一个可以加载指定目录jar文件的类:
  1. public ClassLoader createClassLoader(ClassLoader parentClassLoader, String... folders) {
  2. List<URL> jarsToLoad = new ArrayList<URL>();
  3. for (String folder : folders) {
  4. List<String> jarPaths = scanJarFiles(folder);
  5. for (String jar : jarPaths) {
  6. try {
  7. File file = new File(jar);
  8. jarsToLoad.add(file.toURI().toURL());
  9. } catch (MalformedURLException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. URL[] urls = new URL[jarsToLoad.size()];
  15. jarsToLoad.toArray(urls);
  16. return new URLClassLoader(urls, parentClassLoader);
  17. }
 
这个方法很简单,就是从多个目录中扫描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. ClassLoader loader = this.jarLoader .createClassLoader(ApplicationManager. class.getClassLoader(), folderName);
  4. try {
  5. Class<?> appClass = loader. loadClass(config.getFile());
  6. IApplication app = (IApplication)appClass.newInstance();
  7. app.init();
  8. this.apps .put(config.getName(), app);
  9. } catch (ClassNotFoundException e) {
  10. e.printStackTrace();
  11. } catch (InstantiationException e) {
  12. e.printStackTrace();
  13. } catch (IllegalAccessException e) {
  14. e.printStackTrace();
  15. }
可以看到,这个方法接收两个参数,一个是基本路径,一个是应用程序配置。基本路径其实就是项目发布目录的地址,而AppConfig其实就是配置文件中app标签的一个实体映射,这个方法从指定的配置目录中加载指定的类,然后调用该应用的init方法,完成用户自定义应用程序的初始化。最后将,该加载的应用放入内存中。
现在,所有的准备工作,都已经完成了。接下来,在整个应用程序启动的时候,我们需要加载所有的用户自定义应用程序,所以,我们在ApplicationManager中添加一个方法:
 
  1. public void loadAllApplications(String basePath){
  2. for(AppConfig config : this.configManager.getConfigs()){
  3. this.createApplication(basePath, config);
  4. }
  5. }
这个方法,就是将用户配置的所有应用程序加载到该容器应用中来。好了,现在我们是不是需要写两个独立的应用程序试试效果了,要写这个应用程序,首先我们新建一个java应用程序,然后引用这个例子项目,或者将该例子项目打包成一个jar文件,然后引用到这个独立的应用中来,因为这个独立的应用程序中,必须要包含一个实现了IApplication接口的类。我们来看看这个例子包含的一个独立应用的样子:
  1. public class TestApplication1 implements IApplication{
  2. @Override
  3. public void init() {
  4. System. out.println("TestApplication1-->init" );
  5. }
  6. @Override
  7. public void execute() {
  8. System. out.println("TestApplication1-->do something" );
  9. }
  10. @Override
  11. public void destory() {
  12. System. out.println("TestApplication1-->destoryed" );
  13. }
  14. }
 
是不是很简单?对,就是这么简单。你可以照这个样子,再写一个独立应用。接下来,你还需要在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. String ext = event.getFile().getName().getExtension();
  3. if(!"jar" .equalsIgnoreCase(ext)){
  4. return;
  5. }
  6. String name = event.getFile().getName().getParent().getBaseName();
  7. ApplicationManager. getInstance().reloadApplication(name);
当某个文件改变的时候,该方法会被回调。所以,我们在这个方法中调用了ApplicationManager的reloadApplication方法,重现加载该应用程序。
 
  1. public void reloadApplication (String name){
  2. IApplication oldApp = this.apps .remove(name);
  3. if(oldApp == null){
  4. return;
  5. }
  6. oldApp.destory();     //call the destroy method in the user's application
  7. AppConfig config = this.configManager .getConfig(name);
  8. if(config == null){
  9. return;
  10. }
  11. createApplication(getBasePath(), config);
重现加载应用程序时,我们首先从内存中删除该应用程序,然后调用原来应用程序的destory方法,最后按照配置重新创建该应用程序实例。
到这里,你还觉得热部署很玄妙很高深吗?一切就是如此简单。好了,言归正传,为了让我们自定义的监听接口可以有效工作起来,我们还需要指定它要监听的目录:
 
  1. public void initMonitorForChange(String basePath){
  2. try {
  3. this.fileManager = VFS.getManager();
  4. File file = new File(basePath + GlobalSetting.JAR_FOLDER);
  5. FileObject monitoredDir = this.fileManager .resolveFile(file.getAbsolutePath());
  6. FileListener fileMonitorListener = new JarFileChangeListener();
  7. this.fileMonitor = new DefaultFileMonitor(fileMonitorListener);
  8. this.fileMonitor .setRecursive(true);
  9. this.fileMonitor .addFile(monitoredDir);
  10. this.fileMonitor .start();
  11. System. out.println("Now to listen " + monitoredDir.getName().getPath());
  12. } catch (FileSystemException e) {
  13. e.printStackTrace();
  14. }
  15. }
这里,就是初始化监听器的地方,我们使用VFS的DefaultFileMonitor完成监听。而监听的目录,就是应用发布目录applications。接下来,为了让整个应用程序可以持续的运行而不会结束,我们修改下启动方法:
  1. public static void main(String[] args){
  2. Thread t = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. ApplicationManager manager = ApplicationManager.getInstance();
  6. manager.init();
  7. }
  8. });
  9. t.start();
  10. while(true ){
  11. try {
  12. Thread. sleep(300);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
好了,到这里,一切都要结束了。现在,你已经很明白热部署是怎么一回事了,对吗?不明白?OK,还有最后一招,去看看源码吧!
 
源码我已经放到了GitHub上面了,地址:https://github.com/chenjie19891104/ijavaboy/tree/master/AppLoader

Java服务器热部署的实现原理的更多相关文章

  1. paip.提升用户体验--提升java的热部署热更新能力

    paip.提升用户体验--提升java的热部署热更新能力 想让java做到php那么好的热部署能力  "fix online"/在线修复吗??直接在服务器上修改源码生效,无需重启应 ...

  2. springboot之DevTools热部署的简单原理解析

    IDEA新建springboot选择DevTools springboot-devtools模块能够实现热部署,添加类.添加方法,修改配置文件,修改页面等,都能实现热部署. 原理就是重启项目,但比手动 ...

  3. SpringBoot项目构建、测试、热部署、配置原理、执行流程

    SpringBoot项目构建.测试.热部署.配置原理.执行流程 一.项目构建 二.测试和热部署 三.配置原理 四.执行流程

  4. java的热部署和热加载

    ps:热部署和热加载其实是两个类似但不同的概念,之前理解不深,so,这篇文章重构了下. 一.热部署与热加载 在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载. 对于Java应用程序来说 ...

  5. JAVA代码热部署,在线不停服动态更新

    本地debug的时候,可以实时编译并更新代码,线上也可以不停服来动态更新类,即所说的java热部署.   JDK代理的两种方式: 1.premain方式是Java SE5开始就提供的代理方式,但其必须 ...

  6. java 中 热部署与卸载关系

    今天发现早年在大象笔记中写的一篇笔记,之前放在ijavaboy上的,现在它已经访问不了了.前几天又有同事在讨论这个问题.这里拿来分享一下. 在web应用开发或者游戏服务器开发的过程中,我们时时刻刻都在 ...

  7. Tomcat热部署的实现原理

    Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件.在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色.大多数基于Java的应用服务器,包括EJ ...

  8. Java 项目热部署,节省构建时间的正确姿势

    上周末,帮杨小邪(我的大学室友)远程调试项目.SpringBoot 构建,没有热部署,改一下就得重启相关模块.小小的 bug ,搞了我一个多小时,大部分时间都还在构建上(特么,下次得收钱才行).我跟他 ...

  9. IDEA 服务器热部署详解(On Update action/On frame deactivation)

    https://blog.csdn.net/w15321271041/article/details/80597962 场景:一般服务器(比如tomcat,jboss等)启动以后,我们还需要进一步修改 ...

随机推荐

  1. Qt中调用PolarSSL库(一)

    最近一直在学习SSL相关的知识,也是先了解理论相关的知识,主要是SSL相关的基本概念和连接建立过程,主要是基于PolarSSL开源库进行学习.学习完了之后就希望能给有所运用,就想用Qt写一个简单的程序 ...

  2. 【Razor语法规则小手册....】

    在经过长达半年的Windows开发后,Razor的一些语法有些生疏了.搜集些,再熟悉下.呵呵,甚是怀念以前做web 项目的时候,基于动软代码生成器自定义T4模板,后来vs2010后开始支持T4模板. ...

  3. C语言简单strcat和strcmp的实现

    对于C标准库中的字符串处理函数应该平常用的比较多:简单实现strcat和strcmp _strcpy: char *_strcpy(char *dest, char *src) { char *buf ...

  4. 【转】android camera(三):camera V4L2 FIMC

    关键词:android  camera CMM 模组 camera参数  CAMIF   V4L2  平台信息:内核:linux系统:android 平台:S5PV310(samsung exynos ...

  5. The Building Blocks- Components of EA Part 2- Process, People, Network and Time

    1. Zachman Framework Information (Data) - Answer the Question 'What?' Contextual: List of Things imp ...

  6. Linux shell中的一个问题 ${}带正则匹配的表达式

    目前在准备龙芯项目的PMON,在研究其编译过程的时候,看到一些make 语句,百思不得其解.后来在shell编程中看到一点资料,牵扯到Shell中的正则表达式.故记录下来,以备后来查阅. 问题: 在某 ...

  7. 那些 Cynthia 教我的事 之 PMSec (三)

    在项目中,聪明的Jenny童鞋提了一个suggestion,即将同一个店同一人提交的请求,经过上级批准之后,邮件内容需要合并. 非常滴合理有木有~~ 提交十个申请,将收到十封邮件,的确不友好哦.可是由 ...

  8. pl_sql 报ora-12154 无法解析指定的连接标识符的问题

    情况一:连接本地的没有问题,连接远程服务器的时候报以上错误.那么在本地客户端下的TNSNames.ora设置中配置你的远程服务器连接,本人的如下: //mestest是远程服务器名 //172.18. ...

  9. word 中巧妙添加分隔线

  10. Spring整合Hibernate 一 - 注入SessionFactory

    Spring3 整合 Hibernate4 - 注入SessionFactory 版本: spring-framework-3.2.4.RELEASE hibernate-release-4.2.5. ...