功能需求
在每个月的1号凌晨1点,定时把上个月产生的数据,进行处理汇总到当前月;同时为了方便测试,也可以通过接口的方式,处理指定月份的数据;
需求分析
- 需要使用接口调用,那个需要定义一个方法,接受一个时间变量的参数,把上月的产生的数据,汇总到当前月。
- 方法需要定时执行,那就给时间设置一个默认值,默认获取上一个月,如果时间字段为空,就使用使用默认值,否则使用接口的值
- 增加一个定时任务方法,定时调用方法,处理数据
代码实现
引入工具类
需要使用Hutool
的相关工具类,代码增加Maven依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
处理数据代码
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.StrUtil;
public class DataHandling {
// 获取上个月的时间
private static String lastMonth = DateUtil.lastMonth().toString();
public void handlingMethod(String handlingTime){
if(StrUtil.isNotBlank(handlingTime)){
lastMonth = handlingTime;
}
// 根据当前时间,查询数据库,执行相关逻辑
Console.log("定时任务执行了,时间:{}", lastMonth);
}
}
定时任务方法
这里为了方便测试,将定时任务设置为每10秒执行一次;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.task.Task;
public class MyTask1 {
public static void main(String[] args) {
// 每月1号凌晨1点执行
//String schedulingPattern = "0 0 1 1 * ?";
// 每10秒执行一次
String schedulingPattern = "0/10 * * * * ? ";
// 使用CronUtil来安排定时任务的执行
CronUtil.schedule(schedulingPattern, new Task() {
@Override
public void execute() {
// 调用数据处理方法
DataHandling dataHandling = new DataHandling();
dataHandling.handlingMethod(null);
}
});
// 支持秒级别定时任务
CronUtil.setMatchSecond(true);
// 启动定时任务
CronUtil.start();
}
}
发现问题
该功能经过测试,发现没有什么问题,于是就进行部署上线。某有1号上班的时候,有同事找了过来,就说查不到补数据,让我看一下,于是就开始了问题排查之路。
第一次排查问题
第一次发现这个问题的时候,就先去查了数据库,发现当月的数据为空;是定时任务没有执行吗?于是重点测试了定时任务(Cron
)表达式,发现也没有问题,为了不影响同事的使用,就通过接口手动调用,发现代码是可以正常执行,执行完,数据库可以正常查到数据,说明接口没有问题啊!出现这样的问题就很奇怪了,于是就给这个定时任务加了很多log,下个月再排查看下。
第二次排查问题
第二个月1号的时候,一上班就赶紧看了下那个定时任务,发现数据库里还是没有数据,这时看了下日志,发现定时任务是执行了的,但是竟然执行的是上一个月的数据,纳尼,这什么情况?不可能吧?当时脑袋里的第一个想法就是,总有刁民想害朕!有人动了我的代码?我把源码下载下来看看;有人改了服务器的时间?造成获取的时间不对;经过一系列排查下来,发现竟然没有问题,那是什么问题呢?算了,我把能加日志的地方都加上,下个月再看看,这个问题影响我很多时间了,不能再耽误了,反正也能通过接口执行,实在不行,我就每月手动执行一次。
第三次排查问题
很快,又到了下一个的1号,经过了前面两次的排查,没有找到问题,这次就等到凌晨1点,等定时任务跑完再休息,看看到底是什么问题;检查了一下部署包,服务器的时间,等快到1点的时候打开日志,就盯着log看是怎么执行的。结果发现定时任务执行的还是上一个月的数据,这么来看,饶了这么一大圈,还是自己的问题?那我就好好排查一下吧!本地启动代码,修改一下机器的时间,让定时任务执行,发现获取的时间始终是服务启动的时获取的时间,时间一直没有变,再看下代码,发现获取上个月的时间竟然有一个static
关键字,咳,这下问题算是整明白了。
问题分析
根据上面的代码,发现lastMonth
字段的值是固定的,原因是在程序启动时就获取了当前时间,并将其赋值给了lastMonth
字段。
由于lastMonth
字段是静态字段,它属于类级别的,而不是对象级别的。
静态字段在类加载时就会被初始化,并且所有的对象共享同一个静态字段的值。因此,无论创建多少个对象,它们都会共享相同的lastMonth
值。
为了解决这个问题,我们只需要将获取时间的变量的static
关键字去掉即可,这样每次执行定时任务时都会获取最新的当前时间。
修改后的代码如下:
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.StrUtil;
public class DataHandling {
// 获取上个月的时间
private String lastMonth = DateUtil.lastMonth().toString();
public void handlingMethod(String handlingTime){
if(StrUtil.isNotBlank(handlingTime)){
lastMonth = handlingTime;
}
// 根据当前时间,查询数据库,执行相关逻辑
Console.log("定时任务执行了,时间:{}", lastMonth);
}
}
在修改后的代码后,去掉了变量的static
关键字,这样每次执行定时任务时,都会重新获取最新的时间。
为什么定位问题用了这么久
- 没有把这个问题,当成一个真正的问题去处理,数据不是频繁使用的数据,调用接口也能正常处理数据,没有把它当回事;
- 定时任务的执行周期比较长,当月没有发现问题,就把这件事推到下一个月了,很常见的偷懒现象;
- 发现问题之后,把服务重启了,这就造成每个月看到的日志,是跑的上一个月的,总以为有人动了我的代码;
Static关键字的作用
在Java语言中,static
关键字用于修饰类的成员,包括字段、方法和内部类。
它的作用是将成员声明为静态的,即不依赖于对象的存在而存在。
静态成员属于类级别的,可以通过类名直接访问,而不需要创建对象。
静态字段在类加载时就会被初始化,而不是在对象创建时才被初始化。
静态方法也是类级别的,可以直接通过类名调用,而不需要通过对象调用。
类与对象的区别
类是对一类事物的抽象描述,它定义了事物的属性和行为。
而对象是类的实例化,是具体的实体。类是对象的模板,通过类可以创建多个对象。对象具有自己的状态和行为,可以调用类中定义的方法来执行特定的操作。
最后总结
总结一下,静态字段属于类级别的,所有对象共享同一个静态字段的值;
静态方法属于类级别的,可以直接通过类名调用;
类是对一类事物的抽象描述,对象是类的实例化,具有自己的状态和行为。