Java Service Wrapper 使用经验总结
来源:互联网 发布:侠盗飞车作弊软件 编辑:程序博客网 时间:2024/05/19 16:38
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.File和log4j.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.ConsoleAppenderlog4j.appender.stdout.Target = System.outlog4j.appender.stdout.layout = org.apache.log4j.PatternLayoutlog4j.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\u5230log4j.appender.D = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File = E://logs/log.loglog4j.appender.D.Append = truelog4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayoutlog4j.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\u523log4j.appender.E = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.E.File =E://logs/error.log log4j.appender.E.Append = truelog4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayoutlog4j.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 block e.printStackTrace(); log.error(e.getMessage()); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error(e.getMessage()); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error(e.getMessage()); } catch (IOException e) { // TODO Auto-generated catch block e.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() { @Override public boolean accept(File dir, String name) { // TODO Auto-generated method stub try { new SimpleDateFormat("yyyyMMdd").parse(name); return true; } catch (Exception e) { // TODO: handle exception return 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()) { // 不存在返回 false return 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); @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { // TODO Auto-generated method stub log.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 block e.printStackTrace(); log.error(e.getMessage()); } catch (ParseException e) { // TODO Auto-generated catch block e.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 使用经验总结
- Java Service Wrapper 使用
- Java wrapper service 使用
- Java Service Wrapper 使用
- Java Service Wrapper使用总结
- java service wrapper的使用
- Java Service Wrapper使用中的问题
- Java Service Wrapper简介与使用
- Java Service Wrapper简介与使用
- Java Service Wrapper简介与使用 linux
- Java Service Wrapper简介与使用
- Java Service Wrapper-简介与使用
- Java Service Wrapper
- Java Service Wrapper
- java service wrapper集成
- Java Service Wrapper
- Java Service Wrapper
- Java Service Wrapper 详解
- C++基础学习之6 - STL解构
- awk 命令使用字符串分割字符串
- 对于功能需求的一些自我理解
- 创龙TMS320C6748开发板———按键中断学习
- FZU-1082(DFS)
- Java Service Wrapper 使用经验总结
- sqlserver 查询当日、当月数据
- js实现网页刷新后滚动条位置不变
- 创龙TMS320C6748开发板———GPIO配置深入
- [22]CSS 传统布局(下)
- 制作水果忍者-JS-2
- 【微信小程序】事件交互案例演示
- linux-kernel配置命令
- HEVC部分通测序列特点