参考《Ice 分布式程序设计》 马维达 译,ydogg博客
在编写Ice相关应用时,无论是Client还是Server端,都必须进行一些必要的动作,如:Ice通信器初始化、异常捕获,以及应用终止后的销毁。鉴于每个应用都需要,Ice运行时库提供了Ice.Application类来简化我们的应用编程,避免重复劳动,消除繁琐的初始化和销毁细节。
一、main函数
Ice run time 的主要进入点main函数的主体结构如下:
package Demo;
public class Server {
public static void main(String[] args) {
int status = 0;
Ice.Communicator ic = null;
try {
ic = Ice.Util.initialize(args);
// ...
} catch (Ice.LocalException e) {
e.printStackTrace();
status = 1;
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
if (ic != null) {
try {
ic.destroy();
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
}
System.exit(status);
}
}
初始化函数Ice.Util.initialize 接受的参数向量是由操作系统传给main 的。这个函数扫描参数向量,查找任何与Ice run time 有关的命令行选项,但不会移除这些选项。 如果在初始化过程中出了任何问题, initialize 会抛出异常。
在离开main 函数之前,必须调用Communicator.destroy()。destroy 操作负责结束Ice run time。特别地,destroy 会等待任何还在运行的操作调用完成。此外, destroy 还会确保任何还未完成的线程都得以汇合,并收回一些操作系统资源,比如文件描述符和内存。决不要让main 函数不先调用destroy 就终止;这样做会导致不确定的行为。
注意,这段代码把对Ice.initialize 的调用放在了try 块中,并且会负责把正确的退出状态返回给操作系统。还要注意,只有在初始化曾经成功的情况下,代码才会尝试销毁通信器。
二、Ice.Application 类
前面的main 函数所用的结构很常用,所以Ice 提供Ice.Application 类,封装了所有正确的初始化和结束活动。下面是这个类的概况(省略了一些细节):
package Ice;
public abstract class Application {
public Application();
public final int main(String appName, String[] args);
public final int main(String appName, String[] args, String configFile);
public abstract int run(String[] args);
public static String appName();
public static Communicator communicator();
// ...
}
这个类的意图是,你对Ice.Application 进行特化,在你的派生类中实现run 抽象方法。你通常会放在main 中的代码,都要放进run 方法。使用Ice.Application,我们的程序看起来像这样:
public class Server extends Ice.Application {
public int run(String[] args) {
// ......
return 0;
}
public static void main(String[] args) {
Server app = new Server();
int status = app.main("Server", args);
System.exit(status);
}
}
Application.main 函数会做这样一些事情:
1.针对java.lang.Exception 安装一个异常处理器。如果你的代码没有处理某个Ice 异常, Application.main 会先在System.err上打印异常的名字和栈踪迹,然后返回非零的返回值。
2.初始化(通过调用Ice.Util.initialize)和结束(通过调用Communicator.destroy)通信器。你可以调用静态的communicator 访问器,访问你的服务器的通信器。
3.扫描参数向量,查找与Ice run time 有关的选项,并移除这样的选项。因此,在传给你的run 方法的参数向量中,不再有与Ice 有关的选项,而只有针对你的应用的选项和参数。
4.通过静态的appName 成员函数,提供你的应用的名字。这个调用的返回值是调用Application.main 时用的第一个参数,所以,你可以在你的代码的任何地方调用Ice.Application.appName,从而获得这个名字(通常在打印错误消息时需要这一信息)。
5.安装一个关闭挂钩,适当地关闭通信器。
Ice.Application 能够确保你的程序适当地结束Ice run time,不管你的服务器是正常终止的,还是因为对异常或信号作出响应而终止的。建议在所有程序中都使用这个类。此外, Ice.Application 还提供了信号处理以及配置特性,当你使用这个类时,你无需自己实现这些特性。
Ice.Application类在客户端和服务器的使用相同。只需从Ice.Application 派生一个类,把客户代码放进它的run 方法,就可以了。这种做法带来的好处是在发生异常的情况下, Ice.Application 也能确保正确销毁通信器。
三、捕捉信号
服务器在终止之前必须进行一些清理工作,比如刷出数据库缓冲区,或者关闭网络连接。要想在收到信号或键盘中断时,防止数据库文件或其他持久数据损坏,这样的清理工作特别重要。
Java 没有提供对信号的直接支持,但它允许应用注册关闭挂钩,当JVM 关闭时会调用这样的关闭挂钩。有若干事件能够触发JVM 关闭,比如调用System.exit或者操作系统发出了中断信号,但关闭挂钩不会收到对关闭原因的说明。
在缺省情况下, Ice.Application 会注册一个关闭挂钩,从而允许JVM 关闭之前干净地终止应用:
package Ice;
public abstract class Application {
// ...
synchronized public static void shutdownOnInterrupt();
synchronized public static void defaultInterrupt();
synchronized public static boolean interrupted();
}
下面是Ice.Application类各成员函数的行为:
l shutdownOnInterrupt
这个函数安装一个关闭挂钩,它会调用通信器的shutdown 干净地关闭你的应用。这是缺省行为。
l defaultInterrupt
这个函数移除关闭挂钩。
l interrupted
如果此前是信号造成了通信器的关闭,这个函数返回真,否则返回假。据此,我们可以区分有意的关闭和JVM 造成的被迫关闭。例如,这可以用于日志记录。在缺省情况下, Ice.Application 的表现就好像shutdownOnInterrupt 被调用过一样,因此,要确保程序在JVM 关闭时干净地终止,我们的服务器的main 函数不需要变动。但我们增加了一个诊断功能,报告这件事情的发生,所以我们的main 函数现在看起来像这样:
public class Server extends Ice.Application {
public int run(String[] args) {
// Server code here...
if (interrupted())
System.err.println(appName() + ": terminating");
return 0;
}
public static void main(String[] args) {
Server app = new Server();
int status = app.main("Server", args);
System.exit(status);
}
}
四、Ice.Application 和属性
Ice.Application 还负责用属性值初始化Ice run time。通过属性,能够用两种方式配置run time。方式一,用属性控制线程池尺寸或服务器端口号。
package test;
import Ice.Properties;
public class Test extends Ice.Application {
public int run(String[] args) {
Ice.ObjectAdapter adapter = communicator()
.createObjectAdapterWithEndpoints("SimpleFilesystem",
"default -h 127.0.0.1 -p 10000");
// 获取命令行传入的参数集
Properties properties = communicator().getProperties();
// 通过属性名称获取属性值
String serverThreadPool = properties.getProperty("Ice.ThreadPool.Server.Size");
System.out.println("serverThreadPool = " + serverThreadPool);
return 0;
}
public static void main(String[] args) {
Test app = new Test();
System.exit(app.main("Server", args));
}
}
在命令行中运行:
》Java test.Test --Ice.ThreadPool.Server.Size=100 |
输出结果:serverThreadPool = 100
Ice.Application 的main函数是重载的,
public final int main(String appName, String args[], String configFile); |
所以方式二允许你指定一个配置文件名,这个文件将在初始化过程中被处理,所有的参数可以直接放在配置文件中。
五、Ice.Application 的局限
Ice.Application 是一个单体类,会创建单个通信器。如果你要使用多个通信器,你不能使用Ice.Application。相反,你必须像下面这样安排你的代码结构(一定要记得销毁通信器)。
public class Server {
public static void main(String[] args) {
int status = 0;
Ice.Communicator ic = null;
try {
// Server implementation here...
} catch (Ice.LocalException e) {
e.printStackTrace();
status = 1;
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
} finally {
if (ic != null)
ic.destroy();
}
System.exit(status);
}
}