spring-boot-quartz, 依赖spring-boot-parent
/**
*
state的值代表该任务触发器的状态:
STATE_BLOCKED 4 // 运行
STATE_COMPLETE 2 //完成那一刻,不过一般不用这个判断Job状态
STATE_ERROR 3 // 错误
STATE_NONE -1 //未知
STATE_NORMAL 0 //正常无任务,用这个判断Job是否在运行
STATE_PAUSED 1 //暂停状态
*/
import java.util.Date; import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; import cn.wa8.qweb.extract.action.Extract2DB;
public class SimpleRun { private static Logger log = LoggerFactory.getLogger(SimpleRun.class); public void run() throws Exception {
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
JobDetail jobDetail = new JobDetail("myJob",null,SimpleJob.class);
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
null,
new Date(),
null,
SimpleTrigger.REPEAT_INDEFINITELY,
30L * 1000L); sched.scheduleJob(jobDetail, trigger);
//sched.addJobListener(new MyTriggerListener());
SimpleJob.preDate = new Date();
sched.start();
System.out.println("starting");
/**
*
state的值代表该任务触发器的状态:
STATE_BLOCKED 4 // 运行
STATE_COMPLETE 2 //完成那一刻,不过一般不用这个判断Job状态
STATE_ERROR 3 // 错误
STATE_NONE -1 //未知
STATE_NORMAL 0 //正常无任务,用这个判断Job是否在运行
STATE_PAUSED 1 //暂停状态
*/
while (true){
if(4 == sched.getTriggerState("myTrigger", null)){
System.out.println("running");
}else if(0 == sched.getTriggerState("myTrigger", null)){
System.out.println("ending");
}else {
System.out.println("error state:"+sched.getTriggerState("myTrigger", null));
}
try {
Thread.sleep(5*1000);
} catch (Exception e) {
// TODO: handle exception
}
}
} public static void main(String[] args) { SimpleRun simpleRun = new SimpleRun(); try {
simpleRun.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import cn.wa8.qweb.extract.action.Extract2DB; import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.StatefulJob;
/*Extract2DB extract2db = new Extract2DB();
extract2db.CommonBaseExtract();*/ public class SimpleJob implements StatefulJob{
public static Date preDate ;
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("into Job");
Date currentDate = new Date();
Long s = (currentDate.getTime()-preDate.getTime())/1000;
try {
Thread.sleep(10*1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(s);
System.out.println("leave Job:"+Thread.currentThread().toString());
preDate =currentDate;
} }
http://blog.csdn.net/u010666884/article/details/51842610
都是执行时间大于间隔时间才会出现的情况,实际做了测试和http://blog.sina.com.cn/s/blog_56d8ea900100cecq.html第2点有点不符,记录如下:
第一种情况:misfire设置时间为600秒;任务每隔2分钟执行一次;任务执行时间为3分钟;
上次执行时间 下次执行时间 状态 解释
20:23:04 20:25:04 正在执行 任务实际要执行到20:26:04,推后两分钟是20:28:04
时间到了20:26:04。日志变更为:
20:25:04 20:27:04 正在执行 任务实际要执行的20:29:04,推后两分钟是20:31:04
时间到了20:29:04,日志变更为:
20:27:04 20:29:04
连续三次发现,实际开始的时间减去应该开始的时间差是递增的;上次执行和下次执行时间反映的实际情况都是不准确的,而且会出现下次执行时间小于当前时间的情况。
注意:持续执行的到 实际启动时间 减去 应该开始时间 大于等于misfire时间;奇怪的是,不是开始参考第二种情况继续执行,而是最后一次执行后即开始长时间等待,而且上次以及下次开始时间也不更新,保持原样;直到实际启动时间+misfire时间 时刻开始继续执行,并且更新上次以及下次开始时间,再开始一个上述周期。
第二种情况:misfire设置时间为6秒,任务每隔2分钟执行一次,任务执行时间为3分钟:
上次执行时间 下次执行时间 状态 解释
18:08:12 18:10:12 正在执行 任务实际要执行到18:11:12,推后两分钟是18:13:12
时间到了18:11:12,日志变更为:
18:08:12 18:10:12 等待 超出misfire时间;任务还没有更新状态
18:08:12 18:12:12 等待 是18:12:12,而不是18:13:12。
算法描述如下:
本次任务应该开始时间为18:10:12,应该结束时间为18:12:12;实际启动时间为18:11:12;实际启动后结束时间为18:13:12;实际启动时间减去应该开始时间超出了misfire,所以状态为等待,即本次任务不执行,从而上次执行时间不变;
计算下次执行时间:当前时间为18:11:12(或者一个稍微大于该值的值),拿应该结束时间以及实际启动后结束时间和当前时间比较,取当前时间往后的最小值作为下次任务启动时间。(算法兼容下面第2点说法)
18:12:12 18:14:12 正在执行
其他引用:
org.quartz.jobStore.misfireThreshold = 60000 #60秒 默认值
那么执行第一次作业是在10:01秒,这时会设定下一次的执行时间为10:02秒,要等一个作业执行完之后才有可用线程,大概要在10:11秒才能执行前面安排的应该在10:02执行的作业,这时就会用到misfireThreshold, 因为10:11与10:02之间的差值小于6000,所以执行该作业,并以10:02为基准设置下一次执行时间为10:03,这样造成每次实际执行时间与安排时间错位
如果 org.quartz.jobStore.misfireThreshold = 6000 #秒
同样,在10:11计划执行安排在10:02的作业,发现10:11与10:02之间的差值小于6000,那么直接跳过该作业,执行本应在当前时间执行的 作业,这时候会以10:11为基准设定下次作业执行时间为10:12(状态此段区间内一直是等待,只是更改了下次作业时间)
其他情况:
quartz有个全局的参数misfireThreshold设置可以允许的超时时间,超过了就不执行,未超过就执行。
比如设置了misfireThreshold=30分钟,如果一个任务定时在10:30执行,但在10:29服务器挂了,在10:50启动,虽然任务超时了21分钟,但小于misfireThreshold,所以还是可以执行。
而如果服务器11:10才启动,那就misfire了。
对于周期性的任务,如果有misfire的情况出现,则会自动更新CronTrigger的时间周期
默认情况下会在当前时间马上执行前一个被misfire的任务
而如果设置MISFIRE_INSTRUCTION_DO_NOTHING,则不对misfire的任务做特殊处理,只从当前时间之后的下一次正常调度时间开始执行
http://blog.sina.com.cn/s/blog_56d8ea900101d2mh.html
http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html
spring-boot-quartz, 依赖spring-boot-parent
- 项目启动后输入:http://localhost/
- 数据库文件: https://github.com/leelance/spring-boot-all/blob/master/spring-boot-quartz/src/main/resources/demo-schema.sql
application.properties
# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.index=WebQuartz.v1.1
spring.application.name=WebQuartz
#Server
server.port=80
server.jsp-servlet.class-name=org.apache.jasper.servlet.JspServlet
security.basic.enabled=false
management.security.enabled=false
#MVC
spring.mvc.view.prefix=/WEB-INF/views/
spring.resources.static-locations=classpath:/static/
security.basic.enabled=false
management.security.enabled=false
#LOG
logging.config=classpath:log4j2.xml
configuration
@Configuration
public class QuartzConfig { @Bean
public Scheduler scheduler() throws IOException, SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory(quartzProperties());
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();
return scheduler;
} /**
* 设置quartz属性
* @throws IOException
* 2016年10月8日下午2:39:05
*/
public Properties quartzProperties() throws IOException {
Properties prop = new Properties();
prop.put("quartz.scheduler.instanceName", "ServerScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
prop.put("org.quartz.scheduler.skipUpdateCheck", "true");
prop.put("org.quartz.scheduler.instanceId", "NON_CLUSTERED");
prop.put("org.quartz.scheduler.jobFactory.class", "org.quartz.simpl.SimpleJobFactory");
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
prop.put("org.quartz.jobStore.dataSource", "quartzDataSource");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "5"); prop.put("org.quartz.dataSource.quartzDataSource.driver", "com.mysql.jdbc.Driver");
prop.put("org.quartz.dataSource.quartzDataSource.URL", "jdbc:mysql://localhost:3306/demo-schema");
prop.put("org.quartz.dataSource.quartzDataSource.user", "root");
prop.put("org.quartz.dataSource.quartzDataSource.password", "123456");
prop.put("org.quartz.dataSource.quartzDataSource.maxConnections", "10");
return prop;
}
}
JS
@Service
public class TaskServiceImpl {
private Logger logger = LogManager.getLogger(getClass());
@Autowired
private Scheduler scheduler; /**
* 所有任务列表
* 2016年10月9日上午11:16:59
*/
public List<TaskInfo> list(){
List<TaskInfo> list = new ArrayList<>(); try {
for(String groupJob: scheduler.getJobGroupNames()){
for(JobKey jobKey: scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(groupJob))){
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger: triggers) {
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
JobDetail jobDetail = scheduler.getJobDetail(jobKey); String cronExpression = "", createTime = ""; if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
cronExpression = cronTrigger.getCronExpression();
createTime = cronTrigger.getDescription();
}
TaskInfo info = new TaskInfo();
info.setJobName(jobKey.getName());
info.setJobGroup(jobKey.getGroup());
info.setJobDescription(jobDetail.getDescription());
info.setJobStatus(triggerState.name());
info.setCronExpression(cronExpression);
info.setCreateTime(createTime);
list.add(info);
}
}
}
} catch (SchedulerException e) {
e.printStackTrace();
} return list;
} /**
* 保存定时任务
* @param info
* 2016年10月9日上午11:30:40
*/
@SuppressWarnings("unchecked")
public void addJob(TaskInfo info) {
String jobName = info.getJobName(),
jobGroup = info.getJobGroup(),
cronExpression = info.getCronExpression(),
jobDescription = info.getJobDescription(),
createTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
try {
if (checkExists(jobName, jobGroup)) {
logger.info("===> AddJob fail, job already exist, jobGroup:{}, jobName:{}", jobGroup, jobName);
throw new ServiceException(String.format("Job已经存在, jobName:{%s},jobGroup:{%s}", jobName, jobGroup));
} TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
JobKey jobKey = JobKey.jobKey(jobName, jobGroup); CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withDescription(createTime).withSchedule(schedBuilder).build(); Class<? extends Job> clazz = (Class<? extends Job>)Class.forName(jobName);
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobKey).withDescription(jobDescription).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException | ClassNotFoundException e) {
throw new ServiceException("类名不存在或执行表达式错误");
}
} /**
* 修改定时任务
* @param info
* 2016年10月9日下午2:20:07
*/
public void edit(TaskInfo info) {
String jobName = info.getJobName(),
jobGroup = info.getJobGroup(),
cronExpression = info.getCronExpression(),
jobDescription = info.getJobDescription(),
createTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
try {
if (!checkExists(jobName, jobGroup)) {
throw new ServiceException(String.format("Job不存在, jobName:{%s},jobGroup:{%s}", jobName, jobGroup));
}
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
JobKey jobKey = new JobKey(jobName, jobGroup);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withDescription(createTime).withSchedule(cronScheduleBuilder).build(); JobDetail jobDetail = scheduler.getJobDetail(jobKey);
jobDetail.getJobBuilder().withDescription(jobDescription);
HashSet<Trigger> triggerSet = new HashSet<>();
triggerSet.add(cronTrigger); scheduler.scheduleJob(jobDetail, triggerSet, true);
} catch (SchedulerException e) {
throw new ServiceException("类名不存在或执行表达式错误");
}
} /**
* 删除定时任务
* @param jobName
* @param jobGroup
* 2016年10月9日下午1:51:12
*/
public void delete(String jobName, String jobGroup){
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
try {
if (checkExists(jobName, jobGroup)) {
scheduler.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);
logger.info("===> delete, triggerKey:{}", triggerKey);
}
} catch (SchedulerException e) {
throw new ServiceException(e.getMessage());
}
} /**
* 验证是否存在
* @param jobName
* @param jobGroup
* @throws SchedulerException
* 2016年10月8日下午5:30:43
*/
private boolean checkExists(String jobName, String jobGroup) throws SchedulerException{
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
return scheduler.checkExists(triggerKey);
}
}
https://github.com/leelance/spring-boot-all/tree/master/spring-boot-quartz
简单的说调度器就是:
Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息(Scheduler运行时会执行类A的excute方法),
JobDetail描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息。
Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等
Calendar:一个Trigger可以和多个Calendar关联
Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
SchedulerContext:它类似于ServletContext,保存着Scheduler上下文信息.
JobDataMap : qrtz_job_details表JOB_DATA字段存放了相关数据
在前面部分,我们知道Job中定义了实际的业务逻辑,而JobDetail包含Job相关的配置信息。在Quartz中,每次Scheduler执行Job时,在调用其execute()方法之前,它需要先根据JobDetail提供的Job类型创建一个Job class的实例,在任务执行完以后,Job class的实例会被丢弃,Jvm的垃圾回收器会将它们回收。
因此编写Job的具体实现时,需要注意:(1) 它必须具有一个无参数的构造函数;(2) 它不应该有静态数据类型,因为每次Job执行完以后便被回收,因此在多次执行时静态数据没法被维护。
Keep moving,在JobDetail中有这么一个成员JobDataMap,JobDataMap是Java Map接口的具体实现,并添加了一些便利的方法用于存储与读取原生类型数据,里面包含了当Job实例运行时,你希望提供给它的所有数据对象。
可以借助JobDataMap为Job实例提供属性/配置,可以通过它来追踪Job的执行状态等等。对于第一种情况,可以在创建Job时,添加JobDataMap数据,在Job的execute()中获取数据,第二种,则可以在Listener中通过获取JobDataMap中存储的状态数据追踪Job的执行状态。
按例,一个简单的例子:
- // 创建Job的实例
- JobDetail jobIns = JobBuilder.newJob(SimpleJob.class).withIdentity(
- "simpleJob", "group1").usingJobData("domain",
- "www.jmatrix.org").usingJobData("rank", "求别提~~~").build();
Job实现:
- public void execute(JobExecutionContext context)
- throws JobExecutionException {
- System.out.println("开始!");
- //……JobDataMap
- JobDataMap dataMap = context.getJobDetail().getJobDataMap();
- System.out.println("域名 : "+dataMap.getString("domain"));
- System.out.println("排名 : "+dataMap.getString("rank"));
- System.out.println("结束!");
- }
完成了这些工作,还需决定如何存储Job的数据,Quartz提供了JobStore接口来做这件事,如果你决定将Job数据保存在内存中,则可以使用RAMJobStore,它的优点是速度快,缺点是一旦机器挂了,Job相关的数据也丢失了,
如果要采用数据库来存储Job数据,可以使用JobStoreTX或JobStoreCMT,这取决于你采用的事务管理方式,使用RAMJobStore的话配置很简单,只需配置org.quartz.jobStore.class即可,
如果使用数据库存储,则还需要配置
driverDelegate,
tablePrefix及
dataSource,
driverDelegate
一般情况下使用StdJDBCDelegate(MySQL便可使用这个),
特殊的可以使用Quartz提供的相关delegate,请查看jar包,一般命名就说明了一切。
TablePrefix是你的数据库表前缀,创建数据库的sql文件可以在docs\dbTables目录下找到。
最后的数据源dataSource就有点麻烦,Quartz为用户提供了三种创建dataSource的方式:
- 配置相关的数据库属性(driverClass,url,username,password等),让Quartz为你创建dataSource。
- 通过jndi使用你应用服务器管理的dataSource。
- 通过实现org.quartz.utils.ConnectionProvider定制自己的datasource。
前面两种都是依据datasource的名称为其配置相关的属性,具体有哪些属性可直接参考Quartz的文档。
quartz.properties 配置:
# Main Quartz configuration
#是否跳过联网检查更新
#默认会联网检查是否有更新
org.quartz.scheduler.skipUpdateCheck = true
#调度器的实例名
#可以是你喜欢的任何字符串。它用来在用到多个调度器区分特定的调度器实例
org.quartz.scheduler.instanceName = DatabaseScheduler
#调度器的实例ID
#也允许任何字符串。这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中
#如果 Quartz 框架是运行在非集群环境中,那么自动产生的值将会是 NON_CLUSTERED
#假如是在集群环境下使用 Quartz,这个值将会是主机名加上当前的日期和时间。大多情况下,设置为 AUTO 即可
org.quartz.scheduler.instanceId = NON_CLUSTERED
#作业存储方式
#数据库存储
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = quartzDataSource
#org.quartz.dataSource.quartzDataSource.connectionProvider.class = cn.com.quartz.QuartzDataSource
#调度器数据库表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
#线程管理类
#Quartz 自带的线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#处理的线程个数
org.quartz.threadPool.threadCount = 10
#这是项目启动自动到数据库加载调度任务的设置,但是我没加一样可以自动初始化,设置false无效
# org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
# 作业存储数据库配置: JDBC jobStoreTX
#org.quartz.dataSource.quartzDataSource.driver = com.microsoft.sqlserver.jdbc.SQLServerDriver
#org.quartz.dataSource.quartzDataSource.URL = jdbc:sqlserver://192.168.1.69:1433;database=DGWEB
#org.quartz.dataSource.quartzDataSource.user = sa
#org.quartz.dataSource.quartzDataSource.password = matech
#org.quartz.dataSource.quartzDataSource.driver = oracle.jdbc.driver.OracleDriver
#org.quartz.dataSource.quartzDataSource.URL = jdbc:oracle:thin:@127.0.0.1:1521:MATECH
#org.quartz.dataSource.quartzDataSource.user = matech
#org.quartz.dataSource.quartzDataSource.password = matech
#org.quartz.dataSource.quartzDataSource.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.quartzDataSource.URL = jdbc:mysql://127.0.0.1:3306/asdb
#org.quartz.dataSource.quartzDataSource.user = root
#org.quartz.dataSource.quartzDataSource.password = 123
org.quartz.dataSource.quartzDataSource.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.quartzDataSource.URL = jdbc:mysql://183.60.183.47:8098/asdb
org.quartz.dataSource.quartzDataSource.user = xoops_root
org.quartz.dataSource.quartzDataSource.password = 654321
# 最大的数据库链接数:推荐 threadCount size + 3
#org.quartz.dataSource.quartzDataSource.maxConnections = 8
#在超过它的下次触发时多少毫秒才算是错过触发
org.quartz.jobStore.misfireThreshold = 60000
#同一时刻能处理错过触发 Trigger 的最大数量
org.quartz.jobStore.maxMisfiresToHandleAtATime = 10
http://blog.csdn.net/fupengyao/article/details/51645897
由于项目使用spring-boot框架,其框架是为了实现零配置文件去做开发的理念,所以在项目中集成Quartz任务调度并不像spring那样直接配置XML.
首先项目需要用到的jar包:
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
- <version>4.1.6.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.quartz-scheduler</groupId>
- <artifactId>quartz</artifactId>
- <version>2.2.1</version>
- </dependency>
交给spring管理的bean,代码如下
- package com.xxx;
- import java.io.IOException;
- import org.mybatis.spring.annotation.MapperScan;
- import org.quartz.JobDetail;
- import org.quartz.Trigger;
- import org.quartz.spi.JobFactory;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.ComponentScan.Filter;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.FilterType;
- import org.springframework.scheduling.annotation.EnableScheduling;
- import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
- import org.springframework.scheduling.quartz.JobDetailFactoryBean;
- import org.springframework.scheduling.quartz.SchedulerFactoryBean;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.web.WebAppConfiguration;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
- import com.xxx.base.BaseWebAppConfig;
- import com.xxx.cars.quartz.AutowiringSpringBeanJobFactory;
- import com.xxx.cars.quartz.SampleJob;
- @Configuration
- @EnableScheduling
- @ContextConfiguration
- @WebAppConfiguration
- @ComponentScan(basePackages = { "com.xxx" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) })
- @MapperScan("com.xxx.cars.persistence")
- @EnableTransactionManagement
- @EnableAutoConfiguration
- public class WebAppConfig extends BaseWebAppConfig {
- /**
- * 配置拦截器
- *
- * @author jodie
- * @param registry
- */
- // public void addInterceptors(InterceptorRegistry registry) {
- // registry.addInterceptor(new UserSecurityInterceptor()).addPathPatterns(
- // "/**");
- // }
- @Bean
- public JobFactory jobFactory(ApplicationContext applicationContext) {
- AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
- jobFactory.setApplicationContext(applicationContext);
- return jobFactory;
- }
- /**调度工厂bean
- * @param jobFactory
- * @param sampleJobTrigger
- * @return
- * @author LDX
- * @throws IOException
- */
- @Bean
- public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,
- @Qualifier("cronJobTrigger") Trigger cronJobTrigger) throws IOException {
- SchedulerFactoryBean factory = new SchedulerFactoryBean();
- // this allows to update triggers in DB when updating settings in config file:
- //用于quartz集群,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
- factory.setOverwriteExistingJobs(true);
- //用于quartz集群,加载quartz数据源
- // factory.setDataSource(dataSource);
- factory.setJobFactory(jobFactory);
- //QuartzScheduler 延时启动,应用启动完20秒后 QuartzScheduler 再启动
- factory.setStartupDelay(20);
- //用于quartz集群,加载quartz数据源配置
- // factory.setQuartzProperties(quartzProperties());
- //注册触发器
- factory.setTriggers(cronJobTrigger);
- return factory;
- }
- /**加载quartz数据源配置,quartz集群时用到
- * @return
- * @author LDX
- * @throws IOException
- */
- // @Bean
- // public Properties quartzProperties() throws IOException {
- // PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
- // propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
- // propertiesFactoryBean.afterPropertiesSet();
- // return propertiesFactoryBean.getObject();
- // }
- /**加载触发器
- * @author LDX
- * @return
- */
- @Bean
- public JobDetailFactoryBean sampleJobDetail() {
- return createJobDetail(ApplicationJob.class);
- }
- /**加载定时器
- * @param jobDetail
- * @param frequency
- * @author LDX
- * @return
- */
- @Bean(name = "cronJobTrigger")
- public CronTriggerFactoryBean sampleJobTrigger(@Qualifier("sampleJobDetail") JobDetail jobDetail,
- @Value("${samplejob.frequency}") long frequency) {
- return createTrigger(jobDetail, frequency);
- }
- /**创建触发器工厂
- * @param jobClass
- * @author LDX
- * @return
- */
- private static JobDetailFactoryBean createJobDetail(Class jobClass) {
- JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
- factoryBean.setJobClass(jobClass);
- factoryBean.setDurability(true);
- return factoryBean;
- }
- /**创建一个以频率为触发节点,以毫秒为单位,可以指定每隔x秒执行任务
- * @param jobDetail
- * @param pollFrequencyMs
- * @author LDX
- * @return
- private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs) {
- SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
- factoryBean.setJobDetail(jobDetail);
- factoryBean.setStartDelay(0L);
- factoryBean.setRepeatInterval(pollFrequencyMs);
- factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
- // in case of misfire, ignore all missed triggers and continue :
- factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
- return factoryBean;
- }*/
- /**创建定时器工厂
- * @param jobDetail
- * @param pollFrequencyMs
- * @author LDX
- * @return
- */
- private static CronTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs) {
- CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
- factoryBean.setJobDetail(jobDetail);
- factoryBean.setStartDelay(0L);
- factoryBean.setCronExpression ("0/5 * * * * ? ");//每5秒执行一次
- return factoryBean;
- }
- package com.xxx.cars.quartz;
- import org.quartz.spi.TriggerFiredBundle;
- import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
- import org.springframework.scheduling.quartz.SpringBeanJobFactory;
- public class AutowiringSpringBeanJobFactory 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;
- }
- }
任务调度触发器类
- package com.xxx.cars.quartz;
- import javax.annotation.Resource;
- import org.quartz.Job;
- import org.quartz.JobExecutionContext;
- import org.quartz.JobExecutionException;
- import com.cbkj.sz.cars.entity.ApplicationInfo;
- import com.cbkj.sz.cars.service.ApplicationInfoService;
- import org.springframework.scheduling.quartz.QuartzJobBean;
- /**
- * @author LDX
- *
- */
- public class ApplicationJob implements Job{
- @Resource
- private ApplicationInfoService<ApplicationInfo> applicationInfoService;
- @Override
- public void execute(JobExecutionContext arg0) throws JobExecutionException {
- try {
- applicationInfoService.quartz_text();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- <pre name="code" class="java">@Value("${samplejob.frequency}")
这个配置系统配置文件中,本项目使用的是yml配置文件,示例如下
运行项目,任务调度完美运行......
http://blog.csdn.net/u010623907/article/details/46684515
这篇文章参照了以下三篇文章:
http://www.itnose.net/detail/6149670.html
http://blog.csdn.net/u010623907/article/details/46684515
http://lavasoft.blog.51cto.com/62575/181907/
首先明白Quartz核心概念会变得很容易理解配置.
以下是主要的配置类
- @Configuration
- public class SchedledConfiguration {
- @Bean(name = "detailFactoryBean")
- public MethodInvokingJobDetailFactoryBean detailFactoryBean(ScheduledTasks scheduledTasks){
- MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean ();
- //这儿设置对应的Job对象
- bean.setTargetObject (scheduledTasks);
- //这儿设置对应的方法名 与执行具体任务调度类中的方法名对应
- bean.setTargetMethod ("work");
- bean.setConcurrent (false);
- return bean;
- }
- @Bean(name = "cronTriggerBean")
- public CronTriggerFactoryBean cronTriggerBean(MethodInvokingJobDetailFactoryBean detailFactoryBean){
- CronTriggerFactoryBean trigger = new CronTriggerFactoryBean ();
- trigger.setJobDetail (detailFactoryBean.getObject ());
- try {
- trigger.setCronExpression ("0/5 * * ? * *");//每5秒执行一次
- } catch (ParseException e) {
- e.printStackTrace ();
- }
- return trigger;
- }
- @Bean
- public SchedulerFactoryBean schedulerFactory(CronTriggerFactoryBean cronTriggerBean){
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean ();
- schedulerFactory.setTriggers(cronTriggerBean.getObject());
- return schedulerFactory;
- }
- }
以下是具体执行调度任务的类
- @Component
- @Configurable
- @EnableScheduling
- public class ScheduledTasks {
- public void work(){
- //这儿插入具体的调度任务
- }
- }
http://blog.csdn.net/u012432826/article/details/50827260
在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的。
spring支持多种定时任务的实现。我们来介绍下使用spring的定时器和使用quartz定时器
1.我们使用spring-boot作为基础框架,其理念为零配置文件,所有的配置都是基于注解和暴露bean的方式。
2.使用spring的定时器:
spring自带支持定时器的任务实现。其可通过简单配置来使用到简单的定时任务。
@Component
@Configurable
@EnableScheduling
public class ScheduledTasks{ @Scheduled(fixedRate = 1000 * 30)
public void reportCurrentTime(){
System.out.println ("Scheduling Tasks Examples: The time is now " + dateFormat ().format (new Date ()));
} //每1分钟执行一次
@Scheduled(cron = "0 */1 * * * * ")
public void reportCurrentByCron(){
System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
} private SimpleDateFormat dateFormat(){
return new SimpleDateFormat ("HH:mm:ss");
} }
没了,没错,使用spring的定时任务就这么简单,其中有几个比较重要的注解:
@EnableScheduling:标注启动定时任务。
@Scheduled(fixedRate = 1000 * 30) 定义某个定时任务。
3.使用quartz实现定时任务。
Quartz设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。
CronTrigger是基于Calendar-like调度的。当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。
使用quartz说使用的maven依赖。
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>1.8.4</version>
</dependency>
由于我们使用的是spring-boot框架,其目的是做到零配置文件,所以我们不使用xml文件的配置文件来定义一个定时器,而是使用向spring容器暴露bean的方式。
向spring容器暴露所必须的bean
@Configuration
public class SchedledConfiguration { // 配置中设定了
// ① targetMethod: 指定需要定时执行scheduleInfoAction中的simpleJobTest()方法
// ② concurrent:对于相同的JobDetail,当指定多个Trigger时, 很可能第一个job完成之前,
// 第二个job就开始了。指定concurrent设为false,多个job不会并发运行,第二个job将不会在第一个job完成之前开始。
// ③ cronExpression:0/10 * * * * ?表示每10秒执行一次,具体可参考附表。
// ④ triggers:通过再添加其他的ref元素可在list中放置多个触发器。 scheduleInfoAction中的simpleJobTest()方法
@Bean(name = "detailFactoryBean")
public MethodInvokingJobDetailFactoryBean detailFactoryBean(ScheduledTasks scheduledTasks){
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean ();
bean.setTargetObject (scheduledTasks);
bean.setTargetMethod ("reportCurrentByCron");
bean.setConcurrent (false);
return bean;
} @Bean(name = "cronTriggerBean")
public CronTriggerBean cronTriggerBean(MethodInvokingJobDetailFactoryBean detailFactoryBean){
CronTriggerBean tigger = new CronTriggerBean ();
tigger.setJobDetail (detailFactoryBean.getObject ());
try {
tigger.setCronExpression ("0/5 * * * * ? ");//每5秒执行一次
} catch (ParseException e) {
e.printStackTrace ();
}
return tigger; } @Bean
public SchedulerFactoryBean schedulerFactory(CronTriggerBean[] cronTriggerBean){
SchedulerFactoryBean bean = new SchedulerFactoryBean ();
System.err.println (cronTriggerBean[0]);
bean.setTriggers (cronTriggerBean);
return bean;
}
}
MethodInvokingJobDetailFactoryBean:此工厂主要用来制作一个jobDetail,即制作一个任务。由于我们所做的定时任务根本上讲其实就是执行一个方法。所以用这个工厂比较方便。
注意:其setTargetObject所设置的是一个对象而不是一个类。
CronTriggerBean:定义一个触发器。
注意:setCronExpression:是一个表达式,如果此表达式不合规范,即会抛出异常。
SchedulerFactoryBean:主要的管理的工厂,这是最主要的一个bean。quartz通过这个工厂来进行对各触发器的管理。
4.对quartz的封装
由上面代码可以看出来,此处我们设置的是一个固定的cronExpression,那么,做为项目中使用的话,我们一般是需要其动态设置比如从数据库中取出来。
其实做法也很简单,我们只需要定义一个Trigger来继承CronTriggerBean。顶用其setCronExpression方法即可。
那么另外一个问题,如果我们要定义两个定时任务则会比较麻烦,需要先注入一个任务工厂,在注入一个触发器。
为了减少这样的配置,我们定义了一个抽象的超类来继承CronTriggerBean。
具体代码如下:
public abstract class BaseCronTrigger extends CronTriggerBean implements Serializable { private static final long serialVersionUID = 1L; public void init(){
// 得到任务
JobDetail jobdetail = new JobDetail (this.getClass ().getSimpleName (),this.getMyTargetObject ().getClass ());
this.setJobDetail (jobdetail);
this.setJobName (jobdetail.getName ());
this.setName (this.getClass ().getSimpleName ());
try {
this.setCronExpression (this.getMyCronExpression ());
} catch (java.text.ParseException e) {
e.printStackTrace ();
} } public abstract String getMyCronExpression(); public abstract Job getMyTargetObject(); }
其init()方法,来为这个触发器绑定任务。其任务为一个Job类型的,也就是说其执行的任务为实现了Job接口的类,这个任务会有一个execute()方法,来执行任务题。
public class ScheduledTasks implements Job { @Override
public void execute(JobExecutionContext context) throws JobExecutionException{
System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
} private SimpleDateFormat dateFormat(){
return new SimpleDateFormat ("HH:mm:ss");
}
}
为了给触发器添加任务,我们需要在子类中调用init()方法,由于spring容器注入时是使用的空参的构造函数,所以我们在此构造函数中调用init()方法。
@Component
public class InitializingCronTrigger extends BaseCronTrigger implements Serializable { private static final long serialVersionUID = 1L; @Autowired
private SchedulerFactoryBean schedulerFactoryBean; public InitializingCronTrigger() {
init ();
} @Override
public String getMyCronExpression(){
return "0/5 * * * * ?";
} @Override
public Job getMyTargetObject(){
return new ScheduledTasks ();
} public void parse(){
try {
schedulerFactoryBean.getObject ().pauseAll ();
} catch (SchedulerException e) {
e.printStackTrace ();
}
} }
此时我们只需要在配置类中加入一个配置就可以了。
@Bean
public SchedulerFactoryBean schedulerFactory(CronTriggerBean[] cronTriggerBean){
SchedulerFactoryBean bean = new SchedulerFactoryBean ();
System.err.println (cronTriggerBean[0]);
bean.setTriggers (cronTriggerBean); return bean;
}
4.介绍一个cronExpression表达式。
这一部分是摘抄的:
字段 允许值 允许的特殊字符 秒
0-59
, - * /
分
0-59
, - * /
小时
0-23
, - * /
日期
1-31
, - * / L W C
月份
1-12 或者 JAN-DEC
, - * /
星期
1-7 或者 SUN-SAT
, - * / L C #
年(可选)
留空, 1970-2099
, - * /
如上面的表达式所示:“*”字符被用来指定所有的值。如:”*“在分钟的字段域里表示“每分钟”。
“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。
“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”.
“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。
“L”字符指定在月或者星期中的某天(最后一天)。即“Last ”的缩写。但是在星期和月中“L”表示不同的意思,如:在月子段中“L”指月份的最后一天-1月31日,2月28日,如果在星期字段中则简单的表示为“7”或者“SAT”。如果在星期字段中在某个value值得后面,则表示“某月的最后一个星期value”,如“6L”表示某月的最后一个星期五。
“W”字符只能用在月份字段中,该字段指定了离指定日期最近的那个星期日。
“#”字符只能用在星期字段,该字段指定了第几个星期value在某月中
每一个元素都可以显式地规定一个值(如6),一个区间(如9-12),一个列表(如9,11,13)或一个通配符(如*)。“月份中的日期”和“星期中的日期”这两个元素是互斥的,因此应该通过设置一个问号(?)来表明你不想设置的那个字段。表7.1中显示了一些cron表达式的例子和它们的意义:
表达式
意义 "0 0 12 * * ?"
每天中午12点触发
"0 15 10 ? * *"
每天上午10:15触发
"0 15 10 * * ?"
每天上午10:15触发
"0 15 10 * * ? *"
每天上午10:15触发
"0 15 10 * * ? 2005"
2005年的每天上午10:15
触发"0 * 14 * * ?"
在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?"
在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?"
在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?"
在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED"
每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI"
周一至周五的上午10:15触发
"0 15 10 15 * ?"
每月15日上午10:15触发
"0 15 10 L * ?"
每月最后一日的上午10:15触发
"0 15 10 ? * 6L"
每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005"
2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3"
每月的第三个星期五上午10:15触发
每天早上6点 0 6 * * *
每两个小时 0 */2 * * *
晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 * * *
每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点 0 11 4 * 1-3
1月1日早上4点 0 4 1 1 *
http://www.cnblogs.com/lic309/p/4089633.html
spring-boot-quartz, 依赖spring-boot-parent的更多相关文章
- Spring.Scheduling.Quartz 作业的应用(定时任务和循环触发任务)
.定时任务的实现,比如有个任务是要晚上2点10分的时候要去触发的,先定义这个任务类RskBookFilterInitDiningService.cs,这里其实有两种实现,一种是需要继承QuartzJo ...
- 朱晔和你聊Spring系列S1E3:Spring咖啡罐里的豆子
标题中的咖啡罐指的是Spring容器,容器里装的当然就是被称作Bean的豆子.本文我们会以一个最基本的例子来熟悉Spring的容器管理和扩展点. 阅读PDF版本 为什么要让容器来管理对象? 首先我们来 ...
- spring boot+Quartz+数据库存储
SpingBoot+Quartz+数据库存储 1.Spring整合Quartz 2.读取数据库中表达式启动定时任务1(每5s执行) 3.更改定时任务状态(启用/禁用),定时任务1停止 4.读取数据库中 ...
- Spring Boot 不使用默认的 parent,改用自己的项目的 parent
在初学spring boot时,官方示例中,都是让我们继承一个spring的 spring-boot-starter-parent 这个parent: <parent> <group ...
- Spring Boot起步依赖:定制starter
在定制我们自己的起步依赖--xxx.spring.boot.starter之前,我们先了解下什么是Spring Boot起步依赖. 起步依赖,是Spring Boot带给我们的一项重要的便利.要理解S ...
- SpringBoot 之Spring Boot Starter依赖包及作用
Spring Boot 之Spring Boot Starter依赖包及作用 spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置.日志和YAML. spri ...
- Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具:Spr ...
- Spring boot quartz的相关资源
https://github.com/82253452/banner https://github.com/lvhao/schedule-job/tree/master/src/main/java/c ...
- Spring Boot Quartz 分布式集群任务调度实现
Spring Boot Quartz 主要内容 Spring Scheduler 框架 Quartz 框架,功能强大,配置灵活 Quartz 集群 mysql 持久化定时任务脚本(tables_mys ...
- Spring Boot 一个依赖搞定 session 共享,没有比这更简单的方案了!
有的人可能会觉得题目有点夸张,其实不夸张,题目没有使用任何修辞手法!认真读完本文,你就知道松哥说的是对的了! 在传统的单服务架构中,一般来说,只有一个服务器,那么不存在 Session 共享问题,但是 ...
随机推荐
- float类型进行计算精度丢失的问题
今天一个案子,用户反映数量差异明明是 2.0-1.8,显示的结果却为0.20000005,就自己写了段方法测试了一下:package test1;public class Test2 {/*** @p ...
- WPF中的Style
一.Style基础知识 构成Style最重要的两种元素是Setter和Trigger Setter类帮助我们设置控件的静态外观风格 Trigger类帮助我们设置控件的行为风格 Setter类的Prop ...
- Fat-tree 胖树交换网络
胖树架构下,网络带宽不收敛 传统的树形网络拓扑中,带宽是逐层收敛的,树根处的网络带宽要远小于各个叶子处所有带宽的总和. 而胖树网络则更像是真实的树,越到树根,枝干越粗,即:从叶子到树根,网络带宽不收敛 ...
- Java Day 05
数组第二种定义 数组-遍历 数组操作的核心思想就是对角标的操作: 数组-求最值 1.循环 比较 排序 选择排序 把原始数组分割成了两个数组,至少有一个是有序的 冒泡排序 相邻元素比较 位置置换代码提取 ...
- 微软职位内部推荐-Senior Software Development En
微软近期Open的职位: Job Title: Senior Development Engineer Division: Visual Studio China - Developer Divisi ...
- 3243: [Noi2013]向量内积 - BZOJ
Description 两个d 维向量A=[a1,a2,...,ad]与B=[b1,b2,...,bd]的内积为其相对应维度的权值的乘积和,即: 现有 n 个d 维向量x1,...,xn ,小喵喵想知 ...
- Educational Codeforces Round 11 C. Hard Process 前缀和+二分
题目链接: http://codeforces.com/contest/660/problem/C 题意: 将最多k个0变成1,使得连续的1的个数最大 题解: 二分连续的1的个数x.用前缀和判断区间[ ...
- Ext学习-高级组件介绍
在这一部分的学习中,主要是学习一些比较特殊的组件. 1.图表 2.日历 3.颜色,日期,时间的选择器 4.滑动条 5.各种工具类 参考文档:http://docs.sencha.com/extjs/4 ...
- SqlServer 系统存储过程
exec sp_databases; --查看数据库exec sp_tables; --查看表exec sp_columns Categories;--查看列exec sp_helpIndex Cat ...
- 【POJ】【2125】Destroying the Graph
网络流/二分图最小点权覆盖 果然还是应该先看下胡伯涛的论文…… orz proverbs 题意: N个点M条边的有向图,给出如下两种操作.删除点i的所有出边,代价是Ai.删除点j的所有入边,代价是Bj ...