Java定时任务&推送数据实例

本文使用jdk自带工具实现

依赖jar包

# 怕不兼容jdk1.6使用老版本jar
commons-beanutils-1.6.jar
commons-collections-3.2.1.jar
commons-lang-2.1.jar
commons-logging-1.1.1.jar
ezmorph-1.0.1.jar
ezmorph-1.0.2.jar
json-lib-2.4-jdk15.jar
mysql-connector-java-5.0.5-bin.jar

HttpURLConnection

// 使用JDK自带Timer 和 HttpURLConnection 多任务使用ScheduledExecutorService
public static void main(String[] args) {

	Timer timer = new Timer();
	timer.schedule(new TimerTask() {
		public void run() {

			List userList = new ArrayList();
			List orgList = new ArrayList();

			try {
				String URL="jdbc:mysql://localhost/data?characterEncoding=utf-8";
				String USER="data";
				String PASSWORD="data";
				// 1.加载驱动程序
				Class.forName("com.mysql.jdbc.Driver");
				// 2.获得数据库连接
				Connection conn=DriverManager.getConnection(URL, USER, PASSWORD);
				// 3.通过数据库的连接操作数据库 实现增删改查(使用Statement类)
				Statement st=conn.createStatement();

				// ---用户信息开始
				ResultSet userRs=st.executeQuery(
						"SELECT" +
								" EMP_ID" + // 用户id
								" ,ACCOUNT" + // 用户账户
								" ,ORG_ID" + // 用户部门id
								" ,EMPNAME" + // 用户名
								" ,EMP_PASSWORD" + // 密码
								" ,ISDELETE" + // 用户是否删除 0未删除 1已删除
								" FROM" +
								" ORG_EMPLOYEE");
				// 4.处理数据库的返回结果(使用ResultSet类)
				userList = new ArrayList();
				while (userRs.next()) {
					Map user = new HashMap();
					user.put("id", userRs.getString("EMP_ID"));
					user.put("loginName", userRs.getString("ACCOUNT"));
					user.put("userName", userRs.getString("EMPNAME"));
					user.put("password", userRs.getString("EMP_PASSWORD"));
					user.put("userCateId", userRs.getString("ORG_ID"));
					userList.add(user);
				}
				userRs.close(); // 关闭资源
				// ---用户信息结束

				// ---部门信息开始
				ResultSet orgRs = st.executeQuery(
						"SELECT" +
						 " ORG_ID" +
						 " ,ORGNAME" +
						 " ,ORGPARENTORGID" +
						 " FROM" +
						 " ORG_ORGANIZATION");
				// 4.处理数据库的返回结果(使用ResultSet类)
				while (orgRs.next()) {
					Map org = new HashMap();
					org.put("id", orgRs.getString("ORG_ID"));
					org.put("cateName", orgRs.getString("ORGNAME"));
					org.put("parentId", orgRs.getString("ORGPARENTORGID"));
					orgList.add(org);
				}
				orgRs.close(); // 关闭资源
				// ---部门信息结束

				st.close(); // 关闭资源
				conn.close(); // 关闭资源
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (SQLException e) {
				e.printStackTrace();
			}

			// 可直接在url上输入参数 中文要编码成UTF-8的字节数组后转成String
			// String urlPath = new String("http://localhost:8080/data_sync?data=数据".getBytes("UTF-8"));
			String urlPath = "http://localhost:8080/data_sync";

			// 调用端如不自动解码 则可通过如下方式编码
			// URLEncoder.encode(userList.toString(), "UTF-8")
			// 调用端使用如下解码
			// URLEncoder.decode(userList.toString(), "UTF-8")

			String param  = "userList=" + JSONArray.fromObject(userList).toString()
					+ "&orgList=" + JSONArray.fromObject(orgList).toString()
					+ "&token=t$o$k$e$n";
			try {
				// 服务地址
				URL url = new URL(urlPath);
				// 设定连接的相关参数
				HttpURLConnection connection = (HttpURLConnection) url.openConnection();
				// 设置是否向HttpURLConnection输出 post请求参数要放在http正文内需设为true 默认false
				connection.setDoOutput(true);
				// 设置请求方法类型为 默认为GET 此处必须为大写
				connection.setRequestMethod("POST");
				// 设置连接超时
	            connection.setConnectTimeout(5000);
	            // 设置读取超时
	            connection.setReadTimeout(5000);
	            // 开始连接 connection.getOutputStream()已包含此操作
	            // connection.connect();
				// 如果响应内容乱码在此处添加编码名称 new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
				OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
				// 发送参数
				out.write(param);
				out.flush();
				out.close();
				// 获取服务端的反馈
				String strLine = "";
				String strResponse = "";
				InputStream in = connection.getInputStream();
				BufferedReader reader = new BufferedReader(new InputStreamReader(in));
				while((strLine = reader.readLine()) != null) {
					strResponse += strLine + "\n";
				}
				System.out.println(strResponse);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("已经执行" + new SimpleDateFormat ("HH:mm:ss").format (new Date()));
		}
	}, 1000, 1000 * 60); // 此时为一分钟执行一次 每次延迟一秒执行 为下面最后一种写法
	// schedule(TimerTask task, Date time) 在指定的时间执行某任务
	// schedule(TimerTask task, Date firstTime, long period) 在指定的时间执行某任务并进行重复的某时间延迟执行
	// schedule(TimerTask task, long delay) 在指定的某时间延迟执行某任务
	// schedule(TimerTask task, long delay, long period) 在指定的某时间延迟执行某任务并进行重复的某时间延迟执行
}

调用方为Spring的Controller

@Controller
public class DataSyncController {

    @Autowired
    private SysUserService userService;

    @Autowired
    private SysUserCateService userCateService;

    @ResponseBody
    @PostMapping("data_sync")
    public boolean test(String token, String userList, String orgList) {

        if (!token.equals("t$o$k$e$n")) {
            return false;
        }

        try {
            JSONArray userJson =  JSONArray.fromObject(userList); // 获取data字段
            JSONArray orgJson =  JSONArray.fromObject(orgList); // 获取data字段

            List<SysUser> users = JSONArray.toList(userJson, new SysUser(), new JsonConfig());
            List<SysUserCate> userCates = JSONArray.toList(orgJson, new SysUserCate(), new JsonConfig());

            userService.syncData(users);
            userCateService.sysnData(userCates);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

窗口运行

java -jar DataSync.jar

后台运行

# 运行java命令时 会出现并保持一个console窗口 程序中的信息可以通过System.out在console内输出
# 而运行javaw 开始时会出现console 当主程序调用之后 console就会消失 javaw大多用来运行GUI程序
# @echo off执行后 后面所有的命令均不显示 包括本条命令
# echo off执行后 后面所有的命令均不显示 但显示本条命令
# start 在新的窗口打开
@echo off
start "data-sync" javaw -jar DataSync.jar
exit # exit退出窗口命令可以省略
# 0 标准输入 一般是键盘
# 1 标准输出 一般是显示屏 是用户终端控制台
# 2 标准错误 错误信息输出
# 将运行的jar 错误日志信息输出到log.file文件中 然后 >&1就是继续输出到标准输出
#前面加的& 是为了让系统识别是标准输出 最后一个& 表示在后台运行
nohup java -jar DataSync.jar > log.file 2 > &1&
# 不指定日志配置可以简写如下 默认输出被重定向到nohup.out文件
nohup java -jar DataSync.jar&

Timer的多任务执行缺陷

public class TimerTest {
    
    private static long start;

    public static void main(String[] args) throws Exception {

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {

                System.out.println("task1 invoked ! "
                        + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    }
}
定义了两个任务 预计是第一个任务1s后执行 第二个任务3s后执行 但是看运行结果
task1 invoked ! 1000
task2 invoked ! 4000

task2实际4后执行 Timer内部是一个线程 而任务1所需的时间超过了两个任务间的间隔导致

使用ScheduledThreadPool定时运行多任务

public class ScheduledThreadPoolExecutorTest {
    
    private static long start;

    public static void main(String[] args) {
        // 使用工厂方法初始化一个ScheduledThreadPool 
        ScheduledExecutorService newScheduledThreadPool = Executors
                .newScheduledThreadPool(2);

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                try {

                    System.out.println("task1 invoked ! "
                            + (System.currentTimeMillis() - start));
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        start = System.currentTimeMillis();
        newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
        newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
    }
}
输出结果
	task1 invoked ! 1001
	task2 invoked ! 3001
ScheduledThreadPool内部是个线程池 可以支持多个任务并发执行

Timer的抛出异常时执行缺陷

public class ScheduledThreadPoolTest {


    public static void main(String[] args) throws InterruptedException {

        final TimerTask task1 = new TimerTask() {

            @Override
            public void run() {
                throw new RuntimeException();
            }
        };

        final TimerTask task2 = new TimerTask() {

            @Override
            public void run() {
                System.out.println("task2 invoked!");
            }
        };

        Timer timer = new Timer();
        timer.schedule(task1, 100);
        timer.scheduleAtFixedRate(task2, new Date(), 1000);
    }
}
上面两个任务 任务1抛出一个运行时的异常 任务2周期性的执行某个操作 输出结果
task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
	at com.java.ScheduledThreadPoolTest$1.run(ScheduledThreadPoolDemo01.java:24)
	at java.util.TimerThread.mainLoop(Timer.java:512)
	at java.util.TimerThread.run(Timer.java:462)

任务1抛出异常导致任务2也停止运行

使用ScheduledExecutorService抛异常运行多任务

public class ScheduledThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {

        final TimerTask task1 = new TimerTask() {

            @Override
            public void run() {
                throw new RuntimeException();
            }
        };

        final TimerTask task2 = new TimerTask() {

            @Override
            public void run() {
                System.out.println("task2 invoked!");
            }
        };

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
        pool.scheduleAtFixedRate(task2, 0, 1000, TimeUnit.MILLISECONDS);

    }
}
代码基本一致 但是ScheduledExecutorService可以保证 task1出现异常时 不影响task2的运行
task2 invoked!
task2 invoked!
task2 invoked!

Timer执行周期任务时依赖系统时间 如果当前系统时间发生变化会出现一些执行上的变化

ScheduledExecutorService基于时间的延迟 即相对时间 不会由于系统时间的改变发生执行变化


SpringBoot自带定时任务

@EnableScheduling // 开启对定时任务支持
@SpringBootApplication
public class DmsAdminApplication{
    public static void main(String[] args) {
        SpringApplication.run(DmsAdminApplication.class, args);
    }
}
@Component
// 启动类写了如下注解 此处可省略书写
@EnableScheduling
public class TestTask {

	// 配置毫秒数
    @Scheduled(fixedRate = 1000) // 毫秒
    public void reportCurrentTime() {
        System.out.println ("已经执行" + new SimpleDateFormat ("HH:mm:ss").format (new Date()));
    }

	// cron表达式写法
    // @Scheduled(cron = "* * * * *") // 每1分钟执行一次
    // @Scheduled(cron = "*/1 * * * *") // 每1分钟执行一次
    @Scheduled(cron = "0 */2 * * *") // 每两个小时执行一次
    public void reportCurrentByCron() {
        System.out.println ("已经执行" + new SimpleDateFormat ("HH:mm:ss").format (new Date ()));
    }
}

cron表达式有7个域 依序分别为 秒 分 时 日 月 周 年 年为可选类型 在不设定年分时为每年

cron字符描述

字符 描述
* 匹配所有的值 如 *在分钟的字段域里表示 每分钟
? 只在日期域和星期域中使用 它被用来指定非明确的值
- 指定范围 如 10-12在小时域意味着10点、11点、12点
, 指定多个值 如 MON WED FRI在星期域里表示星期一 星期三 星期五
/ 指定增量 如 */1 * * * *每1分钟执行一次
L 表示day-of-month和day-of-week域 但在两个字段中的意思不同 例如day-of-month域中表示一个月的最后一天 如果在day-of-week域表示’7’或者’SAT’ 如果在day-of-week域中前面加上数字 它表示一个月的最后几天 例如’6L’就表示一个月的最后一个星期五
W 只允许日期域出现 这个字符用于指定日期的最近工作日 例如 在日期域中写15W表示 此月15号最近的工作日 所以 如果15号是周六 则任务会在14号触发 如果15好是周日 则任务会在周一也就是16号触发 如果是在日期域填写1W即使1号是周六 那么任务也只会在下周一 也就是3号触发 W字符指定的最近工作日是不能够跨月份的 字符W只能配合一个单独的数值使用 不能够是一个数字段 如 1-15W是错误的
LW L和W可以在日期域中联合使用 LW表示这个月最后一周的工作日
# 只允许在星期域中出现 指定本月的某天 如 6#3表示本月第三周的星期五 6表示星期五 3表示第三周
C 允许在日期域和星期域出现 此表达式值依赖于相关日历计算 无日历关联 则等价于所有包含的日历 如 日期域是5C表示关联日历中第一天 或者这个月开始的第一天的后5天 星期域是1C表示关联日历中第一天 或者星期的第一天的后1天 也就是周日的后一天周一

cron字符对应意义

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
月内日期 1-31 , - * ? / L W C
1-12 或者 JAN-DEC , - * /
周内日期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空 1970-2099 , - * /

cron常用写法

表达式 作用
* * * * * 每1分钟执行一次
*/1 * * * * 每1分钟执行一次
0 0 1 * * ? 每天1点触发
0 10 1 ? * * 每天早上1点10分触发
0 10 1 * * ? 每天早上1点10分触发
0 10 1 * * ? * 每天早上1点10分触发
0 10 1 * * ? 2020 2020年的每天1点10分触发
0 * 2 * * ? 每天1点到1点59分每分钟一次触发
0 0/5 14 * * ? 每天从下午2点开始到2:55分结束每5分钟一次触发
0 0/5 1,3 * * ? 每天1点至1点55分 6点至6点55分每5分钟一次触发
0 5-10 1 * * ? 每天1点5分至1点10分每分钟一次触发
0 10,15 1 ? 3 WED 三月的每周三的1点10分和1点15分触发
0 10 1 ? * MON-FRI 每周 周一至周五1点10分触发
持续更新。。。
Timer缺陷和解决案例参考 https://blog.csdn.net/lmj623565791/article/details/27109467 cron表达式参考 https://www.cnblogs.com/Leo_wl/p/4714135.html

你可能感兴趣的:(java)