相关技术

本文采用spring + quartz的方案。使用mysql作为任务的持久化,支持分布式。

自定义注解

1.启用定时任务

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(QuartzConfig.class) //引入配置
@Documented
public @interface EnableMScheduling { } //该注解需要放在application启动类上,标识启用定时任务,它的作用就是配置、解析任务以及启动调

2.标识调度类

@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface MScheduleClass { /**
* 任务分组,页面显示作用
* @return
*/
String module() default "系统"; /**
* 描述,提示作用
* @return
*/
String desc() default ""; }
//该注解放置在类上,标识指定的类是一组定时任务,其中需要设置任务分组,描述

3.标识执行的方法

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MSchedule { /**
* 任务名称
* @return
*/
String title() default ""; /**
* 调度触发的corn表达式 : 用作Job的触发器,目前只支持一个触发器表达式。
*/
String corn(); /**
* 描述
*/
String desc() default ""; /**
* 参数
*/
String param() default "";
}
//该注解标识在@MScheduleClass标识的类中的方法中,标识指定的方式是任务执行的方法。

配置类

import javax.sql.DataSource;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager; public class QuartzConfig { /**
* 配置任务调度器
* 使用项目数据源作为quartz数据源
* @param jobFactory 自定义配置任务工厂
* @param dataSource 数据源实例
* @return
* @throws Exception
*/
@Bean(destroyMethod = "destroy")
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource,
ObjectProvider<PlatformTransactionManager> transactionManager) throws Exception { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
// 项目启动完成后,等待10秒后开始执行调度器初始化
//schedulerFactoryBean.setStartupDelay(10);
// 设置调度器自动运行
schedulerFactoryBean.setAutoStartup(false);
// 设置数据源,使用与项目统一数据源
schedulerFactoryBean.setDataSource(dataSource); PlatformTransactionManager txManager = transactionManager.getIfUnique();
if (txManager != null) {
schedulerFactoryBean.setTransactionManager(txManager);
}
// 设置上下文spring bean name
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
// 设置配置文件位置
schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
return schedulerFactoryBean;
} @Bean
public MScheduleBeanPostProcessor mScheduleBeanPostProcessor() {
return new MScheduleBeanPostProcessor();
}
}

其中dataSource我自己用的阿里的druid。  具体配置自行处理

quartz.properites

#调度器实例名称
org.quartz.scheduler.instanceName = quartzScheduler #调度器实例编号自动生成
org.quartz.scheduler.instanceId = AUTO #持久化方式配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #持久化方式配置数据驱动,MySQL数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #quartz相关数据表前缀名
org.quartz.jobStore.tablePrefix = QRTZ_ #开启分布式部署
org.quartz.jobStore.isClustered = true
#配置是否使用
org.quartz.jobStore.useProperties = false #分布式节点有效性检查时间间隔,单位:毫秒
org.quartz.jobStore.clusterCheckinInterval = 20000 #线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool #执行最大并发线程数量
org.quartz.threadPool.threadCount = 10 #线程优先级
org.quartz.threadPool.threadPriority = 5 #配置是否启动自动加载数据库内的定时任务,默认true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

quartz初始化的数据库表在org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql

注解解析beanPosrProcessor

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set; import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent; import com.mustr.cluster.annotation.MSchedule;
import com.mustr.cluster.annotation.MScheduleClass; import lombok.extern.slf4j.Slf4j; @Slf4j
public class MScheduleBeanPostProcessor implements BeanPostProcessor, ApplicationListener<ContextRefreshedEvent>, DisposableBean { @Autowired
private Scheduler scheduler; private List<MustrTask> tasks = new ArrayList<>(); @Override
public void destroy() throws Exception {
scheduler.shutdown();
} @Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("all scheduler tasks total {}", tasks.size()); try {
//先把原来的都删除
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyGroup());
scheduler.deleteJobs(new ArrayList<>(jobKeys));
} catch (SchedulerException e1) {
e1.printStackTrace();
} //重新添加新的
tasks.forEach(task -> {
try {
scheduler.scheduleJob(task.getJobDetail(), task.getTrigger());
} catch (SchedulerException e) {
e.printStackTrace();
}
}); try {
scheduler.start(); //启动调度器
} catch (SchedulerException e) {
e.printStackTrace();
}
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MScheduleClass msClass = bean.getClass().getAnnotation(MScheduleClass.class);
if (msClass == null) {
return bean;
} String group = bean.getClass().getSimpleName();
Method[] methods = bean.getClass().getDeclaredMethods();
if (methods == null) {
return bean;
} for (Method method : methods) {
MSchedule mSchedule = method.getAnnotation(MSchedule.class);
if (mSchedule == null) {
continue;
}
hanlderSchedule(group, mSchedule, method, bean);
} return bean;
} private void hanlderSchedule(String group, MSchedule mSchedule, Method method, Object bean) {
String jobName = method.getName(); JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("targetClass", bean);
jobDataMap.put("targetMethod", method.getName());
jobDataMap.put("arguments", mSchedule.param()); JobDetail jobDetail = JobBuilder.newJob(MustrCommonJob.class)
.setJobData(jobDataMap)
.withIdentity(new JobKey(jobName, group))
.withDescription(mSchedule.desc())
.storeDurably()
.build(); Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(new TriggerKey(jobName, group))
.withDescription(mSchedule.desc())
.withSchedule(CronScheduleBuilder.cronSchedule(mSchedule.corn()).withMisfireHandlingInstructionDoNothing())
.forJob(jobDetail)
.build(); tasks.add(new MustrTask(jobDetail, trigger));
}
}

该类就是解析自定义注解的@MScheduleClass和@MSchedule标识的任务。封装jobDetail和Trigger。

任务job统一使用MustrCommonJob通过反射来执行配置的指定类的指定方法

 

MustrTask

import org.quartz.JobDetail;
import org.quartz.Trigger; import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter; @Setter
@Getter
@AllArgsConstructor
public class MustrTask { private JobDetail jobDetail;
private Trigger trigger;
}

任务类

import java.io.Serializable;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.util.MethodInvoker; public class MustrCommonJob implements Job, Serializable{
private static final long serialVersionUID = 8651275978441122356L; @Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Object targetClass = context.getMergedJobDataMap().get("targetClass");
String targetMethod = context.getMergedJobDataMap().getString("targetMethod");
String param = context.getMergedJobDataMap().getString("arguments"); //前置处理
// do .... try {
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetObject(targetClass);
methodInvoker.setTargetMethod(targetMethod);
if (param != null && !"".equals(param)) {
String[] params = param.split(",");
Object[] temp = new Object[params.length];
for (int i = 0; i < params.length; i++) {
temp[i] = params[i];
}
methodInvoker.setArguments(temp);
}
methodInvoker.prepare();
methodInvoker.invoke();
} catch (Exception e) {
e.printStackTrace();
} finally { //后置处理
// do ... 如记录日志
}
} }
该类实现了quartz的job接口。通过反射调用指定的方法

一个简单的demo

import java.io.Serializable;
import java.time.LocalDateTime; import com.mustr.cluster.annotation.MSchedule;
import com.mustr.cluster.annotation.MScheduleClass; @MScheduleClass(module = "系统", desc = "测试组")
public class HelloSchedule implements Serializable{
private static final long serialVersionUID = 3619058186885794136L; /*@MSchedule(corn = "0/30 * * * * ?", desc = "打印hello world")
public void hello() {
System.out.println("hello mustr..... <<<:::>>>" + LocalDateTime.now());
}*/ @MSchedule(corn = "0/10 * * * * ?", desc = "打印hello world")
public void hello1() {
System.out.println("<<<<hello1 mustr..... <<<:::>>>" + LocalDateTime.now());
}
}

最后一步

在程序启动类中加入 @EnableMScheduling注解,启动项目即可看到控制台打印

本文代码:https://github.com/Mustr/mustr-quartz-boot

spring + quartz 分布式自定义注解的更多相关文章

  1. spring quartz分布式任务计划

    spring quartz分布式任务计划 环境: 通过maven管理的spring mvc工程,且已经成功连接数据库. 数据库表结构 /*Table structure for table `qrtz ...

  2. redis分布式锁-spring boot aop+自定义注解实现分布式锁

    接这这一篇redis分布式锁-java实现末尾,实现aop+自定义注解 实现分布式锁 1.为什么需要 声明式的分布式锁 编程式分布式锁每次实现都要单独实现,但业务量大功能复杂时,使用编程式分布式锁无疑 ...

  3. 使用spring aspect控制自定义注解

    自定义注解:这里是一个处理异常的注解,当调用方法发生异常时,返回异常信息 /** * ErrorCode: * * @author yangzhenlong * @since 2016/7/21 */ ...

  4. spring AOP 和自定义注解进行身份验证

    一个SSH的项目(springmvc+hibernate),需要提供接口给app使用.首先考虑的就是权限问题,app要遵循极简模式,部分内容无需验证,用过滤器不能解决某些无需验证的方法 所以最终选择用 ...

  5. Spring实现封装自定义注解@Trimmed清除字符串前后的空格

    在Spring中实现字符串清除的方法有很多,原生方法String自带trim()方法,或者使用StringUtils提供的trim...方法. 通常可以将上面的方式封装成自定义注解的形式去实现来节省更 ...

  6. Spring Boot中自定义注解+AOP实现主备库切换

    摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的 ...

  7. Spring Boot实现自定义注解

    在Spring Boot项目中可以使用AOP实现自定义注解,从而实现统一.侵入性小的自定义功能. 实现自定义注解的过程也比较简单,只需要3步,下面实现一个统一打印日志的自定义注解: 1. 引入AOP依 ...

  8. spring boot通过自定义注解和AOP拦截指定的请求

    一 准备工作 1.1 添加依赖 通过spring boot创建好工程后,添加如下依赖,不然工程中无法使用切面的注解,就无法对制定的方法进行拦截 <dependency> <group ...

  9. spring mvc实现自定义注解

    实现方式:使用@Aspect实现: 1. 新建注解接口:CheckSign package com.soeasy.web.utils; import org.springframework.core. ...

随机推荐

  1. 一款基于.NET Core的认证授权解决方案-葫芦藤1.0开源啦

    背景 18年公司准备在技术上进行转型,而公司技术团队是互相独立的,新技术的推动阻力很大.我们需要找到一个切入点.公司的项目很多,而各个系统之间又不互通,导致每套系统都有一套登录体系,给员工和客户都带来 ...

  2. 【涂鸦物联网足迹】API及SDK介绍

    前序系列文章>>> [涂鸦物联网足迹]物联网主流通信方式 我们系列文章,都会围绕如何完成一款智能"隔空接吻机"的开发.希望能帮到各异地恋or异国恋的情侣们! 本文 ...

  3. BPMN开源工作流编辑器bpmn-js落地实践中文文档

    BPMN是一套标准的业务流程建模符号规范,bpmn-js是基于此规范实现的一套渲染工具包和web建模器,可以实现拖拽生成工作流程图,效果大概如下 最近刚好用到,研究之后写了系列文章,分享给有需要的小伙 ...

  4. 年轻人不讲武德来白piao我这个老同志

    朋友们好啊,我是码农小胖哥. 今天有个同学问我在吗,我说什么事? 给我发个截图,我一看!噢,原来是帮忙搞个定时任务,还是动态的. 他说了两种选择,一种是用DelayQueue,一种是用消息队列. 他说 ...

  5. Java开发环境搭建(若jdk8按默认安装后没有jre文件夹,卸载重装时选择完整安装)

    JDK下载与安装(JDK 8是主流,新版版就是增加了一些新特性) 卸载旧JDK 删除java的安装目录 删除JAVA_HOME 删除path下关于java的目录 在cmd命令行中输入java vers ...

  6. iscsi客户端常用操作

    说明 本篇主要记录iscsi的客户端的一些常用的一些操作 iscsi服务端常用操作 删除一个lun tgtadm --lld iscsi --mode logicalunit --op delete ...

  7. 编译一个支持多线程的php安装包

    前言 因为项目上的需要,需要用到php,一般来说,用默认的版本和配置就可以满足大多数的场景,因为需要加入多线程,所以需要自己编译一个包 一般来说,发行的包的版本的配置选项和代码都是最稳定的,所以在大多 ...

  8. [LeetCode题解]234. 回文链表 | 快慢指针 + 反转链表

    解题思路 找到后半部分链表,再反转.然后与前半部分链表比较 代码 /** * Definition for singly-linked list. * public class ListNode { ...

  9. Spring第四天,BeanPostProcessor源码分析,彻底搞懂IOC注入及注解优先级问题!

  10. 云计算之路-出海记:建一个免费仓库 Amazon RDS for SQL Server

    上周由于园子后院起火,不得不调兵回去救火,出海记暂时停更,这周继续更新,"出海记"记录的是我们在 AWS 上建设博客园海外站的历程. 在这一记中记录的是我们基于 AWS 免费套餐( ...