原文:https://blog.csdn.net/wzs298/article/details/77608670
1、需要了解的技术点:
1.1、Redis的命令:SETNX,EXPIRE;
1.2、
Spring的Scheduled定时器注解,触发器,任务,调度器;
1.3、
Spring的applicationContext上下文对象,自定义注解,java反射机制;
2、思路:
2.1、创建一个自定义注解,参数:cron(时间格式);
2.2、创建一个@
Component组件,用来实现自定义注解的功能,
2.2.1、实现
ApplicationContextAware
接口,用来获取spring的ApplicationContext上下文对象;
2.2.2、
实现
BeanPostProcessor接口,用来获取自定义注解所对应的方法;
2
.2.3、实现
SchedulingConfigurer
接口,用来创建定时器任务;
2.2.4、创建一个实现Runabel接口的类,用来反射自定义注解所对应的方法和抢占redis的锁;
2.2.5、创建一个实现
Trigger接口的
触发器对象,用来获取下一次执行任务的时间,以便给redis设置锁的生存时间;
2.3、程序执行流程:
2.3.1、给需要加定时器的方法加上自定义注解
2.3.2、程序启动,获取spring上下文对象;
2.3.3、扫描自定义注解所对应的方法;
2.3.4、根据每个自定义注解的信息创建对应触发器和任务;
2.3.5、调度器触发任务时,先去抢占锁,再根据情况判断本实例是否要执行任务;
3、代码分解:
3.1、创建自定义注解;
- @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- public @interface KyScheduled {
- /**
- * A cron-like expression, extending the usual UN*X definition to include
- * triggers on the second as well as minute, hour, day of month, month
- * and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on
- * weekdays (at the top of the minute - the 0th second).
- * @return an expression that can be parsed to a cron schedule
- */
- String cron() default "";
- }
- public class KyScheduledExecution {
- public class KyTask {
- private KyScheduled kyScheduled;
- private Method kyMethod;
- public KyScheduled getKyScheduled() {
- return kyScheduled;
- }
- public void setKyScheduled(KyScheduled kyScheduled) {
- this.kyScheduled = kyScheduled;
- }
- public Method getKyMethod() {
- return kyMethod;
- }
- public void setKyMethod(Method kyMethod) {
- this.kyMethod = kyMethod;
- }
- }
- }
3.3、实现
ApplicationContextAware接口,获取spring上下文对象;
原因:如果单纯使用java的反射机制,当定时器任务使用
@Autowired
注解时,会获取不到bean实例,所以要实现
ApplicationContextAware接口
;
- @Component
- public class KyScheduledExecution implements ApplicationContextAware {
- private ApplicationContext applicationContext;
- @Override
- public void setApplicationContext(ApplicationContext context) throws BeansException {
- this.applicationContext = context;
- }
- private Object getBean(Class classname) {
- try {
- return this.applicationContext.getBean(classname);
- } catch (Exception e) {
- log.error(e);
- return "";
- }
- }
- }
3.4、实现BeanPostProcessor接口,获取自定义注解信息;
- @Component
- public class KyScheduledExecution implements BeanPostProcessor {
- private Log log = LogFactory.getLog(getClass());
- //记录任务集合
- private List<KyTask> kyTaskList = new ArrayList<>();
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
- /**
- * 获取所有自定义注解,并记录注解和方法的信息
- * @param bean bean
- * @param beanName beanName
- * @return Object
- * @throws BeansException BeansException
- */
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
- if (methods != null) {
- for (Method method : methods) {
- KyScheduled annotation = AnnotationUtils.findAnnotation(method, KyScheduled.class);
- if (annotation != null && !"".equals(annotation.cron())) {
- KyTask at = new KyTask();
- at.setKyScheduled(annotation);
- at.setKyMethod(method);
- kyTaskList.add(at);
- }
- }
- }
- return bean;
- }
- }
- public class KyScheduledExecution{
- private Log log = LogFactory.getLog(getClass());
- @Value("${spring.redis.host}")
- private String redisHost;
- @Value("${spring.redis.port}")
- private int redisPort;
- private Jedis jedis;
- //记录任务集合
- private List<KyTask> kyTaskList = new ArrayList<>();
- private ApplicationContext applicationContext;
- /**
- * 创建redis客户端
- */
- private void createRedisClient() {
- if (jedis == null) {
- jedis = new Jedis(redisHost, redisPort);
- }
- }
- /**
- * 获取分布式锁
- *
- * @param lockName 锁名称
- * @param second 加锁时间(秒)
- * @return 如果获取到锁,则返回lockId值,否则为null
- */
- private String setnxLock(String lockName, int second) {
- synchronized (this) {
- //生成随机的Value值
- String lockId = UUID.randomUUID().toString();
- //抢占锁
- Long lock = this.jedis.setnx(lockName, lockId);
- if (lock == 1) {
- //拿到Lock,设置超时时间
- this.jedis.expire(lockName, second - 1);
- return lockId;
- }
- }
- return null;
- }
- }
- public class KyTrigger implements Trigger, Serializable {
- private String cron;
- private boolean syncLock;
- public KyTrigger(KyScheduled kyScheduled){
- if(kyScheduled.cron() != null && !"".equals(kyScheduled.cron())) {
- this.cron = kyScheduled.cron();
- }
- this.syncLock = kyScheduled.synclock();
- }
- public boolean getSyncLock(){
- return this.syncLock;
- }
- public String getCron() {
- return cron;
- }
- public void setCron(String cron) {
- if(cron != null && !"".equals(cron)) {
- this.cron = cron;
- }
- }
- @Override
- public Date nextExecutionTime(TriggerContext triggerContext) {
- CronTrigger cronTrigger = new CronTrigger(this.cron);
- return cronTrigger.nextExecutionTime(triggerContext);
- }
- }
- public class KyScheduledExecution{
- private Log log = LogFactory.getLog(getClass());
- /**
- * 任务对象
- */
- public class Job implements Runnable {
- private Method method;
- private String lockName;
- private Object invokeMethod;
- private Trigger trigger;
- public String getLockName() {
- return lockName;
- }
- Job(Method m, Trigger t) {
- this.trigger = t;
- this.invokeMethod = getBean(m.getDeclaringClass());//获取bean实例
- this.lockName = m.getDeclaringClass().getName() + "." + m.getName();//构造LockName
- this.method = m;
- }
- @Override
- public void run() {
- //获取下次执行时间(秒)
- long nextTime = (this.trigger.nextExecutionTime(new SimpleTriggerContext()).getTime() - new Date().getTime()) / 1000;
- //抢占分布式锁
- String result = setnxLock(this.lockName, (int) nextTime);
- if (result != null && !"".equals(result)) {
- try {
- //执行自定义注解的方法
- this.method.invoke(this.invokeMethod);
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- log.error(e);
- }
- }
- }
- }
- }
3.7、
在
KyScheduledExecution
类中
实现
SchedulingConfigurer接口;
- @Component
- public class KyScheduledExecution implements SchedulingConfigurer{
- private Log log = LogFactory.getLog(getClass());
- /**
- * 配置定时器
- *
- * @param taskRegistrar ScheduledTaskRegistrar
- */
- @Override
- public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
- if (taskRegistrar != null) {
- for (KyTask kt : kyTaskList) {
- Method method = kt.getKyMethod();
- //创建触发器
- KyTrigger trigger = new KyTrigger(kt.getKyScheduled());
- //创建任务
- Job job = new Job(method, trigger);
- //将任务加入调度器中
- taskRegistrar.addTriggerTask(job, trigger);
- }
- }
- }
- }
代码分解完毕,以上为所有代码!!!