Spring集成quartz實現的定時任務調用

     本示例使用Spring+quartz實現定時任務的調度,通過zookeeper+curator實現分佈式鎖,保證分佈式任務串行運行,通過自定義註解實現任務的掃描及註冊;

1.添加相關的maven依賴,不包括spring

<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-client</artifactId>
	<version>2.8.0</version>
</dependency>
<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-framework</artifactId>
	<version>2.8.0</version>
</dependency>
<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-recipes</artifactId>
	<version>2.8.0</version>
</dependency>
<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>2.2.2</version>
</dependency>    

2.定義任務掃描的相關注解:

/**
 * 調度任務註解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scheduler {

}
/**
 *  調度任務是否爲安全的並行任務,默認爲true,不允許並行
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduleTaskConcurrent {
    /**
     * 任務名稱
     * @return
     */
    String  value();
}
/**
 * 任務表達式
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduleTaskCronExpression {
    /**
     * 任務名稱
     * @return
     */
    String value();
}
/**
 * 任務方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduleTaskMethod {
    /**
     * 任務名稱
     * @return
     */
    String value();
}
/**
 *  調度任務是否爲分佈式安全的並行任務,默認爲true,不允許並行
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduleTaskRemoteConcurrent {
    /**
     * 任務名稱
     * @return
     */
    String value();
}
/**
 * 任務是否執行
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduleTaskRunning {
    /**
     * 任務名稱
     * @return
     */
    String value();
}



3.定義任務對象對應的實體bean:

/**
 * 任務對象
 */
public class TaskBean {

    private String name;
    private String cronExpression;
    private Boolean concurrent = true;
    private Boolean remoteConcurrent=true;
    private Boolean running = true;
    private Object targetObject;
    private String targetMethod;

    public TaskBean(String name, String cronExpression, Boolean running, Boolean concurrent, Boolean remoteConcurrent,Object targetObject, String targetMethod) {
        this.name = name;
        this.cronExpression = cronExpression;
        this.concurrent = concurrent;
        this.remoteConcurrent = remoteConcurrent;
        this.running = running;
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
    }


    public TaskBean(String cronExpression, Object targetObject, String targetMethod) {
        this(null, cronExpression, null, null, targetObject, targetMethod);
    }

    public TaskBean(String name, String cronExpression, Object targetObject, String targetMethod) {
        this(name, cronExpression, null, null, targetObject, targetMethod);
    }

    public TaskBean(String cronExpression, Boolean running, Boolean stateful, Object targetObject, String targetMethod) {
        this(null, cronExpression, running, stateful, targetObject, targetMethod);
    }

    public TaskBean(String name, String cronExpression, Boolean running, Boolean concurrent, Object targetObject, String targetMethod) {
        this.name = StringUtils.isEmpty(name) ? getDefaultName() : name;
        this.cronExpression = cronExpression;
        this.running = running != null ? running : this.running;
        this.concurrent = concurrent != null ? concurrent : this.concurrent;
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
    }

    public TaskBean(String name, String cronExpression, Boolean running, Boolean concurrent,
                           Object targetObject, String targetMethod,Boolean remoteConcurrent) {
        this.name = StringUtils.isEmpty(name) ? getDefaultName() : name;
        this.cronExpression = cronExpression;
        this.running = running != null ? running : this.running;
        this.concurrent = concurrent != null ? concurrent : this.concurrent;
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
        this.remoteConcurrent = remoteConcurrent != null ? remoteConcurrent : this.remoteConcurrent;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public Boolean getConcurrent() {
        return concurrent;
    }

    public void setConcurrent(Boolean concurrent) {
        this.concurrent = concurrent;
    }

    public Boolean getRemoteConcurrent() {
        return remoteConcurrent;
    }

    public void setRemoteConcurrent(Boolean remoteConcurrent) {
        this.remoteConcurrent = remoteConcurrent;
    }

    public Boolean getRunning() {
        return running;
    }

    public void setRunning(Boolean running) {
        this.running = running;
    }

    public Object getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    public String getTargetMethod() {
        return targetMethod;
    }

    public void setTargetMethod(String targetMethod) {
        this.targetMethod = targetMethod;
    }

    private String getDefaultName() {
        return this.targetObject.getClass().getName();
    }

}

4.定義任務初始化工廠類及其實現:

/**
 * 任務初始化工廠類
 */
public interface ScheduleFactory {
    public void init(List<TaskBean> taskBeanList) throws Exception;
}

@Component("quartzSchedulerFactory")
public class QuartzScheduleFactory implements ScheduleFactory{

    @Autowired
    private Scheduler quartzScheduler;

    public void init(List<TaskBean> taskBeanList) throws Exception {
        if(taskBeanList ==null || taskBeanList.size()<=0 ){
            return;
        }
        for(TaskBean taskBean : taskBeanList){
            if (taskBean != null) {
                if (taskBean.getRunning()) {
                    QuartzSchedulerUtils.createScheduleJob(quartzScheduler, taskBean);
                }
            }
        }
    }

}

5.zk實現的分佈式鎖:

zk配置信息的加載

public class LocalZookeeperPropertiesLoader{

    private static void putConfig(Properties localProperties, Map.Entry entry) {
        if (entry.getKey().equals(ZookeeperConfigConstants.ZOOKEEPER_ADDRESS)) {
            localProperties.put(ZookeeperConfigConstants.ZOOKEEPER_ADDRESS, entry.getValue());
        }
    }

    /**
     * 從默認配置文件 config.properties 中得到ZooKeeper的配置信息
     * 其中 ZooKeeper 的地址由 (系統屬性 優先於 環境變量 優先於 配置文件)指定
     * @return 配置信息
     */
    @Override
    public Properties load() {
        Properties localProperties = new Properties();
        try {
            InputStream resourceAsStream = LocalZookeeperPropertiesLoader.class.getClassLoader().getResourceAsStream
                    ("config" + ".properties");
            localProperties.load(resourceAsStream);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        System.getenv().entrySet().forEach(entry -> putConfig(localProperties, entry));
        System.getProperties().entrySet().forEach(entry -> putConfig(localProperties, entry));

        return localProperties;
    }

    @Override
    public void destroy() {
    }

 初始化curatorFrameWork

public class CRMSCuratorFrameworkFactory {
    private CRMSCuratorFrameworkFactory() {
    }

    public static CuratorFramework createCuratorFramework(CuratorProperties configuration){
        return createCuratorFramework(configuration, null);
    }
    public static CuratorFramework createCuratorFramework(CuratorProperties curatorProperties, TreeCacheListener listener, String... paths){
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString(curatorProperties.getConnectString()).
                connectionTimeoutMs(curatorProperties.getConnectionTimeoutMs()).retryPolicy(curatorProperties.getRetryPolicy()).
                sessionTimeoutMs(curatorProperties.getSessionTimeoutMs()).build();
        curatorFramework.start();
        if(paths.length > 0 && listener != null){
            for(String path :paths){
                try {
                    if(curatorFramework.checkExists().forPath(path)==null){
                        curatorFramework.create().creatingParentsIfNeeded().forPath(path);
                    }
                    TreeCache watcher =TreeCache.newBuilder(curatorFramework,path).build();
                    watcher.getListenable().addListener(listener);
                    watcher.start();
                }catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
        return  curatorFramework;
    }
}

互斥鎖獲取後的回調

@FunctionalInterface
public interface MutexLockCallable {
    void call();
}

 分佈式鎖的實現

//zk實現的分佈式互斥鎖
public class ZookeeperMetuxLock {
    private static Logger log = LoggerFactory.getLogger(ZookeeperMetuxLock.class);
    /**
     * 分佈式互斥鎖
     * @param lockName 鎖對應的zk節點的名稱
     * @param time     阻塞時間
     * @param unit     阻塞時間單位
     * @param callback  獲得鎖需要執行的內容
     */
    public static boolean execute(String lockName,long time,TimeUnit unit, MetuxLockCallble callback){
        boolean acquired = false;
        CuratorProperties curatorProperties = CuratorPropertiesBuilder.getInstance().build(new LocalZookeeperPropertiesLoader().load());
        try (CuratorFramework curatorFramework = CRMSCuratorFrameworkFactory.createCuratorFramework(curatorProperties)) {
            //互斥分佈式鎖
            InterProcessMutex mutexLock = new InterProcessMutex(curatorFramework, lockName);
            if (mutexLock.acquire(time, unit)) {
                try {
                    callback.call();
                } finally {
                    mutexLock.release();
                    acquired = true;
                }
            }
        } catch (Exception ex) {
            log.error("MutexLock lock error! message:" + ex.getMessage(), ex);
        } finally {
            return acquired;
        }
    }

    /**
     * 分佈式互斥鎖
     *
     * @param lockName 鎖對應的zookeeper節點名字
     * @param callback 獲取鎖後執行的內容
     * @return 是否成功獲取鎖
     */
    public static boolean execute(String lockName, MetuxLockCallble callback) {
        return execute(lockName, -1, null, callback);
    }
}

 6.quartz的job的實現:

public class QuartzJob implements Job {
    private static Logger log = LoggerFactory.getLogger(QuartzJob .class);

    private static final String MUTEX_PATH_ON_ZOOKEEPER_PREFIX = "/scheduler_lock/";

    public void execute(JobExecutionContext context) throws JobExecutionException {
        final TaskFactoryBean taskFactoryBean = (TaskFactoryBean) context.getMergedJobDataMap().get(QuartzSchedulerUtils.SCHEDULEFACTORYBEAN);
        if (taskFactoryBean.getRemoteConcurrent()) {
            String lockName = MUTEX_PATH_ON_ZOOKEEPER_PREFIX + taskFactoryBean.getName();
            ZookeeperMutexLock.execute(lockName, 0, TimeUnit.MICROSECONDS, () -> {
                QuartzJob .executeTask(taskFactoryBean);
            });
        } else {
            executeTask(taskFactoryBean);
        }
    }

    private static void executeTask(TaskFactoryBean taskFactoryBean) {
        try {
            Object targetObject = taskFactoryBean.getTargetObject();
            String targetMethod = taskFactoryBean.getTargetMethod();
            Method method = targetObject.getClass().getMethod(targetMethod, new Class[]{});
            if (method != null) {
                method.invoke(targetObject, new Object[]{});
            } else {
                log.error("task " + taskFactoryBean.getName() + " execute error! message: execute method is null");
            }
        } catch (Exception ex) {
            log.error("task " + taskFactoryBean.getName() + " execute error! message:" + ex.getMessage(), ex);
        }
    }

 7.創建job的工具類:

public class QuartzSchedulerUtils {
    public static final String SCHEDULEFACTORYBEAN = "scheduleFactoryBean";

    public static void createScheduleJob(Scheduler scheduler, TaskBean taskBean)throws SchedulerException {
        //同步或者異步
        Class<? extends Job> jobClass =  QuartzJob.class;

        //構建job信息
        String  jobName = taskBean.getName();
        String jobGroupName = jobName;

        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName,jobGroupName).build();

        //放入參數,運行時的方法可以獲取
        jobDetail.getJobDataMap().put(SCHEDULEFACTORYBEAN,taskBean);

        //表達式調度構造器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(taskBean.getCronExpression());

        //按新的表達式構造一個新的trigger
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName,jobGroupName).withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }
}

8.定義調度管理的入口類:

@Component("schedulerManager")
@Lazy(false)
public class SchedulerManager implements ApplicationContextAware {
    private static Logger log = LoggerFactory.getLogger(SchedulerManager.class);
    private List<TaskFactoryBean> taskFactoryBeen = new ArrayList<TaskFactoryBean>();
    private SchedulerFactory schedulerFactory;

    @PostConstruct
    public void init() throws Exception {
        schedulerFactory.init(taskFactoryBeen);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        initTaskFactoryBeen(applicationContext);
        schedulerFactory = (SchedulerFactory) applicationContext.getBean("quartzSchedulerFactory");
    }

    private void initTaskFactoryBeen(ApplicationContext applicationContext) {
        Map<String, Object> taskBeanMap = applicationContext.getBeansWithAnnotation(Scheduler.class);
        if (taskBeanMap != null && taskBeanMap.size() > 0) {
            for (String beanName : taskBeanMap.keySet()) {
                Object taskBean = taskBeanMap.get(beanName);
                try {
                    List<TaskFactoryBean> currentTaskFactoryBeen = buildTaskFactoryBean(taskBean);
                    if (currentTaskFactoryBeen != null && currentTaskFactoryBeen.size() > 0) {
                        taskFactoryBeen.addAll(currentTaskFactoryBeen);
                    }
                } catch (Exception ex) {
                    log.error("Initializes the scheduling bean " + beanName + " failure ! message:" + ex.getMessage(), ex);
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    private List<TaskFactoryBean> buildTaskFactoryBean(Object taskBean) throws Exception {
        Object targetBean = SpringTargetBeanUtils.getTarget(taskBean);
        Map<String, String> cronMap = new HashMap<String, String>();
        Map<String, Boolean> runningMap = new HashMap<String, Boolean>();
        Map<String, Boolean> concurrentMap = new HashMap<String, Boolean>();
        Map<String, Boolean> remoteConcurrentMap = new HashMap<String, Boolean>();
        Field[] fields = targetBean.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(ScheduleTaskCronExpression.class)) {
                ScheduleTaskCronExpression scheduleTaskCronExpression = field.getAnnotation(ScheduleTaskCronExpression.class);
                String taskName = scheduleTaskCronExpression.value();
                String cronExpression = (String) field.get(targetBean);
                cronMap.put(taskName, cronExpression);
            } else if (field.isAnnotationPresent(ScheduleTaskRunning.class)) {
                ScheduleTaskRunning scheduleTaskRunning = field.getAnnotation(ScheduleTaskRunning.class);
                String taskName = scheduleTaskRunning.value();
                Boolean running = (Boolean) field.get(targetBean);
                runningMap.put(taskName, running);
            } else if (field.isAnnotationPresent(ScheduleTaskConcurrent.class)) {
                ScheduleTaskConcurrent scheduleTaskConcurrent = field.getAnnotation(ScheduleTaskConcurrent.class);
                String taskName = scheduleTaskConcurrent.value();
                Boolean concurrent = (Boolean) field.get(targetBean);
                concurrentMap.put(taskName, concurrent);
            }else if (field.isAnnotationPresent(ScheduleTaskRemoteConcurrent.class)) {
                ScheduleTaskRemoteConcurrent scheduleTaskRemoteConcurrent = field.getAnnotation(ScheduleTaskRemoteConcurrent.class);
                String taskName = scheduleTaskRemoteConcurrent.value();
                Boolean concurrent = (Boolean) field.get(targetBean);
                remoteConcurrentMap.put(taskName, concurrent);
            }
        }

        List<TaskFactoryBean> currentTaskFactoryBeen = new ArrayList<TaskFactoryBean>();
        Method[] methods = targetBean.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(ScheduleTaskMethod.class)) {
                ScheduleTaskMethod scheduleTaskMethod = method.getAnnotation(ScheduleTaskMethod.class);
                String taskName = scheduleTaskMethod.value();
                String methodName = method.getName();
                String cronExpression = cronMap.get(taskName);
                Boolean running = runningMap.get(taskName);
                Boolean concurrent = concurrentMap.get(taskName);
                Boolean remoteConcurrent = remoteConcurrentMap.get(taskName);
                TaskFactoryBean taskFactoryBean = new TaskFactoryBean(taskName, cronExpression, running, concurrent, taskBean, methodName, remoteConcurrent);
                currentTaskFactoryBeen.add(taskFactoryBean);
            }
        }
        return currentTaskFactoryBeen;
    }

    public List<TaskFactoryBean> getTaskFactoryBeen() {
        return taskFactoryBeen;
    }

9.相關spring及zk的配置:

<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false">
	<!-- quartz 延時加載 -->
	<property name="startupDelay" value="1"/>
</bean>

config.properies

#zookeeper addresss
zk_address=ip:2181
#zk properties
zookeeper.config.sessionTimeoutMs=60000
zookeeper.config.connectionTimeoutMs=3000
zookeeper.config.retry.baseSleepTimeMs=100
zookeeper.config.retry.maxRetries=3

10. 創建test類:

@Component
@Scheduler
public class TestTask {
    private static final String ADDUSERTASK = "TestTask";

    @ScheduleTaskCronExpression(ADDUSERTASK)
    private String taskEx = "* * * * * ? *";

    @ScheduleTaskRunning(ADDUSERTASK)
    private Boolean taskRunning = true;

    @ScheduleTaskRemoteConcurrent(ADDUSERTASK)
    private Boolean remoteCon = true;


    @ScheduleTaskMethod(ADDUSERTASK)
    public void task() {
        System.out.println("this is the task");
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章