【IT168 专稿】
一、概述
现在Java语言越来越受到程序员的关注。和Java相关的应用也越来越多。虽然Java是跨平台语言,但在国内有很多的应用都是运行在Windows下的。尤其是一些服务类程序。而一般基于Java的服务类程序都是以控制台方式运行的。这样虽然很直接。但如果服务程序多了,显得很乱。而且要使其在系统启动时运行也比较麻烦。因此,本文将介绍一种可以将Java程序转换为Windows服务的方法。通过这种方法。可以使Java程序象Windows服务程序一样运行。下面就让我们来进行转换吧。
一般有两种方法可以将Java程序转换为Windows服务:
1. 使用Windows服务直接在同一个进程运行Java应用程序。(这种方法服务程序无法更好地控制Java程序)。
2. 在Windows服务程序中建立一个java虚拟机实例(JVM),这个JVM实例和服务程序在同一个上下文中,而且JVM在Windows服务程序的控制之下。
第一种方法虽然实现起来简单,但这种方法不能很好地控制Java程序。因此,本文使用了第二种方法来运行Java程序。本文将带领读者一步一步地实现所有的内核代码。在实现代码之前,我们需要很好地了解Windows服务和Java本地接口(JNI)的概念和API的使用。
二、使用JVM API模拟Java运行时
由于Windows任务管理器将所有的Java进程都显示为"Java",因此,我们根本无法通过这种方式区分某一个Java进程。为了给每一个Java应用程序指定一个特殊的名子。我们需要使用JVM API来模拟Java运行时。
下面先来看看Java运行时(也就是java.exe)在运行时需要些什么。当我们使用java <类名>来运行java程序时,java.exe从系统路径动态装载了一些DLL库。这些Dll如下:
下面是java.exe如何处理Dll的过程:
3. 装载指定的Java类。
4. 调用main方法,也就是public static void main (String[] args)。
我们可以使用在jni.h中定义的JNI_CreateJavaVM方法来建立一个JVM实例。我们可
下面是一个简单的Java程序,在控制台中打印出"Hello World"。
在装载com.test.Hello时,我们必须使用"/"分割符(如com/test/Hello)。还有我们需要理解JNI调用Java方法的格式。如,为了调用void main(String[] args)方法,格式为:([Ljava/lang/String;)V:,"["表示数组;"L<classname>;"描述一个Java对象,V表示这个方法返回void。我们可以从JNI规范得到更多的细节。本文不再详细描述。
package com.test; public class Hello{ public static void main(String[] args) { System.out.println("Hello World" ); } }
为了演示Windows服务的功能,我在这设计了一h个叫Dummy的Java类。这个类在main方法等待一个停止事件。并实现了shutdown方法,在这个方法里设置并调用了stop事件。这将保证main线程安全地退出。在Dummy类中还实现了shutdown钩子,这个钩子主要给java.exe使用。Dummy.java的代码如下:
import java.io.* ; public class Dummy... { public static Dummy xyz = null; private boolean stopped = false; public static PrintWriter pw; public Dummy() throws IOException ...{ pw = new PrintWriter(new OutputStreamWriter( new FileOutputStream("C://dummy.log")), true); } public void start() ...{ pw.println("started"); while(!stopped) ...{ synchronized(this) ...{ try ...{ wait(); } catch (InterruptedException ie) ...{} } } pw.println("stopped"); } public void stop() ...{ pw.println("stopping"); synchronized(this) ...{ stopped = true; notifyAll(); } try ...{ Thread.sleep(1000); } catch(Exception ex)...{} } public static void main(String[] args) ...{ try ...{ xyz = new Dummy(); Runtime.getRuntime().addShutdownHook(new Thread() ...{ public void run() ...{ pw.println("inside shutdown hook"); xyz.stop(); } }); xyz.start(); } catch (Exception ex) ...{} } public static void shutdown() ...{ xyz.stop(); } }
我们可以使用如下的API在SCM中安装一个服务:
为了启动服务,我们需要使用StartServiceCtrlDispatcher来注册ServiceMain函数。这个ServiceMain函数包含了我们的主要功能。在我们的例子中,就是InvokeMain函数。接下来,调用RegisterServiceCtrlHandler(SERVICE_NAME, ServiceHandler);,这个函数注册一个Handler,并从SCM接收响应的命令。为了防止由于JVM崩溃而导致整个服务瘫痪,我们在另外一个线程里调用InvokeMain方法。上面的程序被写在DummyService.cpp中,通过VS2005将其编译成DummyService.exe。上面的代码可以通过点击此处下载
接下来我们使用如下的命令来安装、启动、停止以及卸载Windows服务:
上面的程序只是使用了最小的配置。其实要想充分使用JNI,得需要使用很多参数。一般需要至少15至20个配置参数。下面是在定制满足我们需要的程序的配置参数:
(4) 执行路径
(7) 工作目录
(9) 和桌面交互的选项(如果服务有一个UI接口)
(10) 登录用户名和密码(当使用用户帐号或本地系统帐号来运行服务时需要)
2. 和Java应用相关的参数:
(13) 类名
(15) 关闭超时
3. 日志参数:
我们可以根据具体的要求选择使用哪些参数。我们可以将这些参数保存在被推荐的注册表的位置:HKLM/System/CurrentControlSet/[Service Name]/Parameters. 中。
四、安全地退出Java程序
我们首先调用System.exit(0)来退出程序。虽然这个方法同时调用了所有的shutdown钩子,也试着关闭JVM。但它在清除和用于和SCM建立的Windows服务通讯的管道时抛出异常。这些错误信息如下:
The pipe has been ended.