避免重启你的应用程序 二

原创 2005年05月08日 23:54:00

三:如果更新的功能包括应用逻辑,也就是class改变了,那就稍微麻烦点,你需要了解ClassLoader的原理。使用你定制的ClassLoader重新Load 已经编译好的class,就好比你重启应用一样。下面将简单介绍ClassLoader原理,以及举出一个例子来说明如何避免重启应用程序。
      虚拟机通过Classloader来转载类。bootstrap loader 负责load jdk的class(java.*,javax.*), system class loader是bootstrap的子类,负责load 所有在chasspath指定的变量。ClassLoader将字节流转化为Class类,这些字节流可能来源文件,也可能来源于网络或者数据库。转化方法是调用ClassLoader提供的final defineClass(className, classBytes, 0, classBytes.length)方法来实现。需要记住的是虚拟机里一个类的唯一标识是通过类的包名+类名+装载此类的ClassLoader。同一个ClassLoader实例只能装载Class一次,重复装载将抛出重复类定义异常。 
      如下自定义ClassLoader将从classpath里转载指定的类,来说明如上对ClassLoader的介绍,同时,我们用此ClassLoader演示如何避免重启动应用程序
      
public class DyLoader extends ClassLoader
{
    public DyLoader()
    {
        super(DyLoader.class.getClassLoader());
    }

    public Class loadFromCustomRepository(String className) {
    /**取环境变量*/
    String classPath = System.getProperty("java.class.path");
    List classRepository = new ArrayList();
    /**取得该路径下的所有文件夹 */
    if ( (classPath != null) && ! (classPath.equals(""))) {
      StringTokenizer tokenizer = new StringTokenizer(classPath,
          File.pathSeparator);
      while (tokenizer.hasMoreTokens()) {
        classRepository.add(tokenizer.nextToken());
      }
    }
    Iterator dirs = classRepository.iterator();
    byte[] classBytes = null;
    /**在类路径上查找该名称的类是否存在,如果不存在继续查找*/
    while (dirs.hasNext()) {
      String dir = (String) dirs.next();
      //replace '.' in the class name with File.separatorChar & append .class to the name
      String classFileName = className.replace('.', File.separatorChar);
      classFileName += ".class";
      try {
        File file = new File(dir + File.separatorChar + classFileName);
        if (file.exists()) {
          InputStream is = new FileInputStream(file);
          /**把文件读到字节文件*/
          classBytes = new byte[is.available()];
          is.read(classBytes);
          break;
        }
      }
      catch (IOException ex) {
        System.out.println("IOException raised while reading class file data");
        ex.printStackTrace();
        return null;
      }
    }
    return this.defineClass(className, classBytes, 0, classBytes.length);//加载类
  }

}

如下调用
DyLoader loader = new DyLoader();
Class a  = loader.loadFromCustomRepository("com.lijz.SampleDyService"); 
Class b = loader.loadFromCustomRepository("com.lijz.SampleDyService");
第三行代码将会抛出
java.lang.LinkageError: duplicate class definition: com/lijz/SampleDyService



如果如下调用,则一切正常,这是因为你使用新的ClassLoader实例来装载com.lijz.SampleDyService"
DyLoader loader= new DyLoader();
Class a loader.loadFromCustomRepository("com.lijz.SampleDyService"); 
DyLoader newLoader = new DyLoader();
Class b = newLoader.loadFromCustomRepository("com.lijz.SampleDyService");

言归正传,停止介绍Classloader,回到利用Classloader来避免重新启动你的应用程序

首先定义业务逻辑处理模块接口

public interface IDyService
{
   public void start();
   public void close();
   public void doBusiness();
}

start方法用于初始化,close用于清除此服务。doBusiness用来模拟处理业务

一个实现的例子如下:
public class SampleDyService implements IDyService
{
    public SampleDyService()
    {
    }
    public void doBusiness()
    {
        System.out.println("hello boy");
    }
    public void start()
    {
        System.out.println("Start SampleDyService:");
        System.out.println(SampleDyService.class.getClassLoader());
    }

    public void close()
    {
        System.out.println("close SampleDyService:");
    }


start方法 close方法仅打印出提示信息。doBuinsess输出"hello boy"。主程序将循环调用doBusiness方法

public class Main()
{     
     private IDyService service = null;
     public Main()
            throws Exception
    {
        DyLoader loader = new DyLoader();
        service = (IDyService) loader.loadFromCustomRepository(
                "com.gctech.service.test.dyloader.SampleDyService").newInstance();
        service.start();
        
        while (true)
        {

            service.doBusiness();
            Thread.sleep(1000 * 3);
        }
    }
    
    public static void main(String[] args)
            throws Exception
    {
        Main main = new Main();
    }
    
}    
  
  
  假设业务逻辑改变,要求SampleDyService的doBusiness打印出"hello girl"。新编译好的SampleDyService已经覆盖了原来的类。在不启动应用程序前提条件下如何更新新的业务逻辑呢?
  分俩部来完成
  第一步,在Main类里添加notifyReLoad方法,用新的classloader重新生成SampleDyService实例
  public void notifyReLoad()
            throws Exception
    {
        service.close();
        DyLoader loader = new DyLoader();
        service = (IDyService) loader.loadFromCustomRepository(
                "com.gctech.service.test.dyloader.SampleDyService").newInstance();
        service.start();

    }
  
  第二步:使用某种机制检测来检测SampleDyService.class已经改变,如可以通过上面的例子启动一个线程检测SampleDyService.class是否被改变,如果改变则调用Main.notifyReLoad().也可以采用主动通知方式,如web应用中,提供这样的界面调用。在此例子中提供一个检测线程。
  public class DyServciceChecker extends Thread
{
    Main main = null;
    
    public DyServciceChecker()
    {
    }
    public void setMain(Main main)
    {
        this.main = main;

    }

    public void run()
    {
        while(!interrupted())
        {
            try
            {
                boolean isChanged = check();
                if(isChanged)
                {
                    main.notifyReLoad();

                        }
                else
                {
                    Thread.sleep(1000*50);


                }
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
       
}

修改Main类的构造函数成如下

 public Main()
            throws Exception
    {
        DyLoader loader = new DyLoader();
        service = (IDyService) loader.loadFromCustomRepository(
                "com.gctech.service.test.dyloader.SampleDyService").newInstance();
        service.start();
        //添加检测线程
        DyServciceChecker checker = new DyServciceChecker();
        checker.setMain(this);        
        checker.start();       
         
        while (true)
        {

            service.doBusiness();
            Thread.sleep(1000 * 3);
        }
    }
    
好了,运行Main类。并在运行过程中用新的SampleDyService.class覆盖旧的SampleDyService.class。控制台输出信息如下:


Start SampleDyService:

com.gctech.service.test.dyloader.DyLoader@108786b

hello boy

hello boy

hello boy

...............
close SampleDyService:

Start SampleDyService:

com.gctech.service.test.dyloader.DyLoader@c1cd1f

hello girl

hello girl



总结:
  如果应用程序不可避免的在运行中要重启动,你首先要做好的工作是采取合理的设计,保证用户的请求和对用户的相应不丢失。否则,只能等到用户访问量最少的时候去启动。还有你在写你的业务逻辑的时候,即使当时你很肯定你的代码写死也没关系也不要这么做,尽量采用配置文件的方法,并提供如上的检测线程,现在的web container都提供检测web.xml文件是否改变来确定是否需要重新部署web。最后Classloader能帮助你动态加载类,从而在不停止应用程序的情况下动态更新类

避免重启你的应用程序

在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足需求。很好,这一切在开发测试阶段都没有问题...
  • njchenyi
  • njchenyi
  • 2005年05月07日 20:22
  • 1142

避免重启你的应用程序 一

转自:javaresearch.org  在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足...
  • quanjizhu
  • quanjizhu
  • 2005年05月07日 22:52
  • 1031

避免重启你的应用

在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足需求。很好,这一切在开发测试阶段都没有问题...
  • wangsheng1028
  • wangsheng1028
  • 2005年05月20日 22:08
  • 747

windows系统下,实用cmd启动你的应用程序,还你一个干净的桌面

第一步:把你的启动程序的路径添加到环境变量path中: win10:打开步骤:“计算机”——>右键——>"属性"——>"高级系统设置"——>"环境变量"——>"系统环境"——>"path"——>"编辑...
  • u010642308
  • u010642308
  • 2017年12月08日 17:32
  • 115

第 0006 题:你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。

偷懒一下,遍历目录就不写了,毕竟不爱写日记。 忽略的词严格来说应该有个词库,目前用不到就先不研究了。import osos.chdir('C:/workspace')def count_words(...
  • no_giveup
  • no_giveup
  • 2016年05月11日 00:42
  • 635

Python Show-Me-the-Code 第 0006 题 最重要的词

第 0006 题:你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。 思路:切换到目标目录,然后遍历该目录下的txt文件,用...
  • huangxiongbiao
  • huangxiongbiao
  • 2015年04月21日 17:13
  • 1406

Python练习册 第 0006 题:你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。

笔者最近初接触Python语言,在做一些简单的刷题练习。 第 0006 题:你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。 ...
  • Jacky_chenjp
  • Jacky_chenjp
  • 2016年08月21日 17:17
  • 3336

程序自动重启脚本

程序自动重启
  • zhuyouyang
  • zhuyouyang
  • 2017年09月07日 15:44
  • 154

Qt之重启应用程序

今天分享的内容有些意思-如何重启一个应用程序。其实,有时候这是一个很重要的功能点,而且很人性化、易用性很好。例如:切换用户。当某个用户登录成功之后,需要切换到其它账号,那么这时,你就知道它的重要性了。...
  • u011012932
  • u011012932
  • 2015年12月18日 21:29
  • 4906

计算二进制(bin)10101010等于负几你要花多长时间?

本文提供了快速二进制,十进制,十六进制正负数转换的便捷方法。
  • craftsman1970
  • craftsman1970
  • 2017年03月05日 13:31
  • 632
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:避免重启你的应用程序 二
举报原因:
原因补充:

(最多只允许输入30个字)