原创文章,转载请注明出处
从开年到现在两个月时间,一直想慢慢开始写点东西,将工作以来学到的知识和技能进行总结。但是偏偏年后项目比较急,一个集中刻录项目就写了一个月左右,但是得到的收获也不少。好不容易清明有了一点的空闲,就将之前的一些笔记和开发中遇到的问题好好的总结一下吧。
万事开头难,能提笔开始写这篇文章,相信以后能养成定时总结的习惯,也希望能和大家进行探讨,有错误和疏漏的地方还请不吝赐教。
废话也就不多说了,今天主要总结一下 JavaWeb 开发中最基本的三大类:Listener、Filter与 Servlet中的 Listener。对于 JavaWeb 程序来讲,这三个类是维持其基本运作的基石,在现代开发中,各种框架(最常见的 Spring、Struts2、Hibernate等)为程序员搭建好了舒适的开发环境,以至于让人忽略了之三大类能做到的许多事情。互联网时代每时每刻都在变化,但是这些变化也是基于一些不变的东西,学习一门技术也要从最基本的、不变的底层学起,往上才能开枝散叶,正所谓万变不离其宗。
Listener
Listener 是专门用来监听另一个 Java 对象方法的调用或属性改变,实现了特定接口的特殊类 。刚好最近在阅读有关设计模式的书籍,而这里 Listener 正是通过观察者模式进行实现的,而在前一个月做的项目中有多处都用到了这种设计模式。
在 JavaWeb 应用中,被监听的对象主要是:ServletContext、HttpSession、ServletRequest。
针对以上三个对象的操作,Listener 又被划分成为三种类型:
- 域对象创建和销毁的事件监听器
- 域对象中属性改变的事件监听器
- 绑定到 HttpSession域中的某个对象的状态的事件监听器
域对象创建和销毁的事件监听器
这类Listener的作用是监听对象的创建和销毁,自定义的Listener 类需要实现特定接口中的两个方法:XXXInitialized,XXXDestroyed。下面的代码显示了针对不同的被监听对象的Listener实现:
web.xml
<listener>
<listener-class>com.yzhang.listener.test.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.yzhang.listener.test.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.yzhang.listener.test.MyServletRequestListener</listener-class>
</listener>
MyServletContextListener
//***
public class MyServletContextListener implements ServletContextListener {
private static Logger logger = Logger.getLogger(MyServletContextListener.class);
public void contextInitialized(ServletContextEvent sce) {
logger.debug("servletContextInitialized");
}
public void contextDestroyed(ServletContextEvent sce) {
logger.debug("servletContextDestroyed");
}
}
MyHttpSessionListener
//***
public class MyHttpSessionListener implements HttpSessionListener {
private static Logger logger = Logger.getLogger(MyHttpSessionListener.class);
public void sessionCreated(HttpSessionEvent se) {
logger.debug("sessionCreated");
logger.debug(se.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent se) {
logger.debug("sessionDestroyed");
}
}
MyServletRequestListener
public class MyServletRequestListener implements ServletRequestListener {
private static Logger logger = Logger.getLogger(MyServletRequestListener.class);
public void requestInitialized(ServletRequestEvent sre) {
logger.debug("requestInitialized");
HttpServletRequest request = (HttpServletRequest)sre.getServletRequest();
logger.debug(request.getRequestURL());
}
public void requestDestroyed(ServletRequestEvent sre) {
logger.debug("requestDestroyed");
}
}
ServletContextListener 监视的是整个 Servlet 容器对象,当 Servlet 容器启动时,监听器的contextInitialized
方法被调用,当这个方法被调用时,其实传达的是这么一条信息:我(Servlet 容器)已经启动完成,你(对应的程序)可以开始你自己的工作了。所以在contextInitialized
中往往做一些初始化的工作,同理contextDestroyed
在 Servlet 容器对象被销毁时调用,可做一些收尾工作。
HttpSessionListener监视的是 Session 对象,在整个 web 应用生命周期中,每当有 Session 被创建和销毁时,都会调用其中的sessionCreated
或sessionDestroyed
,在方法中可以通过HttpSessionEvent.getSession()
来获取 Session 的详细信息。
ServletRequestListener与 HttpSessionListener 大致相同,区别在于其监视的是 ServletRequest 对象,通俗来讲:每当有一次页面访问请求到达,都会创建一个ServletRequest 对象,从而导致requestInitialized
方法被调用,而当这次请求返回以后,ServletRequest 对象被销毁时,requestDestroyed
方法将被调用。
进行一次实验,启动 Tomcat -> 访问index.jsp ,将得到下面的输出:
很容易可以分析出各监听器的调用顺序,这里就不过多赘述。
ServletContextListener 的应用——— Spring 的入口
大家肯定都记得在使用 Spring 的时候需要在 Web.xml 中配置一个 Spring 的监听器:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>
context-param 是 Spring 的配置文件位置,在 Spring 启动后会根据applicationContext.xml
中的配置对 Bean 进行实例化,这里主要观察org.springframework.web.context.ContextLoaderListener
这个监听器。
当我们进入到这个监听器内部会发现,这个监听器正是实现了 ServletContextListener,这也符合之前所讲:在contextInitialized
中程序可以进行自己的初始化工作了。在这里Spring就会开始自己实际的工作。
//...
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
而在initWebApplicationContext
方法中将会进行 Bean 的初始化等工作。
下面看一个在项目中实际遇到过的问题,新手常常会犯这种错误,如果没有理解 Listener 的作用时间和范围,往往不能很快找出错误原因。
在一个刻录系统需要维护多条线程,每条线程分别与不同的刻录机服务通过 WebService 进行通信。在程序启动时,需要从数据库中获取用户配置过的刻录机信息,并启动相应的线程。这里可以很快想到的做法是实现一个 ServletContextListener,并在
contextInitialized
中调用PrinterService.listAllLivePrinter()
方法获取活跃的刻录机列表,然后开启线程。
然而事与愿违,在程序启动时报错:找不到 PrinterService。问题就出在自己实现的ServletContextListener和 Spring 的ContextLoaderListener同样都是对 Servlet 容器的监听,那么谁先执行呢?结论是在 web.xml中谁排在前面谁先执行。
这里犯的第一个错误就是将自定义的 ServletContextListener放在了 Spring 的 Listener 之前。在进一步的测试中,把自定义 Listener 放到 Spring 的 Listener 之后,并且在contextInitialized
方法中以如下形式调用:
private UserService userService;
private WebApplicationContext springContext;
public void contextInitialized(ServletContextEvent sce) {
logger.debug("servletContextInitialized");
springContext = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
if (springContext!=null){
logger.debug("get Spring ApplicationContext success");
userService = (UserService) springContext.getBean("userService");
}else{
logger.error("get SpringContext failed");
return;
}
userService.listAllAlive();
logger.debug("call userService.listAllAlive() success");
}
WebApplicationContext 描述的是 Web 应用中的Spring上下文,这里需要通过直接获取 bean 的方式得到 userService 实例对象。修改以后的控制台输出:
可以看到,在自定义的 Listener 中成功获取到userService 并调用了userService.listAllAlive()
。稍作总结,在这个例子中,想要在程序初始化时做一些工作,有两点值得我们注意的地方:
- 在自定义的 ServiceContextListener 中获取 Spring 装配的实例对象,必须要将自定义 Listener 写到 Spring 的 ContextLoaderListener 之后。
- 新手可能直接会在自定义 Listener中
new UserService()
,这么做说明根本没有理解 Spring 的IoC,new
出来的对象与 Spring Bean 工厂生成的根本不是同一个实例,其中的各种 dao 必然没有注入,当调用需要 dao 的方法时(往往是必然的)则会报 NPE。
通过这个例子可以对 ServletContextListener 有更深的认识,而对于其他两个 Listener,除了监听的对象不同,在用法上都是大同小异,在理解了其调用关系之后应该能够很快上手使用。