用EXE4J将Java程序制作成windows系统服务。具体方法略。
通常情况下,这样做是没有问题的。
但是如果java程序中有调用DLL文件的内容时,EXE4J生成的系统服务就无法正常运行。跟踪的结果是程序卡在调用DLL方法的代码上(用JNative调用DLL)。
同样的情况也出现在使用jeasyopc时,怀疑也是同样的原因,因为使用jeasyopc.jar时,会在当前文件夹下生成一个JCustomOpc.dll的文件。
出现问题的当时,没有找到解决方法,只能放弃将Java程序做成系统服务,而是将该程序做成tomcat自启动项目,用tomcat来代替想要做成的系统服务。
这样处置会有很多隐患。
现在,又找到了另一个替代方案。
用处理OPC Client的Java程序来举例:
首先假设OPC Client的程序已经开发完成,简称为OPC程序。
做一个定时任务,检查系统中OPC程序是否运行,如果没有运行,就启动OPC程序。
将定时任务做成系统服务。这样可以避免在在服务中直接调用DLL。简称为定时任务服务。
完成这个思路,有2个问题需要处理:
首先,启动OPC程序:
这个问题很简单,代码如下
new Thread() {
public void run() {
try {
Runtime.getRuntime().exec(cmd);
} catch (final SecurityException ex) {
ex.printStackTrace();
} catch (final IOException ex) {
ex.printStackTrace();
}
}
}.start();
这段代码中cmd变量就是OPC程序的启动命令,大概就是Java -cp “.\lib\log4j-1.2.15.jar;” opc.OPC,需要注意的就是路径问题。
其次就是检查系统中OPC程序是否运行:
这个问题有2种处理方式:
一是OPC程序开一个任意监听端口,而定时任务服务将试图连接这个端口与OPC程序通讯,只要定时任务服务能顺利与OPC程序通讯,就说明OPC程序已经在运行了。具体实现略。
没有采用这样方法的原因是因为OPC程序和定时任务程序紧耦合了。
二是使用JDK中提供tools.jar中的工具。
import sun.jvmstat.monitor.*;
import sun.management.ConnectorAddressLink;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class LocalVirtualMachine {
private String address;
private String commandLine;
private String displayName;
private int vmid;
private boolean isAttachSupported;
private String jvmArgs;
public LocalVirtualMachine(int vmid, String commandLine, String jvmArgs, boolean canAttach, String connectorAddress) {
this.vmid = vmid;
this.commandLine = commandLine;
this.jvmArgs = jvmArgs;
this.address = connectorAddress;
this.isAttachSupported = canAttach;
this.displayName = getDisplayName(commandLine);
}
public String getAddress() {
return address;
}
public String getCommandLine() {
return commandLine;
}
public String getDisplayName() {
return displayName;
}
public boolean isAttachSupported() {
return isAttachSupported;
}
public String getJvmArgs() {
return jvmArgs;
}
public int getVmid() {
return vmid;
}
private static String getDisplayName(String commandLine) {
// trim the pathname of jar file if it's a jar
String[] res = commandLine.split(" ", 2);
if (res[0].endsWith(".jar")) {
File jarfile = new File(res[0]);
String displayName = jarfile.getName();
if (res.length == 2) {
displayName += " " + res[1];
}
return displayName;
}
return commandLine;
}
public static Map<Integer, LocalVirtualMachine> getMonitoredVMs() {
Map<Integer, LocalVirtualMachine> map = new HashMap<Integer, LocalVirtualMachine>();
MonitoredHost host;
Set vms;
try {
host = MonitoredHost.getMonitoredHost(new HostIdentifier((String) null));
vms = host.activeVms();
} catch (java.net.URISyntaxException sx) {
throw new InternalError(sx.getMessage());
} catch (MonitorException mx) {
throw new InternalError(mx.getMessage());
}
// LocalMonitoredVm
for (Object vmid : vms) {
if (vmid instanceof Integer) {
int pid = (Integer) vmid;
String name = vmid.toString(); // default to pid if name not available
boolean attachable = false;
String address = null;
String jvmArgs = null;
try {
MonitoredVm mvm = host.getMonitoredVm(new VmIdentifier(name));
// use the command line as the display name
name = MonitoredVmUtil.commandLine(mvm);
jvmArgs = MonitoredVmUtil.jvmArgs(mvm);
attachable = MonitoredVmUtil.isAttachable(mvm);
address = ConnectorAddressLink.importFrom(pid);
mvm.detach();
} catch (Exception x) {
// ignore
}
map.put((Integer) vmid,
new LocalVirtualMachine(pid, name, jvmArgs, attachable, address));
}
}
return map;
}
}
通过遍历getMonitoredVMs()方法的Map中的localVirtualMachine.getDisplayName(),就可以得到当前操作系统中由java启动的所有程序的名称。
好吧,我承认这段代码是抄袭Jconsole的源代码。
综合以上:
final String[] aArray = cmd.split(" ");
if (aArray.length > 0) {
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(new Runnable() {
public void run() {
boolean isRunning = false;
Map<Integer, LocalVirtualMachine> map = LocalVirtualMachine.getMonitoredVMs();
for (LocalVirtualMachine localVirtualMachine : map.values()) {
if (localVirtualMachine.getDisplayName().equals(aArray[aArray.length-1])) {
isRunning = true;
break;
}
}
map.clear();
if (!isRunning) {
new Thread() {
public void run() {
try {
Runtime.getRuntime().exec(cmd);
} catch (final SecurityException ex) {
ex.printStackTrace();
} catch (final IOException ex) {
ex.printStackTrace();
}
}
}.start();
}
}
}, 0, 5, TimeUnit.SECONDS);
}
}
这样做的好处是解除了OPC程序和定时任务服务直接的耦合。
不可否认的是,以上方法依然是治标不治本的方法。到现在为止,还是没有解决EXE4J生成的系统服务中无法调用DLL的问题。甚至问题的根源是不是因为调用了DLL,都不是很有把握。希望达人指点,谢谢!