用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.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;@Overridepublic 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 blocke1.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我还没测试过,如果细节上有错,还请多多包含。深知自己写的文章水平还很一般,欢迎高手们指出错误。
?