Servlet之Listener监听器的基本理解

ServletContextListener(Servlet全局监听器)

首先要说明的是,ServletContextListener是一个接口,我们随便写一个类,只要这个类实现了ServletContextListener接口,那么这个类就实现了【监听ServletContext】的功能。那么,这个神奇的接口是如何定义的呢?我们来看一下这个接口的内部情况:

package javax.servlet;

import java.util.EventListener;

public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);

    void contextDestroyed(ServletContextEvent var1);
}

我们发现,在这个接口中只声明了两个方法,分别是void contextInitialized(ServletContextEvent var1)和void contextDestroyed(ServletContextEvent var1)方法,所以,我们很容易的就能猜测到,ServletContext的生命只有两种,分别是:

1.ServletContext初始化。(应用start时)---------->Servlet容器调用void contextInitialized(ServletContextEvent var1)

2.ServletContext销毁。(应用stop时)---------->Servlet容器调用 void contextDestroyed(ServletContextEvent var1)

因此,我们大概能够猜到ServletContextListener的工作机制了,当应用启动时,ServletContext进行初始化,然后Servlet容器会自动调用正在监听ServletContext的ServletContextListener的void contextInitialized(ServletContextEvent var1)方法,并向其传入一个ServletContextEvent对象。当应用停止时,ServletContext被销毁,此时Servlet容器也会自动地调用正在监听ServletContext的ServletContextListener的void contextDestroyed(ServletContextEvent var1)方法。

为了验证我们的猜测,我们来随便写一个类,并且实现ServletContextListener接口,即实现监听ServletContext的功能:

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContextListener.contextInitialized方法被调用");
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContextListener.contextDestroyed方法被调用");
    }
}

然后,在web.xml中注册我们自己写的这个MyListener:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <listener>
        <listener-class>MyListener</listener-class>
    </listener>

</web-app>

接下来,让我们启动一下Tomcat,看一看会发生什么吧!控制台打印信息如下:

我们发现,当应用启动时,ServletContextListener.contextInitialized()方法被调用了。这其实是Servlet容器偷偷干的事情。那么,当我们停止Tomcat时,按照猜想,Servlet容器应该也会偷偷调用void contextDestroyed(ServletContextEvent var1)方法,来通知ServletContextListener监听器:ServletContext已经被销毁了。那么,事实是不是和我们猜想的一模一样呢?让我们来停止Tomcat的运行,看一看控制台的情况吧:

我们发现,void contextDestroyed(ServletContextEvent var1)方法确实被Servlet容器调用了。因此,我们的猜想得到了证实。


【进阶】ServletContextListener在Spring中的应用

如果基础好一点的童鞋,或者已经学过Spring框架的同学,建议阅读下面的内容,没有学过Spring也没有关系,可以先学或者学完之后再回头来看一看,Spring容器是如何借用ServletContextListener这个接口来实例化的。

首先让我们再来回顾一下ServletContext的概念,ServletContext翻译成中文叫做“Servlet上下文”或者“Servlet全局”,但是这个翻译我认为翻译的实在是有点牵强,也导致了许多的开发者不明白这个变量到底具体代表了什么。其实ServletContext就是一个“域对象”,它存在于整个应用中,并在在整个应用中有且仅有1份,它表示了当前整个应用的“状态”,你也可以理解为某个时刻的ServletContext代表了这个应用在某个时刻的“一张快照”,这张“快照”里面包含了有关应用的许多信息,应用的所有组件都可以从ServletContext获取当前应用的状态信息。ServletContext随着程序的启动而创建,随着程序的停止而销毁。通俗点说,我们可以往这个ServletContext域对象中“存东西”,然后也可以在别的地方中“取出来”。

我们知道,Spring容器可以通过:

ApplicationContext ctx=new ClassPathXmlApplicationContext("配置文件的路径");

显示地实例化一个Spring IOC容器。也可以像下面一样,在web.xml中注册Spring IOC容器:

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

其中的监听器类【org.springframework.web.context.ContextLoaderListener】实现了ServletContextListener接口,能够监听ServletContext的生命周期中的“初始化”和“销毁”。注意,这个【org.springframework.web.context.ContextLoaderListener】监听器类当然不是我们自己写的哦,是人家Spring团队写的,我们只要拿来用就行了。当然,别忘记导入相关的Jar包。(spring-web-4.2.4.RELEASE.jar)

那么,Spring团队给我们提供的这个监听器类是如何实现:当ServletContext初始化后,Spring IOC容器也能跟着初始化的呢?怀着好奇心,让我们再来看一看【org.springframework.web.context.ContextLoaderListener】的内部实现情况吧。

package org.springframework.web.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
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());
    }
}

我们发现,【org.springframework.web.context.ContextLoaderListener】这个类实现了ServletContextListener接口中的两个方法,其中,当ServletContext初始化后, public void contextInitialized(ServletContextEvent event)方法被调用,接下来执行initWebApplicationContext(event.getServletContext())方法,但是我们发现这个方法并没有在这个类中声明,因此,我们再看一下其父类中是如何声明的:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
 
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
 
    } else {
 
        Log logger = LogFactory.getLog(ContextLoader.class);
 
        servletContext.log("Initializing Spring root WebApplicationContext");
 
        if (logger.isInfoEnabled()) {
 
            logger.info("Root WebApplicationContext: initialization started");
 
        }
 
        long startTime = System.currentTimeMillis();
 
        try {
 
            if (this.context == null) {
 
                this.context = this.createWebApplicationContext(servletContext);
 
            }
 
            if (this.context instanceof ConfigurableWebApplicationContext) {
 
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
 
                if (!cwac.isActive()) {
 
                    if (cwac.getParent() == null) {
 
                        ApplicationContext parent = this.loadParentContext(servletContext);
 
                        cwac.setParent(parent);
 
                    }
 
                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
 
                }
            }
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
 
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
 
            if (ccl == ContextLoader.class.getClassLoader()) {
 
                currentContext = this.context;
 
            } else if (ccl != null) {
 
                currentContextPerThread.put(ccl, this.context);
 
            }
 
            if (logger.isDebugEnabled()) {
 
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
 
            }
 
            if (logger.isInfoEnabled()) {
 
                long elapsedTime = System.currentTimeMillis() - startTime;
 
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
 
            }
 
            return this.context;
 
        } catch (RuntimeException var8) {
 
            logger.error("Context initialization failed", var8);
 
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
 
            throw var8;
 
        } catch (Error var9) {
 
            logger.error("Context initialization failed", var9);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
 
            throw var9; 
        } 
    } 
}

分析到这一步,我们发现Spring容器在这个方法中被实例化了。接下来,就让我们整理一下整体的思路:

当Servlet容器启动时,ServletContext对象被初始化,然后Servlet容器调用web.xml中注册的监听器的

public void contextInitialized(ServletContextEvent event)

方法,而在监听器中,调用了this.initWebApplicationContext(event.getServletContext())方法,在这个方法中实例化了Spring IOC容器。即ApplicationContext对象。

因此,当ServletContext创建时我们可以创建applicationContext对象,当ServletContext销毁时,我们可以销毁applicationContext对象。这样applicationContext就和ServletContext“共生死”了。

发布了70 篇原创文章 · 获赞 1 · 访问量 2248

猜你喜欢

转载自blog.csdn.net/caozp913/article/details/103916442