将Java应用程序转换为Windows服务
【IT168技术文档】
如果你想将Java应用程序转换为Windows服务,有两种方法可以将Java应用程序转换为一个Windows服务:
1. Windows服务以单独的进程方式来执行Java应用程序
2. Windows服务在自己的进程上下文中创建一个Java virtual machine (JVM),然后以控制的方式关闭JVM。
本文中将采用第二种方法,为此需要能够理解Windows的一些概念和Java Native Interface (JNI)调用API。
模拟Java.exe
Windows任务管理器将会列出所有的Java应用程序进程,因此,没有什么办法可以区分单个的进程,为了指定特殊的Java应用程序的名字。可以通过使用JVM调用API来模拟Java运行时。
当你在命令提示符下运行java <your-class-name>,在系统路径中的java.exe加载几个DLL,以下几个DLL我们会感兴趣:
Java\jdk1.6.0_01\jre\bin\client\jvm.dll—Java JVM DLL 提供 JVM 调用 API. 一旦创建JVM, jvm.dll依次加载各种DLLs.
Java\jdk1.6.0_01\jre\bin\hpi.dll
Java\jdk1.6.0_01\jre\bin\verify.dll
Java\jdk1.6.0_01\jre\bin\java.dll
Java\jdk1.6.0_01\jre\bin\zip.dll
下面是java.exe如何出来DLL:
1. 加载JVMDLL
2. 创建JVM
3. 加载指定的类
4. 调用有签名的main方法:public static void main (String[] args)
使用在jni.h定义的JNI_CreateJavaVM方法,可以创建一个JVM实例,jni.h文件可以在<JDK-install-folder>\include目录下找到。
作为一个例子,采用基于Visual Studio 2005工程HelloKNR的一个简单Java程序:
为了简单起见,从最初的HelloKNR项目中移除了解析代码以追溯JVM选项和命令行参数。package com.doorul;
public class HelloKNR ...{
public static void main(String[] args) ...{
System.out.println("Hello KNR!");
}
}
下面是JNI代码来模拟java.exe,动态的加载jvm.dll而不是静态的绑定jvm.lib能使在选择JVM版本部署时更灵活。
加载类com.doorul.HelloKNR时,可以使用“/”分隔符(例如com/doorul/HelloKNR),需要理解JNI签名形式来调用Java方法,例如,为调用void main(String[] args),签名格式为is ([Ljava/lang/String;)V: "["意思是数组;"L<classname>;"代表一个Java对象,V表示方法返回void。int InvokeMain() ...{
JavaVM *vm;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
jint res;
JNIEnv *env;
jclass cls;
jmethodID mid;
![]()
options[0].optionString = CLASS_PATH;
vm_args.version = JNI_VERSION_1_4;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_FALSE;
![]()
//load the JVM DLL
HINSTANCE handle = LoadLibrary(RUNTIME_DLL);
if( handle == 0) ...{
printf("Failed to load jvm dll %s\n",
RUNTIME_DLL);
return -1;
}
//get the function pointer to JNI_CreateJVM
createJVM = (CreateJavaVM)GetProcAddress(handle,
"JNI_CreateJavaVM");
![]()
res = createJVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...{
printf("Error creating JVM");
return -1;
}
![]()
//locate the class
cls = env->FindClass(CLASS_NAME);
if(cls == 0) ...{
printf("Exception in thread \"main\"
java.lang.NoClassDefFoundError: %s\n",
CLASS_NAME);
return -1;
}
//get the method id of main
mid = env->GetStaticMethodID(cls, "main",
"([Ljava/lang/String;)V");
if(mid == 0) ...{
printf("Exception in thread \"main\"
java.lang.NoSuchMethodError: main\n");
return -1;
}
![]()
//invoke the main method with no parameters
env->CallStaticVoidMethod(cls, mid, 0);
//if there is any exception, print the exception
if(env->ExceptionCheck()) ...{
env->ExceptionDescribe();
return -1;
}
return 0;
}
【IT168技术文档】
集成JNI和Windows服务API
需要不断的运行Java应用程序来证明服务的性能,写一个简单的Dummy.java类在main方法内部为了阻塞结果,Dummy.java类也实现一个关闭的挂起,主要是在执行java.exe时使用。
典型的Widows服务(除了设备驱动)是用户进程,被服务控制管理器SCM(Service Control Manager )执行和管理。也包括services.exe进程,一个单进程包括许多服务。在此设计中,每个进程中包括一个JVM实例和一个Windows服务。
安装一个服务要求得到一个对SCM的处理和创建一个服务:
卸载要求得到对服务的处理和调用删除:SC_HANDLE schSCManager = OpenSCManager(..), SC_MANAGER_CREATE_SERVICE);
SC_HANDLE schService = CreateService(..)
为了启动服务,使用StartServiceCtrlDispatcher注册ServiceMain功能,ServiceMain功能包括主要的功能,本例中,调用InvokeMain函数,下一步调用RegisterServiceCtrlHandler(SERVICE_NAME, ServiceHandler);它注册一个接收SCM的操作。为避免整个服务瘫痪,当JVM终止发生时,在单独的线程中执行InvokeMain方法。SC_HANDLE schSCManager = OpenSCManager(.., SERVICE_ALL_ACCESS);
SC_HANDLE schService = OpenService( schSCManager, ..);
DeleteService(schService)
为自己演示Dummy服务,创建VS 2005工程DummyService,复制Dummy.java到C:\,并编译代码。运行下列命令安装,启动,停止和卸载服务:
DummyService /i (installs the service)
net start DummyService
net stop DummyService
DummyService /u (uninstalls the service)
【IT168技术文档】
至少15-21个可配置参数可从集成JNI和Windows服务API中产生,下面是一些定制程序时可能适合的参数:
Windows服务参数:
服务名称
显示名称
描述
执行路径
启动类型(自动/手动)
工作目录
每个进程的一个服务或者单个进程的多个服务
与桌面选项相互作用(如果服务有UI组件)
登录名和密码(运行服务使用一个用户的帐户或者本地系统帐户)
Java应用程序指定参数:
JVM.dll的路径
JVM选项(-D,-X)
类名
任何命令行参数
关闭时间
Log参数:
时间log和文件log
重定向任何表准的输出/错误到某个文件
安装期间,可以根据需求,在推荐位置HKLM\System\CurrentControlSet\[Service Name]\Parameters存储参数。
关闭Java App
虽然调用Systen.exit(0)方法可以关闭JVM,但是不能清除Windows服务和SCM建立的管道通讯,因此使用Systen.exit调用,但当停止服务时,却得到下列错误:
System error 109 has occurred.
The pipe has been ended.
可以实现一个单独的关闭方法并调用,可以调用public static void shutdown()方法,用20秒的时间以一个单独的线程关闭,如果关闭方法没有返回值,这可以阻止服务产生没有响应的状态,当然可以根据需求设置时间,但是要确保少于系统时间,否则SCM将终止服务进程。
