定时任务主要有两种定时,一种是指定某个时间点运行,比如某日的下午3点,另一种是固定周期的运行,比如间隔10秒去统计数据或查看订单。我们这里演示第二种周期任务。
有现成的各种工具类,包括java自带或者第三方工具,这些工具有个特点是你必须按它要求的任务模型编写任务或定制任务,不能对任意一个模块执行周期任务,这样就需要定制专门任务。我们看看如何通过消息对象编程而对任意模块、类进行定时任务,例如有个统计访问量类A,它过去是通过客户每次需求而触发,现在要周期运行(如改为5分钟统计一次)而不做任何修改。
现在我们假设一个对象--宅男PcBoy,它有三个行为——吃饭、睡觉、玩。代码:
public class PcBoy extends TLBaseModule {
public PcBoy(String name, TLObjectFactory moduleFactory) {
super(name,moduleFactory);
}
@Override
protected void init() {
}
@Override
protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
TLMsg returnMsg = null;
switch (msg.getAction()) {
case "sleep":
sleep(fromWho, msg);
break;
case "eat":
eat(fromWho, msg);
break;
case "play":
play(fromWho, msg);
break;
default:
}
return returnMsg;
}
private void play(Object fromWho, TLMsg msg) {
say("我在玩");
}
private void eat(Object fromWho, TLMsg msg) {
say("我在吃饭,好好吃");
}
private void sleep(Object fromWho, TLMsg msg) {
say("我在睡觉。。。");
}
private void say(String content)
{
Date data = new Date();
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("时间:" + ft.format(data) +" "+content);
}
}
这是一个普通的类,也就是没有为任务而特意定制。我们要对这个宅男boy周期执行任务,间隔3秒吃饭,间隔5秒玩、间隔7秒睡觉。
我们利用基本包里的简单任务模块TLMsgTask来实现任务,定义它的配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<moduleConfig>
<taskMsgTable>
<msg destination="pcboy" action="eat" delay="3"></msg>
<msg destination="pcboy" action="play" delay="5"></msg>
<msg destination="pcboy" action="sleep" delay="7"></msg>
</taskMsgTable>
</moduleConfig>
配置文件定义消息任务表taskMsgTable,项目很容易理解,对于poboy模块的eat方法,间隔3秒运行,其他如是。消息任务表里体现了我们是对消息执行任务,而不是模块,所以说我们可以对任意模块执行任务,因为我们执行的是消息。
现在编制一个程序入口类:
public class Main {
public static void main(String[] args)
{
String configdir = System.getProperty("user.dir") + "\\config\\person\\";
TLObjectFactory myfactory = TLObjectFactory.getInstance(configdir,"moduleFactory_config.xml");
myfactory.boot();
TLMsg taksMsg =new TLMsg().setAction("getModule").setParam("moduleName","myTaskManger");
myfactory.putMsg(myfactory,taksMsg);
}
}
入口类首先创立模块工厂 myfactory,然后模块工厂通过getModule消息创立了名字为myTaskManger的模块,这个名字是我们给TLMsgTask模块的一个别名,我们摘录本例在工厂配置里面定义:
1、定义msgTask模块:
<module name="msgTask" classfile="cn.tianlong.tlobjcet.base.TLMsgTask" />
2、定义了我们给的别名myTaskManger ,它用来指向msgTask模块,也就是TLMsgTask。这样方便同一个类实现不同的名字和配置
<module name="myTaskManger" proxyModule="msgTask" configfile="msgtask_config.xml" />
3、定义PcBoy模块;
<module name="pcboy" classfile=".task.PcBoy" />
运行程序入口类main,myTaskManger模块通过工厂创立后自动开始执行定期任务:
现在我们看到通过消息配置,轻松实现了一个非任务模块的周期任务,不用刻意编制任务模型。增加、修改任务仅仅通过配置而改变。
我们来看LMsgTask类的代码:
public class TLMsgTask extends TLBaseModule {
protected ScheduledExecutorService executor ;
protected ArrayList<TLMsg> taskMsgTable;
protected int poolSize=5;
public TLMsgTask(){
super();
}
public TLMsgTask(String name ){
super(name);
}
public TLMsgTask(String name , TLObjectFactory modulefactory){
super(name,modulefactory);
}
@Override
protected void init() {
if(taskMsgTable==null || taskMsgTable.isEmpty())
return;
if(executor ==null)
executor = Executors.newScheduledThreadPool( poolSize);
for (TLMsg msg : taskMsgTable)
{
TLMsg bmsg=createMsg();
bmsg.copyFrom(msg);
executeTask( bmsg);
}
}
@Override
protected Object setConfig(){
myConfig config=new myConfig();
mconfig=config;
super.setConfig();
taskMsgTable=config.getTaskMsgTable();
return config ;
}
@Override
protected void initProperty() {
super.initProperty();
if(params!=null ){
if( params.get("poolSize")!=null)
{
poolSize= Integer.parseInt(params.get("poolSize"));
};
}
}
private void unRegistTask(Object fromWho, TLMsg msg) {
for (TLMsg tmsg : taskMsgTable){
if(tmsg.getMsgId().equals((String)msg.getParam("msgId")))
taskMsgTable.remove(tmsg);
}
}
@Override
protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
TLMsg returnMsg=null;
switch (msg.getAction()) {
case "registTask":
registTask( fromWho, msg);
break;
case "unRegistTask":
unRegistTask( fromWho, msg);
break;
case "startTask":
if(executor ==null)
executor = Executors.newScheduledThreadPool(poolSize);
runInitMsg();
break;
case "shutdown":
executor.shutdown();
break;
default:
}
return returnMsg;
}
private void registTask(Object fromWho, TLMsg msg) {
TLMsg tmsg= (TLMsg) msg.getParam("msg");
if(taskMsgTable==null)
taskMsgTable= new ArrayList<>();
taskMsgTable.add(tmsg);
if(msg.getParam("run")!=null && (Boolean)msg.getParam("run")==true)
executeTask( tmsg);
}
private void executeTask(TLMsg msg){
Runnable task =newTask(this,msg);
Long begin = Long.valueOf(0);
String sbegin = (String) msg.getParam("begin");
if(sbegin !=null)
begin=Long.parseLong(sbegin);
String sdelay = (String) msg.getParam("delay");
Long delay =Long.parseLong(sdelay);
executor.scheduleAtFixedRate(task, begin, delay, TimeUnit.SECONDS);
}
private Runnable newTask(final TLMsgTask object, final TLMsg msg){
Runnable task = new Runnable() {
@Override
public void run() {
object.getMsg(object,msg);
}
};
return task ;
}
protected class myConfig extends TLModuleConfig {
protected ArrayList<TLMsg> taskMsgTable;
public myConfig() {
}
public ArrayList<TLMsg> getTaskMsgTable() {
return taskMsgTable;
}
protected void myConfig(XmlPullParser xpp) {
super.myConfig(xpp);
try {
if (xpp.getName().equals("taskMsgTable")) {
taskMsgTable=getMsgList(xpp,"taskMsgTable");
}
} catch (Throwable t) {
}
}
}
}
代码重点是根据消息建task并执行,由于执行消息方便所以用较少的代码实现了模块的定时任务。在前面文章介绍过如何包装任意传统类为消息对象类,因此通过包装就可以用于任意类了。
既然任务是执行消息,那么我们就可以灵活的定制消息任务。上面是三个行为独立运行,互不干涉、没有逻辑关系,现在我们希望pcboy的吃、玩、睡顺序运行,修改task配置:
<moduleConfig>
<msgTable>
<msgid value="pcboy" >
<msg destination="pcboy" action="eat" ></msg>
<msg destination="pcboy" action="play" ></msg>
<msg destination="pcboy" action="sleep" ></msg>
</msgid>
</msgTable>
<taskMsgTable>
<msg msgid="pcboy" delay="3" ></msg>
</taskMsgTable>
</moduleConfig>
看任务消息表,现在只有一个消息任务,消息id为pcboy,在消息表msgTable中,pcboy消息包含三个顺序消息,依次为eat,play、sleep,那么执行消息pcboy则顺序执行这些消息。现在看运行:
现在吃饭、玩、睡觉变成固定顺序周期运行了。
由此看来,我们的任务模块只要给它传递任务消息即可,而不受任何模块或方法的限制。没有更改代码,仅通过更改消息,我们改变了任务的运行方式。