将Java程序制作成windows系统服务相关
用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,都不是很有把握。希望达人指点,谢谢!