Quartz学习笔记

Posted by Young Sheep on July 5, 2023

Quartz是一个功能丰富的开源作业调度库,可以集成在Spring Boot中。使用Quartz可以创建简单或者复杂的执行计划,它支持数据库、集群、插件以及邮件,并且支持cron表达式,具有极高的灵活性。Spring Boot中集成Quartz主要提供三个Bean:JobDetail、Trigger以及SchedulerFactory。

引入依赖

1
2
3
4
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

编写定时任务

定时任务可以继承QuartzJobBean类,然后在executeInternal方法中编写定时任务。 但这里有个坑,使用Spring 结合Quartz进行定时任务开发时,如果直接在job内的execute方法内使用service 或者mapper对象,执行时,出现空指针异常。 原因是job对象在Spring容器加载时候,能够注入bean,但是调度时,job对象会重新创建,此时就是导致已经注入的对象丢失,因此报空指针异常。 因此不能直接Autowired对象,在网上看了两种方法,一种是重写JobFactory类,另一种是定义静态工具类获取bean对象。我尝试了第一种方法依然会报错,可能是配置错了,于是就采用了第二种曲线救国的方式。

定义静态工具类获取bean对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class SpringContextJobUtil implements ApplicationContextAware  {

	private static ApplicationContext context;  
  
	@Override  
	public void setApplicationContext(ApplicationContext applicationContext)  
throws BeansException {  
		SpringContextJobUtil.context = applicationContext;  
	}  
  
	public static ApplicationContext getContext() {  
		return context != null ? context : null;  
	}  
	/**  
	* 获取 Spring Bean  
	* @param clazz 类  
	* @param <T> 泛型  
	* @return 对象  
	*/  
	public static <T> T getBean(Class<T> clazz) {  
		if (clazz == null) {  
			return null;  
		}  
		return context.getBean(clazz);  
	}  
}

之后通过在Job类中通过getBean方法便能获取到SpringBoot管理的bean对象。

编写定时任务Job

Job可以是一个普通的JavaBean,如果是普通的JavaBean,那么可以先添加@Component注解将之注册到Spring容器中。 还可以继承抽象类QuartzJobBean,然后实现该类中的executeInternal方法,该方法在任务被调用时使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
@Component
public class MyJob {
    private final XXXXService xxxxService = SpringContextJobUtil.getBean(xxxxService.class);
    protected void doJob(){
        Date now = new Date(System.currentTimeMillis());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.info("{}触发定时任务",formatter.format(now));
        xxxxService.xxxx();
    }
}


public class MyJob2 extends QuartzJobBean {
    private final XXXXService xxxxService =  SpringContextJobUtil.getBean(xxxxService.class);
	
	private String name;

    public void setName(String name) {
        this.name = name;
    }
	
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException{
        Date now = new Date(System.currentTimeMillis());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.info("{}在{}触发定时任务",this.name,formatter.format(now));
        xxxxService.xxxx();
    }
}

坑: 由于SpringBoot启动时Component是有注册顺序的,在IDEA中我这样写可以正常启动,但打包成Jar到服务器上部署时就启动不了,后来才明白可能在服务器上先注册了Jab类的组件,之后再注册SpringContextJobUtil的组件,从而报了空指针的异常。因此需要在编写SpringContextJobUtil类时在Component注解中设置value为springContext,之后在Job类中添加@DependsOn(“springContext”)注解,这样SpringContextJobUtil组件会比Job组件先注册到IOC容器中,便能解决空指针的异常。

编写Quartz配置类

配置类中可以对JobDetail、Trigger和Scheduler进行配置

  • JobDetail的配置有两种方式: 第一种方式通过MethodInvokingJobDetailFactoryBean类配置JobDetail,只需要指定Job的实例名和要调用的方法即可,注册这种方式无法在创建JobDetail时传递参数; 第二种方式是通过JobDetailFactoryBean来实现的,这种方式只需要指定JobClass即可,然后可以通过JobDataMap传递参数到Job中,Job中只需要提供属性名,并且提供一个相应的set方法即可接收到参数。
  • Trigger的配置 常用的Trigger有SimpleTrigger和CronTrigger,这两种Trigger分别使用SimpleTriggerFactoryBean和CronTriggerFactoryBean进行创建。在SimpleTriggerFactoryBean对象中,首先设置JobDetail,然后通过setRepeatCount配置任务循环次数,setStartDelay配置任务启动延迟时间,setRepeatInterval配置任务的时间间隔。在CronTriggerFactoryBean对象中,则主要配置JobDetail和Cron表达式。
  • Scheduler的配置 最后通过SchedulerFactoryBean创建SchedulerFactory,然后配置Trigger即可。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
     @Configuration
    public class QuartzConfig {
    
      @Bean
      MethodInvokingJobDetailFactoryBean jobDetail1() {
          MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
          bean.setTargetBeanName("myJob");
          bean.setTargetMethod("doJob");
          return bean;
      }
    
      @Bean
      JobDetailFactoryBean jobDetail2() {
          JobDetailFactoryBean bean = new JobDetailFactoryBean();
          bean.setJobClass(MyJob2.class);
          JobDataMap jobDataMap = new JobDataMap();
          jobDataMap.put("name", "sang");
          bean.setJobDataMap(jobDataMap);
          bean.setDurability(true);
          return bean;
      }
    
      @Bean
      SimpleTriggerFactoryBean simpleTrigger() {
          SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
          bean.setJobDetail(jobDetail1().getObject());
          bean.setRepeatCount(3);
          bean.setStartDelay(1000);
          bean.setRepeatInterval(2000);
          return bean;
      }
      @Bean
      CronTriggerFactoryBean cronTrigger() {
          CronTriggerFactoryBean bean = new CronTriggerFactoryBean ();
          bean.setJobDetail(jobDetail2().getObject());
          bean.setCronExpression("* * * * * ?");
          return bean;
      }
      @Bean
      SchedulerFactoryBean schedulerFactory() {
          SchedulerFactoryBean bean = new SchedulerFactoryBean();
          SimpleTrigger simpleTrigger = simpleTrigger().getObject();
          CronTrigger cronTrigger = cronTrigger().getObject();
          bean.setTriggers(simpleTrigger, cronTrigger);
          return bean;
      }
    }
    

其实最后发现这只是Quzrtz最简单的使用,而且用Spring的@Scheduled注解加上cron表达式也可以实现相同的功能,Quzrtz还有更强大的Scheduler管理和任务监听,后面有时间再继续学习吧。