用Listener+Timer实现自动周期更新数据库

    前段时间在做一个维护升级一个基于SSI的网站项目,叫做《网络课堂系统》,是广州市一所专科学院在使用的。这个项目是我所在的学院开发工作室两三年前接回来的项目,所以也经过了两三个年级的师兄的手最终传到我们手上。可想而知,这个项目的原代码已经非常凌乱了。原来打算直接重构整个项目的,但由于这是个正在使用中的项目,而我们又人手有限,那所学院的负责人Y要求我们先进行维护升级以满足使用需要后再考虑重构的事情。

    这个项目的数据库有一张学期表,用来记录学期信息,并用一个字段指定哪个是当前学期。但是这张学期表的数据更新居然是靠手动修改数据库实现的,对于用户来说,这非常的不人性化,因此我得到了一个任务:实现一个操作简单的功能以代替这种非常不人性化的操作。

    由于开发经验还不够等原因,我一开始是从管理员的方向去想。首先,我想在管理员页面添加一个按钮,当管理员点击这个按钮的时候进行检查更新数据库学期表。但是Y和我说管理员可能忘记点击,所以这个想法作罢。接着,我想写一个filter用来过滤登录信息,如果发现是管理员登录的话就检查更新数据库学期表。可是,这个想法还是没有实现,原因有二:一是Y说管理员不一定会在开学前登录一次,这可能会导致学期表没及时更新;二是我那时我发现无法用Spring对filter进行注入,实现起来要多走很多路。最后,我打算写一个Listener实现自动周期更新学期表。

 

    后来经过测试,发现Spring也无法通过一般途径对Listener进行注入,查了相关资料发现这是因为filter、listener、servlet不由Spring管理。虽然麻烦,但我也决定做下去了。

当然,在实现的过程中,我也有不断的google查资料,并不是完全靠自己想出来的。

 

    先来简单的介绍一下listener:listener是servlet监听器,它可以监听客户端的请求、服务端的操作等。要创建一个listener就必须实现相关的接口,这类接口有四个:ServletContextAttributeListener、ServletContextListener、HttpSessionAttributeListener、HttpSessionListener。

 

ServletContestener监听对ServletContext属性的操作,比如增删查改等。(ServletContext是servlet上下文的意思,整个web项目共享同一个ServletContext)

 

ServletContextListener用来监听ServletContext,而并非监听对ServletContext属性的操作。它有两个方法:当创建ServletContext时,调用contextInitialized (ServletContextEvent sce)方法;当销毁ServletContext时,调用contextDestroyed(ServletContextEvent sce)方法。

 

HttpSessionAttributeListener用来监听HttpSession中的属性的操作。它有三个方法:当在Session增加一个属性时,调用attributeAdded (HttpSessionBindingEvent se) 方法;当在Session删除一个属性时,调用attributeRemove(HttpSessionBindingEvent se)方法;当在Session属性被重新设置时,调用attributeReplaced(HttpSessionBindingEvent se) 方法。

 

 

HttpSessionListener 监听HttpSession的操作。有两个方法:当创建一个Session时,调用session Created(HttpSessionEvent se)方法;当销毁一个Session时,调用sessionDestroyed (HttpSessionEvent se)方法。

 

    要实现我想实现的那个功能的话,就要写一个实现了ServletContextListener的Listener。

package com.course.online.listener;

import java.util.Timer;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.course.online.util.UpdateTerm;

public class TermListener implements ServletContextListener{
	
	private Timer timer = null;
		
	public void contextDestroyed(ServletContextEvent scEvent) {
		// TODO Auto-generated method stub
		timer.cancel();
		System.out.println("停止学期更新!");
	}

	
	public void contextInitialized(ServletContextEvent scEvent) {
		// TODO Auto-generated method stub
		timer = new Timer(true);
		/*24*60*60*1000*/
		timer.schedule(new UpdateTerm(), 5000, 24*60*60*1000);
		System.out.println("开始监听学期更新!");
	}

}

 

    先看一上面这篇Listener的代码,里面用到了一个Timer对象。这个Timer对象的作用是实现每相隔一段时间就执行某个操作一次。

 

    当开始运行项目,ServletContext被创建时服务器调用contextInitialized(ServletContextEvent scEvent) 方法,生成一个Timer对象,并调用该对象的schedule(new UpdateTerm(), 5000, 24*60*60*1000)方法,其中参数“new UpdateTerm()”代表要定时执行的操作,“5000”代表第一次执行的时间为第5秒,“24*60*60*1000”代表执行周期时间为一天。

 

    当项目结束运行,ServletContext被销毁的时候,服务器调用contextDestroyed(ServletContextEvent scEvent) 方法,该方法调用Timer对象的cancl()方法销毁Timer对象。

 

   接着看一下UpdateTerm.java的代码,UpdateTerm.java是用来执行数据库更新的关键操作的:

package com.course.online.util;

import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;

import com.course.online.model.Term;
import com.ibatis.sqlmap.client.SqlMapClient;

public class UpdateTerm extends TimerTask{

	
	
	private SqlMapClient sqlMapClient = null;
	


	@Override
	public void run(){
		// TODO Auto-generated method stub
		……		
		try {
			Reader reader = 
				com.ibatis.common.resources.Resources.getResourceAsReader(
						"com/course/online/listener/mapping/SqlMapConfig.xml");
			sqlMapClient = 
				com.ibatis.sqlmap.client.SqlMapClientBuilder.buildSqlMapClient(reader);
			reader.close();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		……	}

}

     要把UpdateTerm对象传给Timer对象的schedule()方法的话,UpdateTerm对象必须继承TimerTask并重写其run()方法。因为schedule()方法执行后会自动调用run()。我把所有的相关操作都写在run()方法里面了,因为不用考虑代码重用的问题,那些代码完全只是为了这个listener而写的。其中大部分已经省略掉只留下其中一小段。

    前面有说到因为filter、listener、servlet都不由Spring管理,所以Spring无法通过一般途径对listener进行注入。要解决这个问题,我到目前为止暂时懂得两种方法,暂时称为方法A和方法B。其中A被我用在了项目升级上面,但是这种方法相对笨和麻烦很多。

 

    方法A的做法是:完全跳出Spring框架,单独为这个Listener写一个ibatis的配置文件,然后在UpdateTerm类里面的run()方法里以Reader reader =
com.ibatis.common.resources.Resources.getResourceAsReader(
"com/course/online/listener/mapping/SqlMapConfig.xml");
sqlMapClient =
com.ibatis.sqlmap.client.SqlMapClientBuilder.buildSqlMapClient(reader);
  的方式去创建一个SqlMapClient对象来进行数据库操作。

    方法B的做法和方法A类似,但是简便很多:可以在run()方法里面写入:        

                   ApplicationContext   ctx   =   WebApplicationContextUtils.getWebApplicationContext(servletContext);

                   TermManager   tm   =   (TermsManager)ctx.getBean( "TermManager");

来实现TermManager的注入(TermManager包含各种对学期表的操作,这样就省去了原来很多写在UpdateTerm里面的方法)。虽然这只是对UpdateTerm进行注入而非对Listener,但这个方法对listener也同样适用的。其中参数servletContext这个参数可以由listener里面的ServletContextEvent对象的getServletContext()方法得到,然后当做参数传给UpdateTerm的构造方法就可以了。

    方法B我还没测试过,如果细节上有错,还请多多包含。深知自己写的文章水平还很一般,欢迎高手们指出错误。

 

你可能感兴趣的:(jsp,timer,SSI,listener)