单机模式下的定时任务调用很简单,有很多可实现的方案,这里不多说了。
这里说一下集群部署的情况下,定时任务的使用。这种情况下,quartz是一个比较好的选择。简单,稳定。
想象一下,现在有 A , B , C 3 台机器同时作为集群服务器对外统一提供 SERVICE :
A , B , C 3 台机器上各有一个 QUARTZ Job,它们会按照即定的 SCHEDULE 自动执行各自的任务。
先不说实现什么功能,这样的架构有点像多线程。由于三台SERVER 里都有 QUARTZ ,因此会存在重复处理 TASK 的现象。
一般外面的解决方案是只在一台 服务器上装 QUARTZ ,其它两台不装,这样的话其实就是单机了,quartz存在单点问题,一旦装有quartz的服务器宕了。服务就无法提供了。
当然还有其他一些解决方案,无非就是改 QUARTZ JOB 的代码了,这对程序开发人员来说比较痛苦;
而quartz本身提供了很好的集群方案。下面我们来说一下在spring boot下的集成:
quartz集群需要数据库的支持(JobStore TX或者JobStoreCMT),从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的
一、准备工作:
到quartz官网下载最新的包:http://www.quartz-scheduler.org/downloads/
解压后,可以看到结构目录。在\docs\dbTables下选择合适你数据库的SQL执行文件,创建quartz集群需要的表(共11张表)
二、与springboot整合,这里假定你已经配好数据库连接等,项目能正常运行:
添加quartz的支持
org.quartz-scheduler
quartz
2.3.0
添加quartz配置:
#quartz集群配置
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceid=AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount = 25
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold = 60000
#数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties = false
#数据库别名 随便取
org.quartz.jobStore.dataSource = myDS
#表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered = true
#调度实例失效的检查时间间隔
org.quartz.jobStore.clusterCheckinInterval = 20000
#============================================================================
# Configure Datasources
#============================================================================
#数据库引擎
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
#数据库连接
org.quartz.dataSource.myDS.URL = jdbc:mysql://172.30.12.14:7001/rbl_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
#数据库用户
org.quartz.dataSource.myDS.user = root
#数据库密码
org.quartz.dataSource.myDS.password = 123456
#允许最大连接
org.quartz.dataSource.myDS.maxConnections = 5
#验证查询sql,可以不设置
org.quartz.dataSource.myDS.validationQuery=select 0 from dual
注:如果嫌需要额外配置quart数据源很烦,也可以通过重写一个quartz里面的ConnectionProvider,共用你项目配置的数据库链接,这样每次更换数据库连接,就不需要额外在修改。
我这里用的是阿里的Druid数据源框架,实际用什么无所谓。
DruidConnectionProvider类:
/*
* 文件名:DruidConnectionProvider.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2018年3月20日
* 跟踪单号: 修改单号: 修改内容:
*/
import java.sql.Connection;
import java.sql.SQLException;
import org.quartz.utils.ConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.druid.pool.DruidDataSource;
import com.poly.pay.configuration.properties.DataSourcesProperties;
import com.poly.pay.service.merchant.MerchantInfoService;
import com.poly.rbl.utils.SpringContextUtil;
public class DruidConnectionProvider implements ConnectionProvider
{
// 数据库最大连接数
public int maxConnection;
// 数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
public String validationQuery;
private boolean validateOnCheckout;
private int idleConnectionValidationSeconds;
public String maxCachedStatementsPerConnection;
public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
// Druid连接池
private DruidDataSource datasource;
private DataSourcesProperties dataSourcesProperties;
@Override
public Connection getConnection()
throws SQLException
{
return datasource.getConnection();
}
@Override
public void shutdown()
throws SQLException
{
datasource.close();
}
@Override
public void initialize()
throws SQLException
{
dataSourcesProperties = SpringContextUtil.getBean(DataSourcesProperties.class);
if (dataSourcesProperties.getUrl() == null)
{
throw new SQLException("DBPool could not be created: DB URL cannot be null");
}
if (dataSourcesProperties.getDriverClassName() == null)
{
throw new SQLException(
"DBPool driver could not be created: DB driver class name cannot be null!");
}
if (this.maxConnection < 0)
{
throw new SQLException(
"DBPool maxConnectins could not be created: Max connections must be greater than zero!");
}
datasource = new DruidDataSource();
try
{
datasource.setDriverClassName(dataSourcesProperties.getDriverClassName());
}
catch (Exception e)
{
e.printStackTrace();
}
datasource.setUrl(dataSourcesProperties.getUrl());
datasource.setUsername(dataSourcesProperties.getUsername());
datasource.setPassword(dataSourcesProperties.getPassword());
datasource.setMaxActive(this.maxConnection);
datasource.setMinIdle(1);
datasource.setMaxWait(0);
datasource.setMaxPoolPreparedStatementPerConnectionSize(this.maxConnection);
if (this.validationQuery != null)
{
datasource.setValidationQuery(this.validationQuery);
if (!this.validateOnCheckout)
datasource.setTestOnReturn(true);
else
datasource.setTestOnBorrow(true);
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
public int getMaxConnection()
{
return maxConnection;
}
public void setMaxConnection(int maxConnection)
{
this.maxConnection = maxConnection;
}
public String getValidationQuery()
{
return validationQuery;
}
public void setValidationQuery(String validationQuery)
{
this.validationQuery = validationQuery;
}
public boolean isValidateOnCheckout()
{
return validateOnCheckout;
}
public void setValidateOnCheckout(boolean validateOnCheckout)
{
this.validateOnCheckout = validateOnCheckout;
}
public int getIdleConnectionValidationSeconds()
{
return idleConnectionValidationSeconds;
}
public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds)
{
this.idleConnectionValidationSeconds = idleConnectionValidationSeconds;
}
public String getMaxCachedStatementsPerConnection()
{
return maxCachedStatementsPerConnection;
}
public void setMaxCachedStatementsPerConnection(String maxCachedStatementsPerConnection)
{
this.maxCachedStatementsPerConnection = maxCachedStatementsPerConnection;
}
}
添加一个properties,通过spring自动注入配置数据源参数,这个不解释了。
/*
* 文件名:DataSourcesProperties.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2018年3月20日 跟踪单号:
* 修改单号: 修改内容:
*/
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourcesProperties
{
private String url;
private String driverClassName;
private String username;
private String password;
public String getUrl()
{
return url;
}
public void setUrl(String url)
{
this.url = url;
}
public String getDriverClassName()
{
return driverClassName;
}
public void setDriverClassName(String driverClassName)
{
this.driverClassName = driverClassName;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}
然后配置文件就可以改为:(注意看最后的自定义数据源配置),这样就大功告成,不需要额外配置数据源了
org.quartz.dataSource.myDS.connectionProvider.class=重写连接类的全类名
#quartz集群配置
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceid=AUTO
#禁用quartz软件更新
org.quartz.scheduler.skipUpdateCheck=true
#============================================================================
# Configure ThreadPool
#============================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount = 25
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold = 60000
#数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties = false
#数据库别名 随便取
org.quartz.jobStore.dataSource = myDS
#表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered = true
#调度实例失效的检查时间间隔
org.quartz.jobStore.clusterCheckinInterval = 20000
#============================================================================
# Configure Datasources
#============================================================================
#自定义连接池
org.quartz.dataSource.myDS.connectionProvider.class=com.poly.pay.schedule.DruidConnectionProvider
#允许最大连接
org.quartz.dataSource.myDS.maxConnection = 20
#验证查询sql,可以不设置
#org.quartz.dataSource.myDS.validationQuery=select 0 from dual
第一种:
添加任务注入的工厂类,否则任务注入会出错:
/*
* 文件名:InvokingJobDetailDetailFactory.java 版权:Copyright by www.poly.com 描述: 修改人:gogym
* 修改时间:2017年11月9日 跟踪单号: 修改单号: 修改内容:
*/
package com.poly.rbl.schedule;
import java.lang.reflect.Method;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class InvokingJobDetailDetailFactory extends QuartzJobBean
{
// 计划任务所在类
private String targetObject;
// 具体需要执行的计划任务
private String targetMethod;
private ApplicationContext ctx;
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException
{
try
{
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try
{
m = otargetObject.getClass().getMethod(targetMethod);
m.invoke(otargetObject);
}
catch (SecurityException e)
{
e.printStackTrace();
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
}
catch (Exception e)
{
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext)
{
this.ctx = applicationContext;
}
public void setTargetObject(String targetObject)
{
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod)
{
this.targetMethod = targetMethod;
}
}
配置quartz:
/*
* 文件名:QuartzConfig.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2017年11月9日 跟踪单号: 修改单号:
* 修改内容:
*/
package com.poly.rbl.configuration;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.poly.rbl.schedule.InvokingJobDetailDetailFactory;
@Configuration
public class QuartzConfig
{
// 配置文件路径
static final String QUARTZ_CONFIG = "properties/quartz.properties";
/**
* Description: 定义调用对象和调用对象的方法
*
* @param redisQuartzJob
* @return
* @see
*/
@Bean(name = "enjoyQuartzJobTask")
public JobDetailFactoryBean enjoyQuartzJobTask()
{
//集群模式下必须使用JobDetailFactoryBean, MethodInvokingJobDetailFactoryBean 类中的 methodInvoking 方法,是不支持序列化的
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setName("enjoyQuartzJob");// 设置任务的名字
bean.setGroup("enjoyQuartzJobGroup");// 设置任务的分组,这些属性都可以存储在数据库中,在多任务的时候使用
bean.setDurability(true);
bean.setRequestsRecovery(true);
bean.setJobClass(InvokingJobDetailDetailFactory.class);
Map map = new HashMap<>();
map.put("targetObject", "enjoyQuartzJob");//任务所在的类
map.put("targetMethod", "enjoyWork");//具体执行任务的方法
bean.setJobDataAsMap(map);
return bean;
}
/**
* Description: 定义任务触发规则
*
* @param redisTrigger
* @return
* @see
*/
@Bean(name = "enjoyQuartzJobTrigger")
public CronTriggerFactoryBean enjoyQuartzJobTrigger(@Qualifier("enjoyQuartzJobTask") JobDetail enjoyQuartzJobTask)
{
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(enjoyQuartzJobTask);
trigger.setCronExpression("0/5 * * * * ?");// cron表达式
trigger.setName("enjoyQuartzJobTrigger");// trigger的name
return trigger;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("enjoyQuartzJobTrigger") CronTriggerFactoryBean enjoyQuartzJobTrigger)
throws IOException
{
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
// QuartzScheduler 延时启动,应用启动完10秒后 QuartzScheduler 再启动
factory.setStartupDelay(10);
// 直接使用配置文件,用于quartz集群,加载quartz数据源配置
factory.setConfigLocation(new ClassPathResource(QUARTZ_CONFIG));
factory.setAutoStartup(true);
//集群需要通过QuartzJobBean注入,需要设置上下文
factory.setApplicationContextSchedulerContextKey("applicationContext");
// 注册触发器
Trigger[] triggers = {enjoyQuartzJobTrigger.getObject()};
factory.setTriggers(triggers);
return factory;
}
}
/*
* 文件名:EnjoyQuartzJob.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2017年11月9日 跟踪单号: 修改单号:
* 修改内容:
*/
package com.poly.rbl.schedule;
import org.springframework.stereotype.Component;
@Component("enjoyQuartzJob")
public class EnjoyQuartzJob
{
public void enjoyWork()
{
System.out.println("定时任务执行");
}
}
第二种,更加简洁好用:
/*
* 文件名:QuartzConfig.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2017年11月9日 跟踪单号: 修改单号:
* 修改内容:
*/
package com.poly.pay.configuration;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.poly.pay.schedule.JobRefundWeichartBean;
@Configuration
public class QuartzConfig
{
// 配置文件路径
static final String QUARTZ_CONFIG = "properties/quartz.properties";
// 定时任务组名称
public static final String Quartz_Group_Name = "enjoyQuartzJobGroup";
//定时任务方法后缀
public static final String Quartz_Job_Suffix = "_job";
//定时任务触发器后缀
public static final String Quartz_Trigger_Suffix = "_trigger";
@Bean(name = "triggers")
public CronTriggerImpl[] createTriggers()
throws ParseException
{
List l = new ArrayList();
l.add(createTrigger(JobRefundWeichartBean.class, "0/20 * * * * ?"));
//l.add(createTrigger(JobRefundWeichartBean.class, "0/20 * * * * ?"));
//按你的需要添加多个任务:任务所在类.class cron表达式
return l.toArray(new CronTriggerImpl[l.size()]);
}
private JobDetail create(Class> c)
{
JobDetailFactoryBean d = new JobDetailFactoryBean();
d.setDurability(true);
d.setRequestsRecovery(true);
d.setJobClass(c);
d.setName(c.getSimpleName() + Quartz_Job_Suffix);
d.setGroup(Quartz_Group_Name);
d.afterPropertiesSet();
JobDetail jd= d.getObject();
//jd.getJobDataMap().put("key", 123);//如果想通过jobDataMap传递值,在这里添加
return jd;
}
private CronTriggerImpl createTrigger(Class> t, String cronExpression) throws ParseException {
CronTriggerFactoryBean c = new CronTriggerFactoryBean();
c.setJobDetail(create(t));
c.setCronExpression(cronExpression);
c.setName(t.getSimpleName() + Quartz_Trigger_Suffix);
c.setGroup(Quartz_Group_Name); c.afterPropertiesSet();
return (CronTriggerImpl)c.getObject();
}
@Bean(name = "schedulerFactoryBean")
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("triggers") CronTriggerImpl[] triggers)
throws IOException, ParseException, SchedulerException
{
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 用于quartz集群,QuartzScheduler,启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
// QuartzScheduler 延时启动,应用启动完10秒后 QuartzScheduler 再启动
factory.setStartupDelay(10);
// 直接使用配置文件,用于quartz集群,加载quartz数据源配置
factory.setConfigLocation(new ClassPathResource(QUARTZ_CONFIG));
factory.setAutoStartup(true);
// 集群需要通过QuartzJobBean注入,需要设置上下文
factory.setApplicationContextSchedulerContextKey("applicationContext");
// 注册触发器
// factory.getScheduler().pauseAll();
factory.setTriggers(createTriggers());// 直接使用配置文件
// factory.setConfigLocation(new
// FileSystemResource(this.getClass().getResource("/quartz.properties").getPath()));
return factory;
}
/**
* 添加该方法的目的在于一个使用场景。如果代码中删除了不需要的定时任务,但是数据库中不会删除掉,会导致之前
* 的定时任务一直在运行,如果把定时任务依赖的类删除了,就会导致报错,找不到目标。所以配置动态删除任务
*/
@Bean
public String fulsh(@Qualifier("schedulerFactoryBean") SchedulerFactoryBean schedulerFactoryBean,
@Qualifier("triggers") CronTriggerImpl[] triggers)
throws SchedulerException
{
try
{
Scheduler s = schedulerFactoryBean.getScheduler();
if (null == s)
{
return "Scheduler is null";
}
// 最新配置的任务
List newTriNames = new ArrayList();
if (null != triggers)
{
for (CronTriggerImpl cronTriggerImpl : triggers)
{
newTriNames.add(cronTriggerImpl.getName());
}
}
// 现有数据库中已有的任务
Set myGroupTriggers = s.getTriggerKeys(GroupMatcher.triggerGroupEquals(Quartz_Group_Name));
if (null == myGroupTriggers || myGroupTriggers.size() == 0)
{
return "myGroupTriggers is null";
}
if (newTriNames != null && newTriNames.size() > 0)
{
for (TriggerKey triggerKey : myGroupTriggers)
{
String dbTriggerName = triggerKey.getName();
if (!newTriNames.contains(dbTriggerName))
{
// 暂停 触发器
s.pauseTrigger(triggerKey);
Trigger g = s.getTrigger(triggerKey);
JobKey jk = null;
if (null != g)
{
jk = g.getJobKey();
}
// 停止触发器
s.pauseTrigger(triggerKey);
// 注销 触发器
s.unscheduleJob(triggerKey);
if (null != jk)
{
// 暂停任务
s.pauseJob(jk);
// 删除任务
s.deleteJob(jk);
}
}
}
}
// 重要,如果不恢复所有,会导致无法使用
s.resumeAll();
}
catch (Exception e)
{
e.printStackTrace();
return "Exception:" + e.getMessage();
}
return "success";
}
}
任务类:
/*
* 文件名:JobRefundWeichartBean.java 版权:Copyright by www.poly.com 描述: 修改人:gogym 修改时间:2017年12月28日 跟踪单号:
* 修改单号: 修改内容:
*/
package com.poly.pay.schedule;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.scheduling.quartz.QuartzJobBean;
// 1.修改数据,防止并发,2不允许并发执行
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
//@Component
public class JobRefundWeichartBean extends QuartzJobBean {
//需要注入的类,如redis代理
private IRedisProxy redisProxy;
@Override
protected void executeInternal(JobExecutionContext arg0)
throws JobExecutionException {
//System.out.println("打印通过jobDataMap传递的值:"+arg0.getJobDetail().getJobDataMap().get("qqq"));
//需要使用的时候,通过SpringContextUtil获取spring托管的实例注入即可
redisProxy = SpringContextUtil.getBean(IRedisProxy.class);
System.out.println("执行方法");
}
}
注意:在任务类里面注入,不能通过@Autowired注解直接注入。因为任务类本身并没有被spring托管,所以注入是null。可以这样解决
1、加入@Component类注解,让spring托管。但这种方法某些环境下不一定能使用,不推荐
2、通过自定义SpringContextUtil类来注入问题:
SpringContextUtil工具类:(注意,这个类要@Component注册到spring(前提是这个类要能被spring 扫描到)。也可以通过代码注册,在@Configuration类里面加入@Bean即可,这样可以手动注册。二者选其一就行)
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 这个类是为了解决在普通类调用service的问题
*
* @ClassName SpringContextUtil
* @Description
* @author gogym [email protected]
* @date 2016-6-12
* @content OfflineMessageService offlineMessageService = (OfflineMessageService) SpringContextUtil
* .getBean("offlineMessageService");
*/
@Component
public class SpringContextUtil implements ApplicationContextAware
{
private static ApplicationContext applicationContext; // Spring应用上下文
// 下面的这个方法上加了@Override注解,原因是继承ApplicationContextAware接口是必须实现的方法
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext()
{
return applicationContext;
}
public static Object getBean(String name)
throws BeansException
{
return applicationContext.getBean(name);
}
public static Object getBean(String name, Class requiredType)
throws BeansException
{
return applicationContext.getBean(name, requiredType);
}
public static T getBean(Class clazz)
throws BeansException
{
return applicationContext.getBean(clazz);
}
public static boolean containsBean(String name)
{
return applicationContext.containsBean(name);
}
public static boolean isSingleton(String name)
throws NoSuchBeanDefinitionException
{
return applicationContext.isSingleton(name);
}
public static Class getType(String name)
throws NoSuchBeanDefinitionException
{
return applicationContext.getType(name);
}
public static String[] getAliases(String name)
throws NoSuchBeanDefinitionException
{
return applicationContext.getAliases(name);
}
}
启动即可看到数据库插入了任务相关的信息。集群完成。
需要注意的是:
当你运行水平集群时,时钟应当要同步,以免出现离奇且不可预知的行为。假如时钟没能够同步,Scheduler 实例将对其他节点的状态产生混乱。有几种简单的方法来保证时钟何持同步,而且也没有理由不这么做。最简单的同步计算机时钟的方式是使用某一个 Internet 时间服务器(Internet Time Server ITS)。
常用解决方案:
服务器中配置时间同步只要一台服务器同步互联网的时钟服务器,其它的服务以这台为时钟服务器!
Linux配置(局域网的客户端)
1、安装
yum install ntp (centos的安装方法)
2、先运行 # ntpdate 192.168.1.33 同步一次.
3、然后通过crontab计时器配置一个定时同步的任务,例如每月一号零点零分同步一次.代码如下:
# crontab -e //添下面一行,新建的定时任务文件保存在/var/spool/cron/下,以创建人的用户名命名
0 0 1 * * /etc/ntp/ntprsync.sh //每小时同步一次。
4、创建文件
# vi ntprsync.sh //内容如下 #!/bin/sh /usr/sbin/ntpdate 192.168.1.33 //时钟服务器的IP /sbin/hwclock –w
5、设置权限 chmod 777 ntprsync.sh
6、注意防火墙的设置.
7、成功。
8、服务启动。
/sbin/service crond start //启动服务/sbin/service crond stop //关闭服务/sbin/service crond restart //重启服务/sbin/service crond reload //重新载入配置