|
在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足需求。很好,这一切在开发测试阶段都没有问题,无可厚非。不过到了应用正式上线就出现麻烦了。
重启应用会导致系统不可用,或者导致用户请求、响应丢失。甚至有的系统本生就要求为系统动态添加功能,在没有为你的应用添加防止重启动的策略前,通常能做的是到凌晨2点趁用户少的时候重启你的应用或者是暂时切换到备份系统。
如果你的系统也遇到过或者将不可避免的遇到这样的问题,那在这篇文章里,几个解决办法可以供你选择使用。
一、在启动前,保存未被处理的请求和未发给用户的响应。较好的方式是将接收请求,发送响应的模块与你处理应用逻辑的模块分开设计。如现在的web服务器,可以在你重新部署(redploy)的时候暂把用户的请求保存到队列,等到部署成功后在提交给处理逻辑的模块。
又如,接收请求,发送响应的模块是不同的应用。与应用逻辑模块之间通过文件(把请求序列化成一个文件)等方式交换数据,这样在你的逻辑应用重启后,仍然可以继续读取请求。
二、如果更新的功能是纯数据的,那么,采用动态配置,避免重启动。比如,一个web系统,提供reloadConfig界面,重新从配置文件里读取数据。一个java应用程序,你可以在你的应用程序里启动一个检测线程,检测文件是否被改变,如果改变,则自动重新转载配置。如下例子:
public ConfigChecker extends Thread
{
SystemManager sm = null;
long time ;
public ConfigChecker(SystemManager sm)
throws ApplicationException
{
this.sm = sm;
time = getConfigFileTime();
}
pulblic void run()
{
while(!interrupted())
{
try
{
long newTime
= getConfigFileTime();
if(newTime!=time)
{
time = newTime;
//重新装载配置
sm.reloadConfig();
}
Thread.sleep(1000*3)
}
catch(Exception ex)
{
return ;
}
}
}
public long getConfigFileTime()
throws ApplicationException
{
try
{
File f = new File
(sm.getConfigFile());
return f.lastModified()
}
catch(IOException ex)
{
throw new
ApplicationException
(ex.getMessage())
}
}
}
|
这种方式适合你要动态改变的是纯数据,它要求你应用中的数据不能写死在代码里,而是通过文件配置。通过重新从配置文件里读取数据避免重启动
三、如果更新的功能包括应用逻辑,也就是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");
|
1
2
下一页>>
|