@Scheduled
初始配置
写一个测试类作为组件,注入容器,当容器启动时则会触发这个定时器。注意这个测试类必须要放在启动类的同一目录下或其子目录下。
SpringBoot为我们内置了@Scheduled定时任务,下面我们就来配置下这个注解,找到入口程序xxxApplication添加注解@EnableScheduling。
如图所示,IotHttpTest这个类必须和DeviceApplication在同一目录下,或是在controller,domain,filter,model,repository,service,util等自定义的子目录下,才会在容器启动时触发。
代码示列
@Component
public class IotHttpTest {
@Autowired
IOTReceiverServiceImpl iotReceiverService;
String topic2 = "/a17RVQkwxyh/xylgLight2/thing/event/property/post";
String msg2 = "";
/*容器启动后,此类作为组件注入容器中,开始定时调用iotHttp方法,在前一次调用结束后1s后开始下一次调用*/
@Scheduled(fixedDelay = 1000)
public void test()throws Exception{
msg2 = "{\"deviceType\":\"Lighting\",\"iotId\":\"vimHPjJ8mcbScIPF0z0V000100\",\"productKey\":\"a17RVQkwxyh\"," +
"\"gmtCreate\":1554712072409,\"deviceName\":\"xylgLight2\"," +
"\"items\":{" +
"\"LightCurrent\":{\"value\":" + Math.round(Math.random() * 4000) + ",\"time\":1554712072412}," +
"\"LightVolt\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}," +
"\"LightError\":{\"value\":" + Math.round(Math.random()) + ",\"time\":1554712072412}," +
"\"Power\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}," +
"\"LightAdjustLevel\":{\"value\":" + Math.round(Math.random() * 100) + ",\"time\":1554712072412}," +
"\"PowerConsumption\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}}}";
System.out.println("\n发送数据:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("路灯状态变更信息:" + msg2);
iotReceiverService.iotHttp(topic2,msg2, UUID.randomUUID());
}
}
Cron属性
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
1.Seconds Minutes Hours DayofMonth Month DayofWeek Year
2.Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds: 可出现", - * /“四个字符,有效范围为0-59的整数
Minutes: 可出现”, - * /“四个字符,有效范围为0-59的整数
Hours: 可出现”, - * /“四个字符,有效范围为0-23的整数
DayofMonth :可出现”, - * / ? L W C"八个字符,有效范围为0-31的整数
Month: 可出现", - * /“四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek: 可出现”, - * / ? L C #“四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year: 可出现”, - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1) :表示匹配该域的任意值,假如在Minutes域使用, 即表示每分钟都会触发事件。
(2) ?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3) -:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4) /:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5) ,:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6) L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7) W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。
(8) LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9) #:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
举几个例子:
每隔5秒执行一次:"*/5 * * * * ?"
每隔1分钟执行一次:“0 */1 * * * ?”
每天23点执行一次:“0 0 23 * * ?”
每天凌晨1点执行一次:“0 0 1 * * ?”
每月1号凌晨1点执行一次:“0 0 1 1 * ?”
每月最后一天23点执行一次:“0 0 23 L * ?”
每周星期天凌晨1点实行一次:“0 0 1 ? * L”
在26分、29分、33分执行一次:“0 26,29,33 * * * ?”
每天的0点、13点、18点、21点都执行一次:“0 0 0,13,18,21 * * ?”
表示在每月的1日的凌晨2点调度任务:“0 0 2 1 * ? *”
表示周一到周五每天上午10:15执行作业:“0 15 10 ? * MON-FRI”
表示2002-2006年的每个月的最后一个星期五上午10:15执行:“0 15 10 ? 6L 2002-2006”
fixedDelay属性
配置了该属性后会等到方法执行完成后延迟配置的时间再次执行该方法。
@Scheduled(fixedDelay = 5000) //上一次执行完毕时间点之后5秒再执行
fixedRate属性
该属性的含义是上一个调用开始后再次调用的延时(不用等待上一次调用完成),这样就会存在重复执行的问题,所以不是建议使用,但数据量如果不大时名,在配置的间隔时间内可以执行完也是可以使用的。
@Scheduled(fixedRate = 5000) //上一次开始执行时间点之后5秒再执行
initialDelay属性
该属性跟上面的fixedDelay、fixedRate有着密切的关系,为什么这么说呢?该属性的作用是第一次执行延迟时间,只是做延迟的设定,并不会控制其他逻辑,所以要配合fixedDelay或者fixedRate来使用
@Scheduled(initialDelay=1000, fixedRate=5000)
/*第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次*/
Timer
代码示例
@Component
public class IotHttpTest {
@Autowired
IOTReceiverServiceImpl iotReceiverService;
static String topic2 = "/a17RVQkwxyh/xylgLight2/thing/event/property/post";
public static void main(String args[]) {
Timer timer = new Timer();
IotHttpTest iotHttp=new IotHttpTest();
SecurityApi securityApi = new SecurityApi();
String token1 = securityApi.login("app", "123");
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
//路灯2数据上报
String msg2 = "{\"deviceType\":\"Lighting\",\"iotId\":\"vimHPjJ8mcbScIPF0z0V000100\",\"productKey\":\"a17RVQkwxyh\"," +
"\"gmtCreate\":1554712072409,\"deviceName\":\"xylgLight2\"," +
"\"items\":{" +
"\"LightCurrent\":{\"value\":" + Math.round(Math.random() * 4000) + ",\"time\":1554712072412}," +
"\"LightVolt\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}," +
"\"LightError\":{\"value\":" + Math.round(Math.random()) + ",\"time\":1554712072412}," +
"\"Power\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}," +
"\"LightAdjustLevel\":{\"value\":" + Math.round(Math.random() * 100) + ",\"time\":1554712072412}," +
"\"PowerConsumption\":{\"value\":" + Math.round(Math.random() * 60000) + ",\"time\":1554712072412}}}";
try {
/*直接发送请求的方式,无法回调controller类的接口响应处理请求*/
String url = "http://172.18.1.134:10002/api/device/iotHttp?Topic=" + topic2;
iotHttp.sendPost(url,msg2,token1);
//iotHttp.sendPost1(url,msg2,token1);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 0, 1000);
}
/*使用java的原生接口发送http请求*/
private void sendPost(String uri,String param,String token)throws Exception{
URL url=new URL(uri);
HttpURLConnection connection=(HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization","Bearer "+token);
connection.setRequestProperty("Content-Type","application/json");
connection.setDoOutput(true);
DataOutputStream stream=new DataOutputStream(connection.getOutputStream());
stream.writeBytes(param);
stream.flush();
stream.close();
int status=connection.getResponseCode();
System.out.println("\nSending request at " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("Post param : " + param);
System.out.println("Response status : " + status);
}
/*使用springboot架构中提供接口发送请求*/
private void sendPost1(String url,String param,String token){
RestTemplate restTemplate=new RestTemplate();
HttpHeaders headers=new HttpHeaders();
headers.add("Authorization","Bearer "+token);
MediaType type=MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
HttpEntity<String>entity=new HttpEntity<>(param,headers);
restTemplate.postForEntity(url,entity,String.class);
}
}
schedule方法重载
这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。
public void schedule(TimerTask task, long delay)
在指定的时间点time上调度一次。
public void schedule(TimerTask task, Date time)
这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。
public void schedule(TimerTask task, long delay, long period)
和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。
public void schedule(TimerTask task, Date firstTime, long period)
schedulAtFixedRate方法重载
调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度。
貌似和方法:schedule是一样的,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片。
而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点。
例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次。
而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)