三:如果更新的功能包括应用逻辑,也就是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能帮助你动态加载类,从而在不停止应用程序的情况下动态更新类   
 
  |