提交 06820b54 编写于 作者: C Calvin

#77 Scheduler部分應用Spring的Task NameSpace,及對原有部分的一些註釋與整理

上级 bb72f7a3
......@@ -8,22 +8,17 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
import org.springframework.scheduling.support.TaskUtils;
import org.springside.examples.showcase.service.AccountService;
import org.springside.modules.utils.Threads;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* 被ScheduledThreadPoolExecutor定时执行的任务类, 并强化了退出控制.
* 用JDKScheduledThreadPoolExecutor定时执行的任务。
* 相比Spring的NameSpace, 不需要反射調用,并强化了退出控制.
*/
public class JdkExecutorJob implements Runnable {
private static Logger logger = LoggerFactory.getLogger(JdkExecutorJob.class);
public class JdkTimerJob implements Runnable {
private int initialDelay = 0;
......@@ -33,17 +28,19 @@ public class JdkExecutorJob implements Runnable {
private ScheduledExecutorService scheduledExecutorService;
private AccountService accountService;
@Autowired
private UserCountScanner userCountScanner;
@PostConstruct
public void start() throws Exception {
Validate.isTrue(period > 0);
//任何异常不会中断schedule执行
Runnable task = new DelegatingErrorHandlingRunnable(this, TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER);
//任何异常不会中断schedule执行, 由Spring TaskUtils的LOG_AND_SUPPRESS_ERROR_HANDLER進行处理
Runnable task = TaskUtils.decorateTaskWithErrorHandler(this, null, true);
//创建单线程的SechdulerExecutor,并用guava的ThreadFactoryBuilder设定生成线程的名称
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setNameFormat(
"JdkExecutorJob-%1$d").build());
"JdkTimerJob-%1$d").build());
//scheduleAtFixedRatefixRate() 固定任务两次启动之间的时间间隔.
//scheduleAtFixedDelay() 固定任务结束后到下一次启动间的时间间隔.
......@@ -60,8 +57,7 @@ public class JdkExecutorJob implements Runnable {
*/
@Override
public void run() {
long userCount = accountService.getUserCount();
logger.info("There are {} user in database, printed by jdk timer job.", userCount);
userCountScanner.executeByJdk();
}
/**
......@@ -79,14 +75,9 @@ public class JdkExecutorJob implements Runnable {
}
/**
* 设置gracefulShutdown的等待时间,单位秒.
* 设置normalShutdown的等待时间, 单位秒.
*/
public void setShutdownTimeout(int shutdownTimeout) {
this.shutdownTimeout = shutdownTimeout;
}
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}
package org.springside.examples.showcase.modules.schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springside.examples.showcase.service.AccountService;
/**
* 被Spring的Quartz MethodInvokingJobDetailFactoryBean定时执行的普通Spring Bean.
*/
public class QuartzJob {
private static Logger logger = LoggerFactory.getLogger(QuartzJob.class);
@Autowired
private AccountService accountService;
/**
* 定时打印当前用户数到日志.
*/
public void execute() {
long userCount = accountService.getUserCount();
logger.info("There are {} user in database, printed by quartz local job.", userCount);
}
}
......@@ -7,28 +7,25 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springside.examples.showcase.service.AccountService;
import org.springside.modules.utils.Threads;
/**
* 使用Spring的ThreadPoolTaskScheduler执行Cron式任务的类, 并强化了退出控制.
* 使用Spring的ThreadPoolTaskScheduler执行Cron式任务的类.
* 相比Spring的NameSpace, 不需要反射調用,并强化了退出控制.
*/
public class SpringCronJob implements Runnable {
private static Logger logger = LoggerFactory.getLogger(SpringCronJob.class);
private String cronExpression;
private int shutdownTimeout = Integer.MAX_VALUE;
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private AccountService accountService;
@Autowired
private UserCountScanner userCountScanner;
@PostConstruct
public void start() {
......@@ -44,9 +41,7 @@ public class SpringCronJob implements Runnable {
@PreDestroy
public void stop() {
ScheduledExecutorService scheduledExecutorService = threadPoolTaskScheduler.getScheduledExecutor();
Threads.normalShutdown(scheduledExecutorService, shutdownTimeout, TimeUnit.SECONDS);
}
/**
......@@ -54,8 +49,7 @@ public class SpringCronJob implements Runnable {
*/
@Override
public void run() {
long userCount = accountService.getUserCount();
logger.info("There are {} user in database, printed by spring cron job.", userCount);
userCountScanner.executeBySpringCronByJava();
}
public void setCronExpression(String cronExpression) {
......@@ -63,14 +57,9 @@ public class SpringCronJob implements Runnable {
}
/**
* 设置gracefulShutdown的等待时间,单位秒.
* 设置normalShutdown的等待时间,单位秒.
*/
public void setShutdownTimeout(int shutdownTimeout) {
this.shutdownTimeout = shutdownTimeout;
}
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}
package org.springside.examples.showcase.modules.schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springside.examples.showcase.service.AccountService;
/**
* 被Spring各种Scheduler反射调用的Service POJO
*
* @author Calvin
*/
@Component
public class UserCountScanner {
private static Logger logger = LoggerFactory.getLogger(UserCountScanner.class);
@Autowired
private AccountService accountService;
public void executeByJdk() {
execute("jdk timer job");
}
public void executeBySpringCronByJava() {
execute("spring cron job by java");
}
//被Spring的Quartz MethodInvokingJobDetailFactoryBean反射执行
public void executeByQuartzLocalJob() {
execute("quartz local job");
}
//被Spring的Scheduler namespace 反射构造成ScheduledMethodRunnable
public void executeBySpringCronByXml() {
execute("spring cron job by xml");
}
//被Spring的Scheduler namespace 反射构造成ScheduledMethodRunnable
public void executeBySpringTimerByXml() {
execute("spring timer job by xml");
}
/**
* 定时打印当前用户数到日志.
*/
private void execute(String by) {
long userCount = accountService.getUserCount();
logger.info("There are {} user in database, printed by {}.", userCount, by);
}
}
......@@ -11,7 +11,7 @@
<import resource="jms/applicationContext-jms-advanced.xml" />
<import resource="jmx/applicationContext-jmx.xml" />
<import resource="schedule/applicationContext-jdk-timer.xml" />
<import resource="schedule/applicationContext-spring-cron.xml" />
<import resource="schedule/applicationContext-spring-scheduler.xml" />
<import resource="schedule/applicationContext-quartz-cron-local.xml" />
<import resource="schedule/applicationContext-quartz-timer-cluster.xml" />
<import resource="security/applicationContext-shiro.xml" />
......
......@@ -6,7 +6,7 @@
<description>使用JDK ScheduledExecutorService的定时任务配置</description>
<!-- 定期执行的业务类 -->
<bean id="jdkExecutorJob" class="org.springside.examples.showcase.modules.schedule.JdkExecutorJob" lazy-init="false">
<bean id="jdkExecutorJob" class="org.springside.examples.showcase.modules.schedule.JdkTimerJob" lazy-init="false">
<!-- 每次执行间隔120秒 -->
<property name="period" value="120" />
<!-- 首次执行延期1秒 -->
......
......@@ -13,7 +13,7 @@
<ref bean="cronTrigger" />
</list>
</property>
<!-- Quartz配置 -->
<!-- Quartz配置, 本地运行无需单独的quartz.properties文件,在此简单配置即可 -->
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.threadCount">5</prop>
......@@ -30,14 +30,12 @@
<property name="cronExpression" value="0 0/30 9-17 ? * MON-FRI" />
</bean>
<!-- Cron JobDetajil, 基于MethodInvokingJobDetailFactoryBean调用普通Spring Bean -->
<!-- JobDetajil, 基于MethodInvokingJobDetailFactoryBean调用普通Spring Bean -->
<bean id="cronJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="cronJob" />
<property name="targetMethod" value="execute" />
<property name="targetObject" ref="userCountScanner" />
<property name="targetMethod" value="executeByQuartzLocalJob" />
<!-- 同一任务在前一次执行未完成而Trigger时间又到时是否并发开始新的执行, 默认为true. -->
<property name="concurrent" value="true" />
</bean>
<!-- 被Cron执行的普通Spring Bean -->
<bean id="cronJob" class="org.springside.examples.showcase.modules.schedule.QuartzJob" />
</beans>
......@@ -15,7 +15,7 @@
<ref bean="timerTrigger" />
</list>
</property>
<!-- quartz配置文件路径-->
<!-- quartz配置文件路径, 指向cluster配置 -->
<property name="configLocation" value="classpath:schedule/quartz-cluster.properties" />
<!-- 启动时延期2秒开始任务 -->
<property name="startupDelay" value="2" />
......@@ -32,12 +32,12 @@
<property name="repeatInterval" value="300000" />
</bean>
<!-- Timer JobDetail, 基于JobDetailBean实例化Job Class,可持久化到数据库实现集群 -->
<!-- JobDetail, 基于JobDetailBean实例化Job Class,可持久化到数据库实现集群 -->
<bean id="timerJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="org.springside.examples.showcase.modules.schedule.QuartzClusterableJob" />
</bean>
<!-- Timer Job的可配置属性,在job中通过applicationContext动态获取 -->
<!-- Job的可配置属性,在job中通过applicationContext动态获取 -->
<util:map id="timerJobConfig">
<entry key="nodeName" value="${server.node_name}" />
</util:map>
......
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd"
default-lazy-init="true">
<description>使用Spring的 Scheduled的定时任务配置</description>
<!-- 根据Cron表达式执行的业务类 -->
<!-- namespace 方式 的便捷版 -->
<task:scheduler id="springScheduler" pool-size="2"/>
<task:scheduled-tasks scheduler="springScheduler">
<!-- timer -->
<task:scheduled ref="userCountScanner" method="executeBySpringTimerByXml" fixed-rate="60000"/>
<!-- cron -->
<task:scheduled ref="userCountScanner" method="executeBySpringCronByXml" cron="* 15 9-17 * * MON-FRI"/>
</task:scheduled-tasks>
<!-- coding 方式的加強版 -->
<bean id="springCronJob" class="org.springside.examples.showcase.modules.schedule.SpringCronJob" lazy-init="false">
<!-- cron表达式 -->
<property name="cronExpression" value="* 15 9-17 * * MON-FRI"/>
<!-- shutdown时等待任务完成, 最多等待20秒 -->
<property name="shutdownTimeout" value="20" />
</bean>
</beans>
\ No newline at end of file
......@@ -21,5 +21,6 @@ org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.tablePrefix = QRTZ_
#Cluster setting
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 15000
\ No newline at end of file
......@@ -23,7 +23,7 @@ public class JdkTimerJobTest extends SpringTransactionalTestCase {
//加载测试用logger appender
Log4jMockAppender appender = new Log4jMockAppender();
appender.addToLogger(JdkExecutorJob.class);
appender.addToLogger(UserCountScanner.class);
//等待任务启动
Threads.sleep(3000);
......@@ -31,5 +31,7 @@ public class JdkTimerJobTest extends SpringTransactionalTestCase {
//验证任务已执行
assertEquals(1, appender.getLogsCount());
assertEquals("There are 6 user in database, printed by jdk timer job.", appender.getFirstMessage());
appender.removeFromLogger(UserCountScanner.class);
}
}
......@@ -38,5 +38,7 @@ public class QuartzTimerClusterJobTest extends SpringTransactionalTestCase {
assertEquals("There are 6 user in database, printed by quartz cluster job on node default.",
appender.getFirstMessage());
appender.removeFromLogger(QuartzClusterableJob.class);
}
}
package org.springside.examples.showcase.modules.schedule;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springside.modules.test.category.UnStable;
import org.springside.modules.test.log.Log4jMockAppender;
import org.springside.modules.test.spring.SpringTransactionalTestCase;
import org.springside.modules.utils.Threads;
@Category(UnStable.class)
@DirtiesContext
@ContextConfiguration(locations = { "/applicationContext.xml", "/schedule/applicationContext-spring-scheduler.xml" })
@TransactionConfiguration(transactionManager = "defaultTransactionManager")
public class SpringTimerJobTest extends SpringTransactionalTestCase {
@Test
public void scheduleJob() throws Exception {
//加载测试用logger appender
Log4jMockAppender appender = new Log4jMockAppender();
appender.addToLogger(UserCountScanner.class);
//等待任务启动
Threads.sleep(1000);
//验证任务已执行
assertEquals(1, appender.getLogsCount());
assertEquals("There are 6 user in database, printed by spring timer job by xml.", appender.getFirstMessage());
appender.removeFromLogger(UserCountScanner.class);
}
}
......@@ -199,7 +199,7 @@ public class Reflections {
}
/**
// * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
......
......@@ -41,6 +41,7 @@ public class Threads {
* 按照ExecutorService JavaDoc示例代码编写的Graceful Shutdown方法.
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍人超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void gracefulShutdown(ExecutorService pool, int shutdownTimeout, int shutdownNowTimeout,
......@@ -64,7 +65,7 @@ public class Threads {
}
/**
* 直接调用shutdownNow的方法, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 直接调用shutdownNow的方法, 有timeout控制.取消在workQueue中Pending的任务,并中断所有阻塞函数.
*/
public static void normalShutdown(ExecutorService pool, int timeout, TimeUnit timeUnit) {
try {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册