乐趣区

关于前端:web监听器解析

监听器是 web 三大组件之一,事件监听机制如下:

  • 事件:某个事件,如果初始化上下文
  • 事件源:事件产生的中央
  • 监听器:一个对象,领有须要执行的逻辑
  • 注册监听:将事件、事件源、监听器绑定在一起。当事件源产生某个事件后,将事件传递给监听器,监听器执行相应代码逻辑 

增加监听器

在 web 我的项目中的 web.xml 配置文件中个别有这样一段代码,ContextLoaderListener 是一个监听器实现了 ServletContextListener 接口。在后续解析 web.xml 文件中会增加到 StandardContext 上下文的 applicationListeners 数组中,之后当上下文开启后会依据监听器的全限定名结构监听器实例并初始化监听器。

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

ServletContextListener 能在 web 应用程序初始化的过程中收到告诉,在过滤器 filter 和 servlet 初始化之前执行相应的上下文初始化逻辑,也能在 servlet 上下文敞开时收到告诉,在过滤器 filter 和 servlet 销毁后执行相应的上下文销毁逻辑。

public interface ServletContextListener extends EventListener {

    /**
     ** Notification that the web application initialization process is starting.
     * All ServletContextListeners are notified of context initialization before
     * any filter or servlet in the web application is initialized.
     * @param sce Information about the ServletContext that was initialized
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     ** Notification that the servlet context is about to be shut down. All
     * servlets and filters have been destroy()ed before any
     * ServletContextListeners are notified of context destruction.
     * @param sce Information about the ServletContext that was destroyed
     */
    public void contextDestroyed(ServletContextEvent sce);
}

初始化监听器

当 StandardContext 上下文启动后会调用 listenerStart 办法,该办法初始化所有增加到 context 的监听器,之后构建事件执行 ServletContextListener 类型的监听器 listener.contextInitialized(event); 初始化上下文 

public boolean listenerStart() {if (log.isDebugEnabled()) {log.debug("Configuring application event listeners");
    }

    // Instantiate the required listeners
    // 注册监听器的全限定名数组
    String listeners[] = findApplicationListeners();
    Object results[] = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {if (getLogger().isDebugEnabled()) {getLogger().debug("Configuring event listener class'" +
                listeners[i] + "'");
        }
        try {String listener = listeners[i];
            // 实例化监听器
            results[i] = getInstanceManager().newInstance(listener);
        } catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);
            ExceptionUtils.handleThrowable(t);
            getLogger().error(sm.getString("standardContext.applicationListener", listeners[i]), t);
            ok = false;
        }
    }
    if (!ok) {getLogger().error(sm.getString("standardContext.applicationSkipped"));
        return false;
    }

    // Sort listeners in two arrays
    List<Object> eventListeners = new ArrayList<>();
    List<Object> lifecycleListeners = new ArrayList<>();
    // 分组 辨别事件监听器和生命周期监听器
    // ServletContextListener 为生命周期监听器
    for (Object result : results) {if ((result instanceof ServletContextAttributeListener)
                || (result instanceof ServletRequestAttributeListener)
                || (result instanceof ServletRequestListener)
                || (result instanceof HttpSessionIdListener)
                || (result instanceof HttpSessionAttributeListener)) {eventListeners.add(result);
        }
        if ((result instanceof ServletContextListener)
                || (result instanceof HttpSessionListener)) {lifecycleListeners.add(result);
        }
    }

    // Listener instances may have been added directly to this Context by
    // ServletContextInitializers and other code via the pluggability APIs.
    // Put them these listeners after the ones defined in web.xml and/or
    // annotations then overwrite the list of instances with the new, full
    // list.
    // 设置利用的事件监听器和生命周期监听器
    eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
    setApplicationEventListeners(eventListeners.toArray());
    for (Object lifecycleListener: getApplicationLifecycleListeners()) {lifecycleListeners.add(lifecycleListener);
        if (lifecycleListener instanceof ServletContextListener) {noPluggabilityListeners.add(lifecycleListener);
        }
    }
    setApplicationLifecycleListeners(lifecycleListeners.toArray());

    // Send application start events

    if (getLogger().isDebugEnabled()) {getLogger().debug("Sending application start events");
    }

    // Ensure context is not null
    getServletContext();
    // 不容许设置新的监听
    context.setNewServletContextListenerAllowed(false);

    Object instances[] = getApplicationLifecycleListeners();
    if (instances == null || instances.length == 0) {return ok;}

    // 依据 servlet 上下文构建 ServletContextEvent 事件
    ServletContextEvent event = new ServletContextEvent(getServletContext());
    ServletContextEvent tldEvent = null;
    if (noPluggabilityListeners.size() > 0) {noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
        tldEvent = new ServletContextEvent(noPluggabilityServletContext);
    }
    // 调用 ServletContextListener 监听器
    for (Object instance : instances) {if (!(instance instanceof ServletContextListener)) {continue;}
        ServletContextListener listener = (ServletContextListener) instance;
        try {fireContainerEvent("beforeContextInitialized", listener);
            if (noPluggabilityListeners.contains(listener)) {listener.contextInitialized(tldEvent);
            } else {
                // 容器上下文初始化 调用监听器 contextInitialized 办法初始化
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } catch (Throwable t) {ExceptionUtils.handleThrowable(t);
            fireContainerEvent("afterContextInitialized", listener);
            getLogger().error(sm.getString("standardContext.listenerStart",
                    instance.getClass().getName()), t);
            ok = false;
        }
    }
    return ok;

}

ContextLoaderListener 解析

ContextLoaderListener 上下文加载监听器有什么作用呢?它是 spring 的一个类,次要用来初始化 spring 容器 XmlWebApplicationContext。咱们也能够用 contextClass 指定上下文类应用反对扫描注解的 AnnotationConfigWebApplicationContext,如下:

<context-param>
    <param-name>contextClass</param-name>
    <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
</context-param>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.monian.study.config.AppConfig</param-value>
</context-param>
@Configuration
@ComponentScan(basePackages = "com.monian.study")
@PropertySource(value = {"classpath:oss.properties", "classpath:datasource.properties"})
public class AppConfig {}

当执行 contextInitialized 办法时初始化根 web 利用上下文:依据 contextClass 配置的上下文类 AnnotationConfigWebApplicationContext 若没有配置则默认 XmlWebApplicationContext 进行类加载后再通过反射实例化 web 容器,接着对 web 容器容器 id 更新、配置文件门路设置等初始化操作,最初调用 refresh() 办法刷新容器,将 AppConfig 作为初始 java 配置文件,扫描 basePackages 包下的所有类解析 spring bean 构建 spring 容器 

/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 属性为空,不为空阐明可能有反复 报错
    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!");
    }

    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 {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        // 创立 contextClass 指定的 web 上下文
        if (this.context == null) {this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    // 如果有父容器的话进行设置
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // 配置 & 刷新容器
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将容器上下文设置到 servletContext 的 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 属性中
        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 ex) {logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    // 默认雷同 从新设置容器上下文标识
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        // 从 servlet 上下文获取 contextId
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {wac.setId(idParam);
        }
        else {
            // Generate default id... 设置上下文 id
            // WebApplicationContext:+contextPath
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    // 设置 servlet 上下文
    wac.setServletContext(sc);
    // 获取配置文件门路 contextConfigLocation
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    // 设置 contextConfigLocation
    if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);
    }

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    // 初始化环境配置
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        // 初始化 servlet 属性资源
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    // 自定义初始化
    customizeContext(sc, wac);

    // 刷新 web 容器 初始化 bean
    wac.refresh();}
退出移动版