1. 需求背景和工具简介

1.1 需求背景

最近因为公司需要管理上传的附件,准备把过期的文件夹(我们都是在指定目录下以日期8位来规定文件夹命名)移走进行备份,这时候就需要一个定时任务来进行定时检查文件夹的日期是否达到过期标准。针对这样的需求,我就想到用Java实现一个定时任务程序,然后做成windows的服务让其自动运行。关于程序,我花了点时间写了出来并且成功满足了需求,而关于部署成服务,我在网络上找了一些方案,最终决定使用Java Service Wrapper工具来实现。但在实现过程中发现,虽然网络上有很多教程,但最后发现都是不对或者不完整的, 导致花了很多时间也没能实现。最终我还是根据官方的使用介绍一步一步实验踩坑,才部署成功!所以,我打算把实现的过程记录下来,帮助需要的朋友!

1.2 工具简介

Java Service Wrapper(下面简称wrapper)是目前较为流行的将Java程序部署成Windows服务的解决方案(官方地址:http://wrapper.tanukisoftware.com/doc/english/download.jsp)据我了解还有Apache Common Daemon也是很不错的,Tomcat就是利用该工具实现的,有兴趣的朋友可以尝试一下。wrapper使用方面比daemon较为简单一点,对于windows版本,只有32位有社区版本,其他是收费的,该版本也足够我们使用了, 本文将讨论如何使用wrapper把我们的程序打包成WIN服务!

1.3 开发工具

我目前使用的是Eclipse进行开发,当然你可以使用任何其他开发工具,最终我们也只是需要编译后的文件而已!

2. 使用步骤详述

2.1 第一步:下载wrapper工具包

请至官网下载wrapper的工具包http://wrapper.tanukisoftware.com/doc/english/download.jsp,选择下图中标识的版本进行下载,其他windows版本是收费的

下载的文件是压缩包,先解压缩后备用,解压缩后的文件目录结构应该如下图所示:

2.2 第二步:准备我们的程序(如对本程序无兴趣,可直接拷贝或下载源码后阅读2.3)

这里,我就以我这个文件自动管理服务为例,来讲解如何将程序打包成服务,因为本项目正好有使用第三方jar包,可以直观的看出如何处理第三方Jar包。该程序使用了Quartz定时任务库来实现定时任务。首先,我的工程目录结构是这样的:

引用到的第三方jar包是如下这些:

关于Quartz,可去官网下载Jar包:http://www.quartz-scheduler.org/,对于Quartzde使用的具体讲解可以搜索其他博主的博客

我会把我这个程序的代码贴出来给有需要的朋友,这会使博文有点长甚至有点跑题,还请见谅
当然你可以建立同样的工程,相同的目录,然后建立相同的java文件,并拷贝这些代码进行测试

log4j.properties文件我就不多说了,这个文件可以直接拷贝,只需修改log4j.appender.D.Filelog4j.appender.E.File这两个位置的路径即可,不用多做解释

 ### \u8BBE\u7F6E###
log4j.rootLogger = debug,stdout,D,E### \u8F93\u51FA\u4FE1\u606F\u5230\u63A7\u5236\u62AC ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n### \u8F93\u51FADEBUG \u7EA7\u522B\u4EE5\u4E0A\u7684\u65E5\u5FD7\u5230
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n### \u8F93\u51FAERROR \u7EA7\u522B\u4EE5\u4E0A\u7684\u65E5\u5FD7\u523
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

下面我们来实现能够扫描文件夹同时进行处理的类,我使用xml文件配置的方式,能够灵活配置需要扫描文件夹位置、处理方式以及目标文件夹位置:

来看一下src目录下面的fileHandleJobConfig.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 配置需要检查的文件目录,全部目录都配置到日期文件夹的上一层 -->
<root><!-- 配置希望处理的时间间隔,单位是月,即希望将与当前日期相差几个月的文件夹进行处理 --><time_interval><value>3</value></time_interval><!-- 过期文件目录根节点,每一个直接子节点代表一种文件目录,可配置多个 --><old_file_path><!-- 子节点 --><file_path_1><!-- 过期文件目录 --><file_path>E:/TDDOWNLOAD/testdir</file_path><!-- 标记:希望程序对旧的文件实行的处理是什么,move表示复制、delete表示直接删除,md表示剪切,如果为空,则默认是move --><operate>md</operate><!-- 如果处理标记为:move,则该节点表示希望移动到哪个位置,必须提供该节点值 --><new_file_path>E:/TDDOWNLOAD/newtestdir</new_file_path></file_path_1></old_file_path>
</root>

静态常量的定义 : StaticFieldValue.java,用来比较配置的操作类型:move delete md

package com.op.quartz.job;/*** 一些静态常量的定义* * @author Ivy**/
public class StaticFieldValue {// 表示操作类型的移动后不删除public static final String OPERATE_TYPE_MOVE = "MOVE";// 表示操作类型的删除public static final String OPERATE_TYPE_DELETE = "DELETE";// 表示操作类型的移动后删除public static final String OPERATE_TYPE_MD = "MD";}

其次,实现文件夹扫描类,能够读取上面的配置文件并进行相应处理 : FileHandler.java

package com.op.quartz.job;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;/*** * 过期文件处理工具类* * 逻辑:根据fileHandleJobConfig.xml文件里面的配置进行处理* * @author Johnny.Ji**/
public class FileHandler {// 设置Log4j,每一步的操作必须有日志private static Logger log = Logger.getLogger(FileHandler.class);// 线程安全的缓存池private static ThreadLocal<FileHandler> threadLocal = new ThreadLocal<>();// 单例模式的对象构建器public static FileHandler getInstance() {FileHandler fileHandler = threadLocal.get();if (fileHandler == null) {fileHandler = new FileHandler();threadLocal.set(fileHandler);}return fileHandler;}/*** 对外提供的用来启动程序的统一的入口* * */public void handle() {try {readXML();} catch (DocumentException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());} catch (ParseException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());}}/*** 读取xml文件配置,根据配置进行过期文件处理* * @throws DocumentException* @throws ParseException* @throws IOException* */@SuppressWarnings("unchecked")private void readXML() throws DocumentException, ParseException, IOException {// 构建xml文件读取器对象SAXReader saxReader = new SAXReader();// 读取文件,形成文档对象Document document = saxReader.read(this.getClass().getResourceAsStream("/fileHandleJobConfig.xml") );// 获得文档根节点Element rootElement = document.getRootElement();// 根据根节点获取直接子节点元素Element old_file_path_element = rootElement.element("old_file_path");// <old_file_path>节点Element time_interval_element = rootElement.element("time_interval");// 时间间隔节点// 检查文档的配置是否正确,如果不正确则进行log显示if (time_interval_element == null) {log.info("配置错误:" + rootElement.getName() + "节点下未配置<time_interval>节点");return;}if (old_file_path_element == null) {log.info("配置错误:" + rootElement.getName() + "节点下未配置<old_file_path>节点");return;}// 取出用户希望的时间间隔Element interval_value_element = time_interval_element.element("value");if (interval_value_element == null) {log.info("配置错误:" + time_interval_element.getName() + "节点下未配置<value>节点");return;}// old_file_path节点下面,就是每一个文件夹的路径,我们需要遍历这个节点下面的每一个子节点Iterator<Element> iterator = old_file_path_element.elementIterator();while (iterator.hasNext()) {Element childElement = iterator.next();// 每一个子节点,这里每一个子节点都是一个文件地址// 根据取出来的每一个节点,再次在此节点上进行遍历内部节点Element filePathElement = childElement.element("file_path");// 取出文件路径节点Element operateElement = childElement.element("operate");// 取出操作类型节点Element newFilePathElement = childElement.element("new_file_path");// 取出希望移动到新的位置的节点// 检查文档的结构是否符合要求if (filePathElement == null) {log.info("配置错误:" + childElement.getName() + "节点下未配置<file_path>节点");return;}if (operateElement == null) {log.info("配置错误:" + childElement.getName() + "节点下未配置<operate>节点");return;}if (newFilePathElement == null) {log.info("配置错误:" + childElement.getName() + "节点下未配置<new_file_path>节点");return;}// 如果文件路径节点值为空,则放弃此次的操作,继续下一次操作if (filePathElement.getText() == null || filePathElement.getText().equals("")) {log.info("配置错误:" + filePathElement.getName() + "节点下的<file_path>节点值为空");} else {// 如果不为空,则说明是可以进行操作的,那么再次取出<operate></operate>节点// 如果配置了<operate>节点,则检查值是否是move或者md,如果是的话,则<new_file_path>节点值不能为空if (operateElement.getText() != null && !operateElement.getText().equals("")&& operateElement.getText().toUpperCase().equals(StaticFieldValue.OPERATE_TYPE_DELETE)) {// 如果配置的<operate>是delete,则说明是直接删除的// 检查这个文件路径下面的所有的文件夹的日期距离现在的日期是否满足用户设置的间隔handleFilePath(filePathElement.getText(), operateElement.getText(), newFilePathElement.getText(),interval_value_element.getText());} else {// 否则说明应该是移动,这时就要检查<new_file_path>节点值是否为空if (newFilePathElement.getText() == null || newFilePathElement.getText().equals("")) {log.info("配置错误:" + filePathElement.getName() + "节点下的<new_file_path>节点值为空");} else {// 否则说明可以进行数据迁移handleFilePath(filePathElement.getText(), operateElement.getText(),newFilePathElement.getText(), interval_value_element.getText());}}}}}/*** 根据传入的文件旧路径、操作类型、新路径(如果有的话)进行文件处理* * @param old_path*            文件旧路径* @param operate*            操作类型* @param new_path*            新路径* @param interval_value*            用户希望执行的时间间隔处理* @throws ParseException* @throws IOException*/private void handleFilePath(String old_path, String operate, String new_path, String interval_value)throws ParseException, IOException {// 转换路径String oldPath = old_path.replaceAll("\\\\", "/");String newPath = new_path.replaceAll("\\\\", "/");// 根据传入的文件路径构建文件对象File file = new File(oldPath);// 如果这个路径存在的话,则进入该路径下面,列出全部的子文件夹if (file.exists()) {// 如果存在,则取出该文件夹下面的所有的子文件夹名称,应该名称都是日期的8位表示// 过滤掉不是日期8位表示的文件夹String fileNames[] = file.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {// TODO Auto-generated method stubtry {new SimpleDateFormat("yyyyMMdd").parse(name);return true;} catch (Exception e) {// TODO: handle exceptionreturn false;}}});for (String fileName : fileNames) {if (compareDate(fileName, interval_value)) {// 说明需要处理// 先处理路径的\的问题,让其能够适用于跨平台String oldDirPath = oldPath.endsWith("/") ? oldPath + fileName : oldPath + "/" + fileName;String newDirPath = newPath.endsWith("/") ? newPath + fileName : newPath + "/" + fileName;// 执行文件处理handleFiles(oldDirPath, newDirPath, operate);// 处理完成之后,如果operate是md,则需要删除if (operate != null && operate.toUpperCase().equals(StaticFieldValue.OPERATE_TYPE_MD)) {File delFile = new File(oldDirPath);if (delFile.exists()) {deleteFolder(oldDirPath);}}}}} else {log.info("配置的文件路径:" + old_path + "不存在");}}/*** 比较文件夹时间和当前时间差是否超过配置的月数* * @param dirDateStr*            文件夹日期* @param interval_value*            间隔时间* @return 返回是否符合要求的boolean值* @throws ParseException*/private boolean compareDate(String dirDateStr, String interval_value) throws ParseException {// 将日期字符串转换成日期对象Date dirDate = new SimpleDateFormat("yyyyMMdd").parse(dirDateStr);Calendar calendar = Calendar.getInstance();calendar.setTime(dirDate);int dirYear = calendar.get(Calendar.YEAR);// 取出文件夹日期的年份int dirMonth = calendar.get(Calendar.MONTH);// 取出文件夹日期的月份int dirDay = calendar.get(Calendar.DAY_OF_MONTH);// 取出文件夹日期的天// 再取出当前日期的相关信息calendar.setTime(new Date());int nowYear = calendar.get(Calendar.YEAR);// 取出当前日期的年份int nowMonth = calendar.get(Calendar.MONTH);// 取出当前日期的月份int nowDay = calendar.get(Calendar.DAY_OF_MONTH);// 取出当前日期的天// 这里的判断规则是这样的,如果两个日期的年份是一样的,则只计算月份之间的差,如果大于等于配置的月份数,则说明符合要求// 如果两个日期不在同一个年份内,则分别计算两个日期距离年尾和年首的月份数,再相加// 说明两个时间是同一个年份之内if (dirYear == nowYear) {if ((nowMonth - dirMonth) >= Integer.parseInt(interval_value)) {// 如果月份间隔大于等于配置的时间,则说明是可能符合的,但如果是等于的情况下,还得看天数if ((nowMonth - dirMonth) == Integer.parseInt(interval_value)) {// 如果等于,则看当前日期中天数是否大于等于文件夹天数,如果是的话,则满足要求if (nowDay >= dirDay) {return true;} else {return false;}} else {// 如果是大于的情况下,则可以直接操作return true;}} else {return false;}} else {// 如果不在同一个年份内,则先计算年份是否差一,是的话,说明是正常的,需要进行计算,如果大于1,则说明是之前的遗留文件,是符合的if (nowYear - dirYear == 1) {int dirMonthDiff = 12 - dirMonth;// 文件夹日期距离年尾的月份数量int nowMonthDiff = nowMonth - 0;// 当前日期距离年首的月份数if (nowMonthDiff + dirMonthDiff >= Integer.parseInt(interval_value)) {// 如果总数大于等于配置的月数,则看等于的情况下if (nowMonthDiff + dirMonthDiff == Integer.parseInt(interval_value)) {if (nowDay >= dirDay) {return true;} else {return false;}} else {return true;}}} else {return true;}}return false;}/*** 对文件进行处理,删除文件/文件夹 或者 移动文件/文件夹到新的文件地址* * @param oldFileDir*            旧路径* @param newFileDir*            新路径* @param operate*            操作类型* @throws IOException*/private void handleFiles(String oldDirPath, String newDirPath, String operate) throws IOException {File oldPathFile = new File(oldDirPath);if (operate != null && operate.toUpperCase().equals(StaticFieldValue.OPERATE_TYPE_DELETE)) {// 说明要求删除此文件夹if (oldPathFile.exists()) {// 如果路径有效,则执行删除deleteFolder(oldDirPath);}} else {// 说明需要移动文件夹到指定的目录下面File newPathFile = new File(newDirPath);if (newPathFile.exists()) {// 如果已经存在,则基于该文件夹直接将旧文件夹下面的文件拷贝到该处// 读取旧文件夹下面的文件moveFiles(oldPathFile, newPathFile, operate);} else {// 如果当前文件夹不存在,则需要先创建文件夹if (newPathFile.mkdirs()) {// 成功创建文件夹之后// 如果已经存在,则基于该文件夹直接将旧文件夹下面的文件拷贝到该处// 读取旧文件夹下面的文件moveFiles(oldPathFile, newPathFile, operate);} else {log.info("文件夹创建失败,路径名称:" + newDirPath);}}}}/*** 单独的[移动文件/文件夹]的方法* * @param files* @param newPath* @param operate* @throws IOException*/private void moveFiles(File oldPathFile, File newPathFile, String operate) throws IOException {// 取出旧路径下面的所有的文件对象File files[] = oldPathFile.listFiles();// 循环处理for (File file : files) {if (file.isDirectory()) {// 如果是文件夹,则算作路径,在新地址里面检查是否存在,不存在则直接创建handleFiles(file.getAbsolutePath(), newPathFile.getAbsolutePath() + "/" + file.getName(), operate);} else {// 如果不是文件夹,则直接读取该文件到新的地址// 判断该文件在新地址下面是否已经存在File currFile = new File(newPathFile.getAbsolutePath() + "/" + file.getName());if (currFile.exists()) {log.info("相同名称文件已存在:" + currFile.getName() + ",未执行写入操作!");} else {readFileToPath(file, newPathFile.getAbsolutePath() + "/" + file.getName());}}}}/*** 根据路径,将文件读取到新地址* * @param oldFilePath*            旧文件路径* @param newFilePath*            新文件路径* @throws IOException*/private void readFileToPath(File oldFile, String newFilePath) throws IOException {// 构建流对象FileInputStream fileInputStream = new FileInputStream(oldFile);BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);FileOutputStream fileOutputStream = new FileOutputStream(new File(newFilePath));BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);byte[] bytes = new byte[2048];// 字节数组,存储读取的数据int len = 0;while ((len = bufferedInputStream.read(bytes)) != -1) {bufferedOutputStream.write(bytes, 0, len);bufferedOutputStream.flush();}if (fileInputStream != null) {fileInputStream.close();}if (bufferedInputStream != null) {bufferedInputStream.close();}if (fileOutputStream != null) {fileOutputStream.close();}if (bufferedOutputStream != null) {bufferedOutputStream.close();}}/*** 删除目录(文件夹)以及目录下的文件* * @param filePath*            被删除目录的文件路径* @return 目录删除成功返回true,否则返回false*/private boolean deleteDirectory(String filePath) {// 如果filePath不以文件分隔符结尾,自动添加文件分隔符if (!filePath.endsWith(File.separator)) {filePath = filePath + File.separator;}File dirFile = new File(filePath);// 如果dir对应的文件不存在,或者不是一个目录,则退出if (!dirFile.exists() || !dirFile.isDirectory()) {return false;}boolean flag = true;// 删除文件夹下的所有文件(包括子目录)File[] files = dirFile.listFiles();for (int i = 0; i < files.length; i++) {// 删除子文件if (files[i].isFile()) {flag = deleteFile(files[i].getAbsolutePath());if (!flag)break;} // 删除子目录else {flag = deleteDirectory(files[i].getAbsolutePath());if (!flag)break;}}if (!flag)return false;// 删除当前目录if (dirFile.delete()) {return true;} else {return false;}}/*** 根据路径删除指定的目录或文件,无论存在与否* * @param filePath*            要删除的目录或文件* @return 删除成功返回 true,否则返回 false。*/private boolean deleteFolder(String filePath) {boolean flag = false;File file = new File(filePath);// 判断目录或文件是否存在if (!file.exists()) { // 不存在返回 falsereturn flag;} else {// 判断是否为文件if (file.isFile()) { // 为文件时调用删除文件方法return deleteFile(filePath);} else { // 为目录时调用删除目录方法return deleteDirectory(filePath);}}}/*** 删除单个文件* * @param filePath*            被删除文件的文件名* @return 单个文件删除成功返回true,否则返回false*/private boolean deleteFile(String filePath) {boolean flag = false;File file = new File(filePath);// 路径为文件且不为空则进行删除if (file.isFile() && file.exists()) {file.delete();flag = true;}return flag;}
}

下面实现Quartz定时任务中的任务类,就是定时执行时需要执行的类 : AutoFileJob.java

package com.op.quartz.job;import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;/*** 定时任务的具体执行内容* * 本程序执行内容:检查配置的文件目录下,所有超过两个月的文件夹全部移除到其他位置* * @author Johnny.Ji**/
public class AutoFileJob implements Job {// 设置Log4j,每一步的操作必须有日志private static Logger log = Logger.getLogger(AutoFileJob.class);@Overridepublic void execute(JobExecutionContext arg0) throws JobExecutionException {// TODO Auto-generated method stublog.info("定时任务启动:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));//调用我们的文件处理类方法进行执行FileHandler.getInstance().handle();}
}

任务管理类的实现,这个就是任务定时执行的关键类,用来管理我们的任务并根据设置定时执行 QuartzJobManager.java

package com.op.quartz.scheduler;import java.text.ParseException;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;/** */
/*** @Title:Quartz任务管理类* @Description:* @Copyright:* @version 1.00.000* */
public class QuartzJobManager {private static SchedulerFactory sf = new StdSchedulerFactory();private static String JOB_GROUP_NAME = "group1";private static String TRIGGER_GROUP_NAME = "trigger1";/*** 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名* * @param jobName*            任务名* @param job*            任务* @param time*            时间设置,参考quartz说明文档* @throws SchedulerException* @throws ParseException*/public static void addJob(String jobName, Job job, String time) throws SchedulerException, ParseException {Scheduler sched = sf.getScheduler();JobDetail jobDetail = JobBuilder.newJob(job.getClass()).withIdentity(jobName, JOB_GROUP_NAME).build();// 任务名,任务组,任务执行类// 触发器CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, TRIGGER_GROUP_NAME).withSchedule(CronScheduleBuilder.cronSchedule(time)).startNow().build();// 触发器名,触发器组sched.scheduleJob(jobDetail, trigger);// 启动if (!sched.isShutdown())sched.start();}/** *//*** 添加一个定时任务* * @param jobName*            任务名* @param jobGroupName*            任务组名* @param triggerName*            触发器名* @param triggerGroupName*            触发器组名* @param job*            任务* @param time*            时间设置,参考quartz说明文档* @throws SchedulerException* @throws ParseException*/public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Job job,String time) throws SchedulerException, ParseException {Scheduler sched = sf.getScheduler();JobDetail jobDetail = JobBuilder.newJob(job.getClass()).withIdentity(jobName, jobGroupName).build();// 任务名,任务组,任务执行类// 触发器CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName).withSchedule(CronScheduleBuilder.cronSchedule(time)).startNow().build();// 触发器名,触发器组sched.scheduleJob(jobDetail, trigger);if (!sched.isShutdown())sched.start();}
}

最后就是我们的主程序的可执行入口类:ServiceMain.java

package com.op.service.main;import java.text.ParseException;
import org.apache.log4j.Logger;
import org.quartz.SchedulerException;
import com.op.quartz.job.AutoFileJob;
import com.op.quartz.scheduler.QuartzJobManager;public class ServiceMain{private static Logger log = Logger.getLogger(ServiceMain.class);public static void main(String[] args) {AutoFileJob job = new AutoFileJob();try {//0/10 * * * * ?  表示没每10秒执行一次QuartzJobManager.addJob("11", job, "0/10 * * * * ?");} catch (SchedulerException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());} catch (ParseException e) {// TODO Auto-generated catch blocke.printStackTrace();log.error(e.getMessage());}}
}

2.3 对我们的程序进行部署,形成Win服务

2.3.1 第一步:准备执行环境

在电脑的某一个位置(位置随意)新建一个文件夹,如:ServicePack,然后在该文件夹内分别建立如下几个子文件夹:

然后执行下面一系列的拷贝操作:
1、将wrapper工具包中bin文件夹下的wrapper.exe拷贝到ServicePack文件夹下的bin文件夹中
2、将wrapper工具包中lib文件夹下的wrapper.jar、wrapper.dll拷贝到ServicePack文件夹下的lib文件夹中
3、将wrapper工具包中src/conf文件夹下的wrapper.conf.in拷贝到ServicePack的conf文件夹中,并重命名为wrapper.conf

4、在ServicePack下的lib文件夹中新建classes文件夹,形成如下结构

5、在ServicePack/lib/classes文件夹内,我们新建lib文件夹,然后把我们使用到的第三方Jar包放到该文件夹下:

6、我们都知道,Eclipse是自动编译的,生成的编译文件在工程目录下面的bin文件夹内,所以,我们在
ServicePack/lib/classes文件夹内新建文件夹,名称就是工程名称OpAutoFileService,形成如下结构,然后把我们的工程目录下的bin文件夹内的文件拷贝到该目录下(ServicePack/lib/classes/OpAutoFileService)


7 、最后由于我们下载的32位的wrapper,所以需要配合32位jre才能正常使用,当然工程编译也需要使用1.7的jdk进行编译!而且个人比较推荐拷贝独立的jre,这样就可以在电脑未安装jdk的情况下使用,更加方便!所以我们需要拷贝一份jre到ServicePack目录下,我使用的1.7版本的jre,我会在最后提供出来!

拷贝任务到此结束

2.3.2 第二步: 修改配置文件

我们需要修改ServicePack/conf下的wrapper.conf文件,才能够让wrapper真正起作用:

1、 因为我们使用独立的jre环境,所以要指定我们的jre路径,修改的位置和修改后的值如下图所示:

2、需要指定wrapper官方的jar包、我们编译后的工程文件路径、我们引入的第三方jar包路径:

需要注意的是:wrapper.java.classpath.1的序号1、2、3……是递增的,千万不可重复

3、指定工程的主程序入口类,路径从包名称开始:

4、指定在控制台执行时显示的名称(可选):

5、编辑服务的名称、描述等信息:

至此,配置文件已经修改完毕,下面我们就来尝试进行部署:

2.3.3 第三步 部署我们的程序到服务

Java Wrapper Service有两种方式运行我们的服务,一种是在控制台运行,另外一种就是安装成windows服务。个人比较推荐在正式安装成windows服务之前,用控制台方式测试我们的配置是否能够正常运行,打开CMD控制台,切换到ServicePack目录,然后输入一下命令后回车:

如果控制台打印出程序运行的信息,则说明运行成功了,否则会有报错信息出现:

执行成功之后,我们就可以执行下面这样的命令来把我们的配置安装成windows服务了:

成功后会看到控制台打印出: wrapper | OpAutoFileService service installed

然后到windows的服务里面查看是否存在名称为:OpAutoFileService的服务,默认安装完成之后是未启动状态:

找到后启动该服务,如果一切正常,服务是可以正常启动运行的:

如果启动失败,则可以到ServicePack/logs下查看日志文件寻找错误原因

如果希望卸载服务,则可以在控制输入bin\wrapper.exe -r ..\conf\wrapper.conf 然后回车,服务即可成功卸载:


至此,关于Java Service Wrapper 的使用方式已经介绍完毕,当然我的介绍是基于最简单的配置,还有很多的高级配置可以使用,有兴趣的可以参考官方文档!


[1]: http://wrapper.tanukisoftware.com/doc/english/qna-service.html “Wrapper官方文档”
[2]: http://blog.csdn.net/lotusyangjun/article/details/6450421/ “定时任务Quartz教程”
[3]: http://download.csdn.net/detail/johnnydotji/9736381 “本博文中的源码”
[4]: http://download.csdn.net/detail/johnnydotji/9737278 “32位版本Jre,版本号1.7.0_80”

Java Service Wrapper 使用经验总结相关推荐

  1. java service wrapper日志参数设置及优化

    一般在容器比如tomcat/weblogic中运行时,我们都是通过log4j控制日志输出的,因为我们现在很多服务端使用java service wrapper(至于为什么使用jsw,原先是比较排斥使用 ...

  2. java 压缩jar 仓库,java服务安装(一):使用java service wrapper及maven打zip包

    tags: java jsw maven zip 1.概述 使用java开发程序,在windows平台下,一般有web应用,后台服务应用,桌面应用: web应用多数打成war包在web容器(如tomc ...

  3. JSW Java_java服务安装(一):使用java service wrapper及maven打zip包

    1.概述 使用java开发程序,在windows平台下,一般有web应用,后台服务应用,桌面应用: web应用多数打成war包在web容器(如tomcat,jetty等)中运行 桌面应用一般打成jar ...

  4. java service wrapper jar 服务_javaservice wrapper 实现注册服务功能

    2.例子1 a.创建HelloWorld_HOME文件夹,在下面编写例子程序HelloWorld.java public class HelloWorld { public static void m ...

  5. Java Service Wrapper 发布Java程序为Windows服务

    下载Windows版本:https://www.krenger.ch/blog/java-service-wrapper-3-5-37-for-windows-x64/ 转自:F:\java\bhGe ...

  6. Java Service Wrapper将java程序设置为服务

    有时候我们希望我们java写的程序作为服务注册到系统中,Java Service Wrapper(下面简称wrapper)是目前较为流行的将Java程序部署成Windows服务的解决方案, 本文将讨论 ...

  7. java service wrapper导致内存剧增直至崩溃

    应用程序使用wrapper包装之后导致内存溢出,直至应用程序崩溃. 现象: java应用程序在运行一段时间后把服务器内存耗光,程序死掉,但是守护进程wrapper还活着.虽然java应用程序死了,但是 ...

  8. Java Service Wrapper

    http://blog.csdn.net/coolcoffee168/article/details/9980009 转载于:https://www.cnblogs.com/diyunpeng/p/6 ...

  9. java windows wrapper_Java Service Wrapper 使用(windows)

    1       简介 最近项目中需要做一个Windows系统服务,记录一下使用过程. Java Service Wrapper 可以将Java程序包装成系统服务,这样就可以随着系统的运行而自动运行.J ...

最新文章

  1. 【Ubuntu】ubuntu设置GUI程序自启动
  2. 如何利用 C# 实现 K 最邻近算法?
  3. 最重要的事情 一 、消息通信机制(1)ant 打包方法(2) system.out.println()用法 二、UML学习
  4. C1000k 新思路:用户态 TCP/IP 协议栈
  5. 重新考虑数据中心的冷却方案—水冷却或将再次兴起
  6. 让对方ping不通你的主机
  7. 再谈 Java中Runnable和Thread的区别
  8. activex控件有什么用_你知道怎样用Excel打印「条形码」吗?
  9. Eclipse扩展点评估变得容易
  10. 送货只服京东“特快送”:航空快件可送达近300个城市
  11. CnBlogs自定义博客样式
  12. c语言写贪吃蛇什么水平_细致的C语言写贪吃蛇教程+详细思路-适合新手附源码...
  13. ffmpeg中的sws_scale算法性能测试
  14. Arch Linux 安装 Virtualbox 4.2.0 备忘录
  15. 详解串行通信协议及其FPGA实现
  16. android camera API1调用camera HAL3流程学习总结
  17. 中学生信息技术计算机软件教案,初中信息技术教学计划
  18. 多元统计分析 多元线性回归 python代码实现 简单线性回归
  19. 行列式与矩阵相关与应用
  20. 【实战技能】Google I/O 2022大会AI/ML给开发者的启发

热门文章

  1. pygame制作简单小游戏
  2. How to reduce bias and variance ?
  3. Android App 退出整个应用
  4. 将来我一定将他(科比)讲给你听!特别是你在遇到坎坷,感到迷茫的时候!
  5. 通过token窃取实现降权或者提权
  6. project子项目之间任务关联_任务关联的类型(Project)
  7. python简单加密算法_如何制作一个简单的加密/解密程序?
  8. 北京2008年第29届奥运会吉祥物――福娃
  9. pq grid 及 一些基本方法
  10. 惊喜or惊吓?开发者眼中的苹果新品发布会