`
weknow619
  • 浏览: 63343 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

【Spring】浅谈ContextLoaderListener及其上下文与DispatcherServlet的区别

阅读更多
一般在使用SpingMVC开发的项目中,一般都会在web.xml文件中配置ContextLoaderListener监听器,如下:
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在开始讲解这个之前先讲讲web工程的上下文,对于一个web容器,web容器提供了一个全局的上下文环境,这个上下文就是ServletContext,其为后面Spring IOC容器提供宿主环境。

在web容器启动时会触发容器初始化事件,contextLoaderListener监听到这个事件后其contextInitialized方法就会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文就是根上下文,也就是WebApplicationContext,实际实现类一般是XmlWebApplicationContext,这个其实就是spring的IoC容器,这个IoC容器初始化完后,Spring会将它存储到ServletContext,可供后面获取到该IOC容器中的bean。

下面一步步来跟进,看下ContextLoaderListener源码:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

从上面可以看出ContextLoaderListener继承ContextLoader类并实现了ServletContextListener接口,ServletContextListener接口中只有初始化和销毁的两个方法,如下:
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.
     */
    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.
     */
    public void contextDestroyed ( ServletContextEvent sce );
}

ContextLoaderListener主要的功能还是在继承的ContextLoader类中实现,接下来看看ContextLoaderListener中上下文初始化的方法,也就是:
/**
 * Initialize the root web application context.
 */
@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}

跟进initWebApplicationContext()方法,其调用的实现就在ContextLoader类中:
/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext 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!");
    }

    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.
        if (this.context == null) {
            // 创建WebApplicationContext上下文
            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);
                }
                // 对WebApplicationContext进行初始化,初始化参数从web.xml中取
                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);
        }

        /* 省略部分代码 */
}

上面initWebApplicationContext()方法中,通过createWebApplicationContext(servletContext)创建root上下文(即IOC容器),之后Spring会以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE属性为Key,将该root上下文存储到ServletContext中,下面看看createWebApplicationContext(servletContext)源码:
/**
 * Instantiate the root WebApplicationContext for this loader, either the
 * default context class or a custom context class if specified.
 * <p>This implementation expects custom contexts to implement the
 * {@link ConfigurableWebApplicationContext} interface.
 * Can be overridden in subclasses.
 * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
 * context, allowing subclasses to perform custom modifications to the context.
 * @param sc current servlet context
 * @return the root WebApplicationContext
 * @see ConfigurableWebApplicationContext
 */
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 确定载入的上下文的类型,参数是在web.xml中配置的contextClass(没有则使用默认的)
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 初始化WebApplicationContext并强转为ConfigurableWebApplicationContext类型
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

从上面源码也可以看出使用createWebApplicationContext方法创建的上下文肯定是实现了ConfigurableWebApplicationContext接口,否则抛出异常。上面createWebApplicationContext(servletContext)方法里的determineContextClass方法用于查找root上下文的Class类型,看源码:
/**
 * Return the WebApplicationContext implementation class to use, either the
 * default XmlWebApplicationContext or a custom context class if specified.
 * @param servletContext current servlet context
 * @return the WebApplicationContext implementation class to use
 * @see #CONTEXT_CLASS_PARAM
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

从以上可以看到如果web.xml中配置了实现ConfigurableWebApplicationContext的contextClass类型就用那个参数,否则使用默认的XmlWebApplicationContext。

上面ContextLoader类的initWebApplicationContext()方法里还有个加载父上下文的方法loadParentContext(ServletContext servletContext),也来看看其源码:
/**
 * Template method with default implementation (which may be overridden by a
 * subclass), to load or obtain an ApplicationContext instance which will be
 * used as the parent context of the root WebApplicationContext. If the
 * return value from the method is null, no parent context is set.
 * <p>The main reason to load a parent context here is to allow multiple root
 * web application contexts to all be children of a shared EAR context, or
 * alternately to also share the same parent context that is visible to
 * EJBs. For pure web applications, there is usually no need to worry about
 * having a parent context to the root web application context.
 * <p>The default implementation uses
 * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator},
 * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and
 * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context
 * which will be shared by all other users of ContextsingletonBeanFactoryLocator
 * which also use the same configuration parameters.
 * @param servletContext current servlet context
 * @return the parent application context, or {@code null} if none
 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
 */
protected ApplicationContext loadParentContext(ServletContext servletContext) {
    ApplicationContext parentContext = null;
    String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
    String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

    if (parentContextKey != null) {
        // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
        BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Getting parent context definition: using parent context key of '" +
                    parentContextKey + "' with BeanFactoryLocator");
        }
        this.parentContextRef = locator.useBeanFactory(parentContextKey);
        parentContext = (ApplicationContext) this.parentContextRef.getFactory();
    }

    return parentContext;
}

上面源码就是实现根据locatorFactorySelector和parentContextKey来给上下文设置父上下文,前提是我们在web.xml中配置了这两个参数,不过一般开发中很少会设置这两个参数,从上面源码的大段注释也可以看出如果没有的话父上下文就为空。

在contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中实现的,基本工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关的属性为Key,也将其存到ServletContext中。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(WebApplicationContext)。

最后讲讲ContextLoaderListener与DispatcherServlet所创建的上下文ApplicationContext的区别:

1.ContextLoaderListener中创建ApplicationContext主要用于整个Web应用程序需要共享的一些组件,比如DAO,数据库的ConnectionFactory等。而由DispatcherServlet创建的ApplicationContext主要用于和该Servlet相关的一些组件,比如Controller、ViewResovler等。
2.对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext,而反过来不行。

这两个ApplicationContext都是通过ServletContext的setAttribute方法放到ServletContext中的。从web.xml的配置可知ContextLoaderListener会先于DispatcherServlet创建ApplicationContext,DispatcherServlet在创建ApplicationContext时会先找到由ContextLoaderListener所创建的ApplicationContext,再将后者的ApplicationContext作为参数传给DispatcherServlet的ApplicationContext的setParent()方法,作为它的父上下文,在Spring源代可以看出:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());

    // 设置父ApplicationContext
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

这里wac即为由DisptcherServlet创建的ApplicationContext,而parent则为有ContextLoaderListener创建的ApplicationContext。

当Spring在执行ApplicationContext的getBean时,如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。

0
0
分享到:
评论

相关推荐

    DispatcherServlet 和 ContextLoaderListener 区别

    ContextLoaderListener则是Spring容器的启动监听器,它负责初始化Spring应用上下文(ApplicationContext)。当Web应用启动时,ContextLoaderListener会读取Web-INF下的applicationContext.xml配置文件,创建并加载...

    web.xml中ContextLoaderListener的运行过程解析

    `ContextLoaderListener`是Spring框架中的一个监听器,它负责初始化Spring应用上下文。下面将详细解析`web.xml`中`ContextLoaderListener`的运行过程。 ### 1. `web.xml`的作用 `web.xml`文件主要用来定义Servlet、...

    Spring-5.1.5源码

    `ContextLoaderListener`是一个实现了`javax.servlet.ServletContextListener`接口的类,它的主要职责是在Web应用启动时初始化Spring应用上下文,并在应用关闭时清理资源。这个过程涉及以下几个关键知识点: 1. **...

    SpringMVC 处置流程分析

    总结,SpringMVC的处置流程始于Web应用的初始化,包括ContextLoaderListener加载根上下文和DispatcherServlet初始化子上下文。接着,DispatcherServlet负责接收和分发请求,通过HandlerMapping找到处理器,...

    servlet与spring整合例子

    然后在Web应用的`web.xml`中,通过`ContextLoaderListener`配置Spring上下文,使得在应用启动时Spring容器被初始化。 2. **Servlet与Spring的交互** 传统的Servlet在接收到请求后,会手动创建或查找需要的服务对象...

    Spring与Web环境集成1

    Spring提供了一个名为`ContextLoaderListener`的监听器,它自动加载配置文件,创建ApplicationContext,并将其存储到Servlet上下文(ServletContext)中。这样,我们可以通过`WebApplicationContextUtils`工具类的...

    Spring mvc + Spring + Spring jdbc 整合 demo.rar

    - 配置Web.xml文件,设置DispatcherServlet的初始化参数和监听器,以便加载Spring上下文。 - 编写Controller,处理HTTP请求,并调用Service完成业务逻辑。 5. **项目源码分析**:在压缩包中的项目源码,我们可以...

    Spring之Spring2.5集成Struts2.2

    前者处理请求,后者初始化Spring应用上下文。 4. **Struts2-Spring插件**:为了使Struts2能与Spring协同工作,需要引入Struts2的Spring插件。该插件允许Struts2的动作类(Action)作为Spring的bean来管理,从而利用...

    springMVC spring ibatis整合jar

    DispatcherServlet 负责处理 HTTP 请求,而 ContextLoaderListener 则初始化 Spring 上下文。 2. **引入 iBatis**:添加 iBatis 相关的依赖,配置 SqlSessionFactory 和 DataSource。SqlSessionFactory 是 iBatis ...

    apache wink集成spring 开发rest服务

    8. **部署**:完成开发后,可以通过Spring的ContextLoaderListener加载应用上下文,并将Wink的Servlet配置到应用服务器中,如Tomcat或Jetty。这样,REST服务就可以通过HTTP请求被外部调用了。 通过上述知识点,我们...

    在Servlet直接获取Spring框架中的Bean.docx

    在Web应用中,Spring通常会创建一个WebApplicationContext,它与Servlet上下文(ServletContext)关联。 **方法一:使用WebApplicationContextUtils** 在Servlet中,我们可以利用`WebApplicationContextUtils`工具...

    webwork-spring-hibernate.rar_spring_webwork

    5. **部署描述符**:在web.xml中,需要配置DispatcherServlet、Filter(如Spring的ContextLoaderListener和DelegatingFilterProxy)来启动Spring应用上下文,并将请求转发给WebWork。 6. **测试与调试**:配置完成...

    spring mvc

    - `&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;` 是Spring的监听器,它会在应用启动时加载应用上下文。 2. `&lt;servlet&gt;` 和 `&lt;servlet-mapping&gt;` 部分: - `...

    配置DispatcherServlet的方法介绍

    当DispatcherServlet被载入后,它将从xml文件载入Spring的应用上下文,而从哪个xml文件载入呢?xml文件的名字取决于DispatcherServlet的名字。如果DispatcherServlet的名字为sample,那么它将从sample-servlet.xml的...

    spring和easyui结合

    例如,`ContextLoaderListener`用于在Web应用启动时加载Spring的上下文,`CharacterEncodingFilter`则确保了字符编码的正确处理,而`DispatcherServlet`则是Spring MVC的核心组件,负责调度请求到相应的控制器。...

    Spring Live参考手册

    其核心组件包括DispatcherServlet和ContextLoaderListener,它们分别负责处理HTTP请求和加载应用上下文。Spring MVC强调模型-视图-控制器(MVC)架构,使得Web应用的开发更为结构化和模块化。 五、Spring框架的实际...

    maven spring security框架搭建

    此监听器用于加载Spring的上下文,在应用启动时初始化Spring容器。 4. **Spring MVC Dispatcher Servlet**: ```xml &lt;servlet-name&gt;spring-mvc &lt;servlet-class&gt;org.springframework.web.servlet....

    FreeMarker整合Spring_3

    -- Spring上下文参数 --&gt; &lt;param-name&gt;contextConfigLocation &lt;param-value&gt;classpath*:applicationContext-*.xml &lt;!-- DispatcherServlet配置 --&gt; &lt;servlet-name&gt;dispatcher &lt;servlet-class&gt;org.spring...

    struts2.1.8 集成 spring hibernate需要的 核心 jar

    1. **配置Web.xml**:设置Struts2的前端控制器DispatcherServlet,以及Spring的ContextLoaderListener来初始化Spring容器。 2. **定义Spring配置文件**:声明Bean并配置它们的依赖关系,包括Struts2的Action类、...

Global site tag (gtag.js) - Google Analytics