Spring中使用定时任务的两种方式[@Scheduled,quartz+SpringBeanJobFactory]

文章目录

    • Spring自带的@Scheduled注解
      • 1. 在主启动类上面加@EnableScheduling,开启定时任务
      • 2. 定时任务类
      • 3. 开启异步执行定时任务,在主启动类上面加@EnableScheduling
      • 4. 设置异步执行定时任务的线程池大小
    • Spring中使用quartz
      • 1. pom.xml引入依赖:
      • 2. quartz.properties配置:
        • 内存存储quartz.properties:
        • 数据库存储quartz-prod.properties:
      • 3. spring配置quartz
        • 配置quartz数据源(按照多数据源方式)
        • 写一个AutoWiredSpringBeanToJobFactory
        • 写一个spring的quartz配置类SchedulerConfig.java
      • 4. 配置完毕,正常使用quartz即可
        • 写一个Job类
        • 这里在Controller中开启/停止job
      • 5. 注意我这里是把资源配置文件抽出来,放在了和src同级的config目录里面

Spring自带的@Scheduled注解

简单的定时任务控制,注解使用spring自带定时任务就可以完成啦,定时任务类也是由Spring管理,所以可以正常使用IOC自动注入其他的类.
具体原理没有深究哈,日后学习Spring源码的时候再详细了解吧.这里只说怎么应用.

1. 在主启动类上面加@EnableScheduling,开启定时任务

@SpringBootApplication
@EnableScheduling
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

2. 定时任务类

这个@Slf4j是用了lombok自动生成了一个log,没有配置的话可以直接创建一个log
这里是初始延迟1s,然后以固定3s的频率执行,在方法中sleep了5s.
也可以直接使用cron表达式,这里简单示例

@Component
@Slf4j
public class TestScheduled {

    @Scheduled(fixedRateString = "3000", initialDelay = 1000)
    public void printTrace() {
        log.info("printTrace---------------------------------------- at" + LocalTime.now());
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

会看到程序的打印输出的时间大概是这样:

printTrace--- at11:30:03.713384
printTrace--- at11:30:10.923850
printTrace--- at11:30:17.945608
printTrace--- at11:30:24.957384

你可能会疑问,这时间间隔并不是3s啊,这什么玩意?
这是因为Scheduled默认是同步执行的,下次一任务来的时候,发现上一个还没执行完,那就等它执行完再执行.

3. 开启异步执行定时任务,在主启动类上面加@EnableScheduling

然后在TestScheduled类上加注解@Async
然后我们打印一下当前线程,便于观察:

log.info("printTrace-- at" + LocalTime.now() + "in thread:" + Thread.currentThread().getName());

就会发现每隔3s打印一次,8个线程循环执行,大概就是默认线程池的coreSize是8了,以后再详细研究.

4. 设置异步执行定时任务的线程池大小

在主启动类中,或者其他配置类中,加入一个Bean:

	@Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.initialize();

		// 试了下这个,不太好使,先不深入探究了..
        /*Executor executor = new ThreadPoolExecutor(
                2
                , 10
                , 2000
                , TimeUnit.MILLISECONDS
                , new ArrayBlockingQueue<>(100)
                , Executors.defaultThreadFactory()
                , new ThreadPoolExecutor.AbortPolicy()
        );*/

        return executor;
    }

Spring中使用quartz

先参照W3C了解quartz的基本用法.
然后会发现,应用到Spring项目中,Job中不能使用Spring管理的对象,IOC自动注入类.所以需要稍微做点配置.

1. pom.xml引入依赖:

		
            org.quartz-scheduler
            quartz
            2.2.3
        
        
            org.quartz-scheduler
            quartz-jobs
            2.2.3
        
        
            org.springframework
            spring-context-support
        

2. quartz.properties配置:

定时任务的信息一般会配置存储在数据库中,demo也可以存储在内存中

内存存储quartz.properties:

org.quartz.scheduler.instanceName=Test-testScheduler
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

数据库存储quartz-prod.properties:

#============================================================================
# Configure Main Scheduler Properties
#============================================================================

org.quartz.scheduler.instanceName: TestScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.scheduler.skipUpdateCheck: true

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 5
org.quartz.threadPool.threadPriority: 5

#============================================================================
# Configure JobStore
#============================================================================

#容许的最大作业延
org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.dataSource=springTxDataSource.schedulerFactoryBean
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true

#调度实例失效的检查时间间隔
org.quartz.jobStore.clusterCheckinInterval=20000

3. spring配置quartz

在spring的配置文件(application.properties)中配置quartz配置文件的路径,方便灵活调试:
quartzConfig.location=config/quartz-prod.properties

配置quartz数据源(按照多数据源方式)

package weizhi.example.hiquartz.config;

import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;

/**
 * @Auther: liweizhi
 * @Date: 2019/6/24 14:53
 * @Description:
 */
@Configuration
// basePackages指定mapper的文件夹,如果需要主动查询数据库中的内容,不需要的话可以不配置
@MapperScan(basePackages = {"weizhi.example.hiquartz.dao.quartzdb"}, sqlSessionTemplateRef = "sqlSessionTemplatequartz")
public class DatasourceConfigQuartzDb {

    @Bean(name = "quartzDataSource")
    public DataSource quartzDb(Environment env) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(env.getProperty("spring.datasource.quartzdb.url"));
        ds.setUsername(env.getProperty("spring.datasource.quartzdb.username"));
        ds.setPassword(env.getProperty("spring.datasource.quartzdb.password"));
        ds.setDriverClassName(env.getProperty("spring.datasource.quartzdb.driver-class-name"));

        ds.setMaximumPoolSize(10);
        return ds;
    }


    @Bean
    public SqlSessionFactory sqlSessionFactoryquartz(@Qualifier("quartzDataSource") @Autowired DataSource quartzDb) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(quartzDb);
        // sql写在mapper.xml中需配置
        // factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("mapperLocation"));
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplatequartz(SqlSessionFactory sqlSessionFactoryquartz) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryquartz);
    }
}

写一个AutoWiredSpringBeanToJobFactory

@Component
public class AutoWiredSpringBeanToJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

写一个spring的quartz配置类SchedulerConfig.java

package weizhi.example.hiquartz.config;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import weizhi.example.hiquartz.config.component.AutoWiredSpringBeanToJobFactory;

import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

/**
 * @author liweizhi
 * @date 2019/11/13 16:35
 */
@Configuration
public class SchedulerConfig {
    // 配置文件路径
    @Value("${quartzConfig.location}")
    private String quartzConfig;

    // 按照自己注入的数据源自行修改
    @Qualifier("quartzDataSource")
    @Autowired
    private DataSource quartzDataSource;

    @Autowired
    private AutoWiredSpringBeanToJobFactory autoWiredSpringBeanToJobFactory;

    /**
     * 从quartz.properties文件中读取Quartz配置属性
     *
     * @return
     * @throws IOException
     */
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        // new ClassPathResource(QUARTZ_CONFIG)
        propertiesFactoryBean.setLocation(new FileSystemResource(quartzConfig));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    /**
     * JobFactory与schedulerFactoryBean中的JobFactory相互依赖,注意bean的名称
     * 在这里为JobFactory注入了Spring上下文
     *
     * @param applicationContext
     * @return
     */
    @Bean
    public JobFactory buttonJobFactory(ApplicationContext applicationContext) {
        AutoWiredSpringBeanToJobFactory jobFactory = new AutoWiredSpringBeanToJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setJobFactory(autoWiredSpringBeanToJobFactory);
        factory.setOverwriteExistingJobs(true);
        // 设置自行启动
        factory.setAutoStartup(true);
        // 延时启动,应用启动1秒后
        factory.setStartupDelay(1);
        factory.setQuartzProperties(quartzProperties());
        // 使用应用的dataSource替换quartz的dataSource
        factory.setDataSource(quartzDataSource);
        return factory;
    }

    @Bean(name = "myScheduler")
    public Scheduler createScheduler(SchedulerFactoryBean schedulerFactoryBean)
            throws IOException, SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        scheduler.start();
        return scheduler;
    }

}

4. 配置完毕,正常使用quartz即可

写一个Job类

不需要加@Component,直接可以使用@Autowired自动注入

@Slf4j
public class HiJob implements Job {

    @Autowired
    private IPetService petService;

    public HiJob() {

    }

    @Override
    public void execute(JobExecutionContext context) {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        log.info("getted param name:{}, JobDetail.Key:{}", jobDataMap.getString("name"), context.getJobDetail().getKey());
        petService.getAllPet();
    }
}

这里在Controller中开启/停止job

/**
 * @author liweizhi
 * @date 2019/11/13 16:23
 */
@RestController
@RequestMapping("quartz")
public class QuartzController {

    private static final String GROUP = "group1";

    @Autowired
    @Qualifier("myScheduler")
    private Scheduler scheduler;

    @GetMapping("meta")
    public SchedulerMetaData getMetaData() throws SchedulerException {
        return scheduler.getMetaData();
    }

    @GetMapping("/exist/{id}")
    public boolean existJob(@PathVariable("id") String id) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(id, GROUP);
        return scheduler.checkExists(jobKey);
    }

    @GetMapping("/delete/{id}")
    public SchedulerMetaData deleteJob(@PathVariable("id") String id) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(id, GROUP);
        scheduler.deleteJob(jobKey);
        return scheduler.getMetaData();
    }

    @GetMapping("/add/{id}")
    public SchedulerMetaData addJob(@PathVariable("id") String id) throws SchedulerException {

        JobKey jobKey = JobKey.jobKey(id, GROUP);
        JobDetail job = JobBuilder.newJob(HiJob.class)
                .withIdentity(jobKey)
                .usingJobData("name", "=============axiba================")
                .build();

        // Trigger the job to run now, and then repeat every 40 seconds
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(id, GROUP))
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMilliseconds(5000)
                        .repeatForever()
                )
                .startAt(new Date(System.currentTimeMillis() + 5000))
                /*.withSchedule(cronSchedule("0/20 * * ? * *")
//                        .withMisfireHandlingInstructionDoNothing()
                )*/
                .build();
        Trigger triggerRightNow = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(id + "::triggerRightNow", GROUP))
                .forJob(jobKey)
                .startNow()
                .build();
        System.err.println("before scheduleJob =============" + new Date());
        scheduler.scheduleJob(job, trigger);
//        scheduler.scheduleJob(triggerRightNow);
        System.err.println("after scheduleJob =============" + new Date());
        return scheduler.getMetaData();
    }
}

5. 注意我这里是把资源配置文件抽出来,放在了和src同级的config目录里面

目录结构:
Spring中使用定时任务的两种方式[@Scheduled,quartz+SpringBeanJobFactory]_第1张图片
主启动类加载指定properties,并在:

@SpringBootApplication
@PropertySource("file:config/application.properties")
public class HiquartzApplication {

    public static void main(String[] args) {
        SpringApplication.run(HiquartzApplication.class, args);
    }
}

IntellJ IDEA中启动时需要编辑启动配置:
Spring中使用定时任务的两种方式[@Scheduled,quartz+SpringBeanJobFactory]_第2张图片

你可能感兴趣的:(springboot应用系列)