美文网首页基础与框架
Listener总结(1)---域对象创建和销毁监听器

Listener总结(1)---域对象创建和销毁监听器

作者: 鬼畜的猪 | 来源:发表于2017-04-02 22:32 被阅读511次

    原创文章,转载请注明出处

    ​ 从开年到现在两个月时间,一直想慢慢开始写点东西,将工作以来学到的知识和技能进行总结。但是偏偏年后项目比较急,一个集中刻录项目就写了一个月左右,但是得到的收获也不少。好不容易清明有了一点的空闲,就将之前的一些笔记和开发中遇到的问题好好的总结一下吧。

    ​ 万事开头难,能提笔开始写这篇文章,相信以后能养成定时总结的习惯,也希望能和大家进行探讨,有错误和疏漏的地方还请不吝赐教。

    ​ 废话也就不多说了,今天主要总结一下 JavaWeb 开发中最基本的三大类:Listener、Filter与 Servlet中的 Listener。对于 JavaWeb 程序来讲,这三个类是维持其基本运作的基石,在现代开发中,各种框架(最常见的 Spring、Struts2、Hibernate等)为程序员搭建好了舒适的开发环境,以至于让人忽略了之三大类能做到的许多事情。互联网时代每时每刻都在变化,但是这些变化也是基于一些不变的东西,学习一门技术也要从最基本的、不变的底层学起,往上才能开枝散叶,正所谓万变不离其宗。

    Listener

    ​ Listener 是专门用来监听另一个 Java 对象方法的调用或属性改变,实现了特定接口的特殊类 。刚好最近在阅读有关设计模式的书籍,而这里 Listener 正是通过观察者模式进行实现的,而在前一个月做的项目中有多处都用到了这种设计模式。

    ​ 在 JavaWeb 应用中,被监听的对象主要是:ServletContextHttpSessionServletRequest

    ​ 针对以上三个对象的操作,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 被创建和销毁时,都会调用其中的sessionCreatedsessionDestroyed,在方法中可以通过HttpSessionEvent.getSession()来获取 Session 的详细信息。

    ​ ServletRequestListener与 HttpSessionListener 大致相同,区别在于其监视的是 ServletRequest 对象,通俗来讲:每当有一次页面访问请求到达,都会创建一个ServletRequest 对象,从而导致requestInitialized方法被调用,而当这次请求返回以后,ServletRequest 对象被销毁时,requestDestroyed方法将被调用。

    ​ 进行一次实验,启动 Tomcat -> 访问index.jsp ,将得到下面的输出:

    Tomcat 输出1.png

    ​ 很容易可以分析出各监听器的调用顺序,这里就不过多赘述。

    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.png

    ​ 可以看到,在自定义的 Listener 中成功获取到userService 并调用了userService.listAllAlive()。稍作总结,在这个例子中,想要在程序初始化时做一些工作,有两点值得我们注意的地方:

    1. 在自定义的 ServiceContextListener 中获取 Spring 装配的实例对象,必须要将自定义 Listener 写到 Spring 的 ContextLoaderListener 之后。
    2. 新手可能直接会在自定义 Listener中new UserService(),这么做说明根本没有理解 Spring 的IoC,new出来的对象与 Spring Bean 工厂生成的根本不是同一个实例,其中的各种 dao 必然没有注入,当调用需要 dao 的方法时(往往是必然的)则会报 NPE。

    ​ 通过这个例子可以对 ServletContextListener 有更深的认识,而对于其他两个 Listener,除了监听的对象不同,在用法上都是大同小异,在理解了其调用关系之后应该能够很快上手使用。

    相关文章

      网友评论

        本文标题:Listener总结(1)---域对象创建和销毁监听器

        本文链接:https://www.haomeiwen.com/subject/vlcnottx.html