菜鸟之路——Spring MVC(三)DispatcherServlet详解

前言:回顾一下Spring的启动过程:

  1、首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2、其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3、再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。


  
  因此,使用Spring MVC,配置DispatcherServlet是第一步。
  DispatcherServlet是一个Servlet,所以可以配置多个DispatcherServlet。
  DispatcherServlet是前置控制器,配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据某某规则分发到目标Controller(我们写的Action)来处理。

一、前端控制器架构

  前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。

   《菜鸟之路——Spring MVC(三)DispatcherServlet详解》
   DispatcherServlet 作为前置控制器是web服务器的入口,是spring mvc最重要的一个类,通过它的生命周期可以加深对web服务器的理解。

  首先我们回忆一下servletServlet生命周期
   
生命周期分为三个阶段:
  1、初始化阶段: 调用init()方法。Servlet被装载后,Servlet容器创建一个Servlet实例并且调用Servlet的init()方法进行初始化。在Servlet的整个生命周期内,init()方法只被调用一次。
  2、响应客户请求阶段:调用service()方法
  3、终止阶段:调用destroy()方法的生命周期。

 
  Servlet初始化阶段:
  在下列时刻Servlet容器装载Servlet:
  1、Servlet容器启动时自动装载某些Servlet,实现它只需要在web.XML文件中的<Servlet></Servlet>之间添加如下代码:

    <load-on-startup>1</load-on-startup> 

  2、在Servlet容器启动后,客户首次向Servlet发送请求

  3、Servlet类文件被更新后,重新装载Servlet
  
  我们来看看DispatcherServlet的结构:
  DispatcherServlet继承自抽象类:FrameworkServlet,间接继承了HttpServlet (FrameworkServlet继承HttpServletBean,而HttpServletBean继承自HttpServlet )
  Servlet的初始化:
  《菜鸟之路——Spring MVC(三)DispatcherServlet详解》
  

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context); //文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
        initLocaleResolver(context);    //本地化解析
        initThemeResolver(context);   //主题解析
        initHandlerMappings(context);   //通过HandlerMapping,将请求映射到处理器
        initHandlerAdapters(context);   //通过HandlerAdapter支持多种类型的处理器
        initHandlerExceptionResolvers(context); //如果执行过程中遇到异常将交给HandlerExceptionResolver来解析
        initRequestToViewNameTranslator(context); //直接解析请求到视图名
        initViewResolvers(context);      //通过ViewResolver解析逻辑视图名到具体视图实现
        initFlashMapManager(context);   //flash映射管理器
    }

  Servlet如何处理请求:

  Servlet的service方法处理http请求。
  FrameworkServlet.java 定义了servlet的service和destroy方法,如下所示:

/**
     * Override the parent class implementation in order to intercept PATCH
     * requests.
     */
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String method = request.getMethod();
        if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
            processRequest(request, response);
        }
        else {
            super.service(request, response);
        }
    }


  我们知道http请求类型有七种(外加一个option选项),定义如下:

public enum RequestMethod {

GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE

}

  FrameworkServlet的service()处理不同的请求,我们以常见的post来说明:

/**
     * Process this request, publishing an event regardless of the outcome.
     * <p>The actual event handling is performed by the abstract
     * {@link #doService} template method.
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }

            publishRequestHandledEvent(request, startTime, failureCause);
        }
    }

  FrameworkServlet 抽象定义了处理流程,留待子类来实现该方法,完成具体的请求处理。

/**
     * Subclasses must implement this method to do the work of request handling,
     * receiving a centralized callback for GET, POST, PUT and DELETE.
     * <p>The contract is essentially the same as that for the commonly overridden
     * {@code doGet} or {@code doPost} methods of HttpServlet.
     * <p>This class intercepts calls to ensure that exception handling and
     * event publication takes place.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     * @see javax.servlet.http.HttpServlet#doGet
     * @see javax.servlet.http.HttpServlet#doPost
     */
    protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
            throws Exception;

  具体实现如下:

/**
     * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
     * for the actual dispatching.
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            doDispatch(request, response);
        }
        finally {
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                return;
            }
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

  重头戏,作为请求分发器的实现:

  功能:1. 把请求分发到handler(按照配置顺序获取servlet的映射关系获取handler);2. 根据servlet已安装的 HandlerAdapters 去查询第一个能处理的handler;3. handler激发处理请求

/**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

 
Servlet销毁:

/**
     * Close the WebApplicationContext of this servlet.
     * @see org.springframework.context.ConfigurableApplicationContext#close()
     */
    @Override
    public void destroy() {
        getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
        // Only call close() on WebApplicationContext if locally managed...
        if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
            ((ConfigurableApplicationContext) this.webApplicationContext).close();
        }
    }


  从前面里我们已经看到:
  DispatcherServlet extends FrameworkServlet
  FrameworkServlet extends HttpServletBean implements ApplicationContextAware

  那么HttpServletBean作为DispatcherServlet的父类,起到了一个什么作用呢?

  Spring中这样描述的:

/**
 * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
 * its config parameters ({@code init-param} entries within the
 * {@code servlet} tag in {@code web.xml}) as bean properties.
 *
 * <p>A handy superclass for any type of servlet. Type conversion of config
 * parameters is automatic, with the corresponding setter method getting
 * invoked with the converted value. It is also possible for subclasses to
 * specify required properties. Parameters without matching bean property
 * setter will simply be ignored.
 *
 * <p>This servlet leaves request handling to subclasses, inheriting the default
 * behavior of HttpServlet ({@code doGet}, {@code doPost}, etc).
 *
 * <p>This generic servlet base class has no dependency on the Spring
 * {@link org.springframework.context.ApplicationContext} concept. Simple
 * servlets usually don't load their own context but rather access service
 * beans from the Spring root application context, accessible via the
 * filter's {@link #getServletContext() ServletContext} (see
 * {@link org.springframework.web.context.support.WebApplicationContextUtils}).
 *
 * <p>The {@link FrameworkServlet} class is a more specific servlet base
 * class which loads its own application context. FrameworkServlet serves
 * as direct base class of Spring's full-fledged {@link DispatcherServlet}.*/

  我们可以从HttpServletBean的继承关系来分析它的作用:

  HttpServletBean extends HttpServlet

  implements EnvironmentCapable, EnvironmentAware

  1. 继承了javax.servlet.http.HttpServlet

  简单的说HttpServletBean是javax.servlet.http.HttpServlet类的简单扩展,在web.xml文件中<servlet>标签的下一级标签中通过<init-param>来配置该servlet的参数。实例如下:

<!-- This servlet must be loaded first to configure the log4j
     system and create the WebApplicationContext
     -->
    <servlet>
        <servlet-name>config</servlet-name>
        <servlet-class>org.springframework.framework.web.context.ContextLoaderServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.framework.web.context.XMLWebApplicationContext</param-value>           
        </init-param>    
        <init-param>
            <param-name>log4jPropertiesUrl</param-name>
            <param-value>/WEB-INF/log4j_PRODUCTION.properties</param-value>           
        </init-param>    
        <!-- This is essential -->
        <load-on-startup>1</load-on-startup>
      </servlet>

  2. 继承了EnvironmentAware

  EnvironmentAware到底起了什么作用呢?这需要我们首先了解一下Aware接口的作用:

/**
 * Marker superinterface indicating that a bean is eligible to be
 * notified by the Spring container of a particular framework object
 * through a callback-style method. Actual method signature is
 * determined by individual subinterfaces, but should typically
 * consist of just one void-returning method that accepts a single
 * argument.
 *
 * <p>Note that merely implementing {@link Aware} provides no default
 * functionality. Rather, processing must be done explicitly, for example
 * in a {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}.
 * Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor}
 * and {@link org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory}
 * for examples of processing {@code *Aware} interface callbacks.
 *
 * @author Chris Beams
 * @since 3.1
 */
public interface Aware {

}

  容器中定义的Bean一般不需要了解容器的状态或者直接使用容器,但是在某些情况下,是需要在Bean中直接对IOC容器进行操作的,这时候,就需要在Bean中设定对容器的感知。Spring IOC容器也提供了该功能,它是通过特定的Aware接口来完成的。这个比较抽象,我们来从代码来理解吧:

  从spring-beans模块中我发现有三个实现了Aware接口,它们分别是:

BeanNameAware: Interface to be implemented by beans that want to be aware of their bean name in a bean factory. Note that it is not usually recommended that an object depend on its bean name, as this represents a potentially brittle dependence on external configuration, as well as a possibly unnecessary dependence on a Spring API.

BeanFactoryAware: Interface to be implemented by beans that wish to be aware of their owning {@link BeanFactory}.For example, beans can look up collaborating beans via the factory (Dependency Lookup). Note that most beans will choose to receive references to collaborating beans via corresponding bean properties or constructor arguments (Dependency Injection).

BeanClassLoaderAware: Callback that allows a bean to be aware of the bean {@link ClassLoader class loader}; that is, the class loader used by the present bean factory to load bean classes. This is mainly intended to be implemented by framework classes which have to pick up application classes by name despite themselves potentially being loaded from a shared class loader.

  上面三个接口分别实现了响应的set方法:

public interface BeanNameAware extends Aware {
    void setBeanName(String name);
}
public interface BeanFactoryAware extends Aware {
    void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
public interface BeanClassLoaderAware extends Aware {
    void setBeanClassLoader(ClassLoader classLoader);
}

  从上述三个例子中,我们可以看到实现了Aware接口,bean就可以在spring容器中使用相应的对象。

  那么我们来详细分析一个EnvironmentAware接口:

/**
 * Interface to be implemented by any bean that wishes to be notified
 * of the {@link Environment} that it runs in.
 *
 * @author Chris Beams
 * @since 3.1
 */
public interface EnvironmentAware extends Aware {

    /**
     * Set the {@code Environment} that this object runs in.
     */
    void setEnvironment(Environment environment);

}

  我们来看一下HttpServletBean下的setEnvironment方法实现。

/**
     * {@inheritDoc}
     * @throws IllegalArgumentException if environment is not assignable to
     * {@code ConfigurableEnvironment}.
     */
    @Override
    public void setEnvironment(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        this.environment = (ConfigurableEnvironment) environment;
    }

    /**
     * {@inheritDoc}
     * <p>If {@code null}, a new environment will be initialized via
     * {@link #createEnvironment()}.
     */
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = this.createEnvironment();
        }
        return this.environment;
    }

    /**
     * Create and return a new {@link StandardServletEnvironment}. Subclasses may override
     * in order to configure the environment or specialize the environment type returned.
     */
    protected ConfigurableEnvironment createEnvironment() {
        return new StandardServletEnvironment();
    }

  从上述代码中我们可以看到默认情况下的

  environment=new StandardServletEnvironment()

  StandardServletEnvironment 后续章节讲到,这里我们仅仅看作Spring抽象了一个Environment来表示环境配置。

  3. 继承了EnvironmentCapable

/**
 * Interface indicating a component that contains and exposes an {@link Environment} reference.
 *
 * <p>All Spring application contexts are EnvironmentCapable, and the interface is used primarily
 * for performing {@code instanceof} checks in framework methods that accept BeanFactory
 * instances that may or may not actually be ApplicationContext instances in order to interact
 * with the environment if indeed it is available.
 *
 * <p>As mentioned, {@link org.springframework.context.ApplicationContext ApplicationContext}
 * extends EnvironmentCapable, and thus exposes a {@link #getEnvironment()} method; however,
 * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
 * redefines {@link org.springframework.context.ConfigurableApplicationContext#getEnvironment
 * getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}.
 * The effect is that an Environment object is 'read-only' until it is being accessed from
 * a ConfigurableApplicationContext, at which point it too may be configured.
 *
 * @author Chris Beams
 * @since 3.1
 * @see Environment
 * @see ConfigurableEnvironment
 * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
 */
public interface EnvironmentCapable {

    /**
     * Return the {@link Environment} associated with this component.
     */
    Environment getEnvironment();

}

  4. Environment 环境配置信息

  EnvironmentCapable 接口和EnvironmentAware分别实现抽象了

  Environment getEnvironment();

  void setEnvironment(Environment environment);

  它的主要几个实现如下所示:

  MockEnvironment:模拟的环境,用于测试时使用;

  StandardEnvironment:标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境;

public class StandardEnvironment extends AbstractEnvironment {

    /** System environment property source name: {@value} */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value} */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); //System.getProperties();
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));//System.getenv()
    }

}

  其中:

  StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了 StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及JNDI实例到环境;

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {

    /** Servlet context init parameters property source name: {@value} */
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

    /** Servlet config init parameters property source name: {@value} */
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

    /** JNDI property source name: {@value} */
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";


    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }

    @Override
    public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
        WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
    }

}

   HttpServletBean分别实现了HttpServlet,EnvironmentCapable,EnvironmentAware。简单扩展HttpServlet,给各种类型的servlet提供了一个便利的超类,提供了对属性的操作。

二、配置DispatcherServlet

  讲了这么多,那么该如何配置DispatcherServlet?
  

<servlet>  
    <servlet-name>springMVC</servlet-name>  
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
    <init-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>classpath*:/<span style="font-family: SimSun;font-size:14px;">springMVC.</span><span style="font-family: SimSun;">xml</param-value>  </span>
    </init-param>  
    <load-on-startup>1</load-on-startup>  
</servlet>  
<servlet-mapping>  
    <servlet-name>springMVC</servlet-name>  
    <url-pattern>/</url-pattern>  
</servlet-mapping>  

  <load-on-startup>1</load-on-startup>是启动顺序,让这个Servlet随Servletp容器一起启动。

  <url-pattern> </url-pattern> 配置需要拦截的请求形式。

  <servlet-name>springMVC</servlet-name>这个Servlet的名字是springMVC,可以有多个DispatcherServlet,是通过名字来区分的。每一个DispatcherServlet有自己的WebApplicationContext上下文对象。同时保存的ServletContext中和Request对象中。

  在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为springMVC.xml 的配置文件,生成文件中定义的bean。如果没有配置路径,那么框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。这里指明了配置文件的文件名,不使用默认配置文件名,而使用springMVC.xml配置文件。

其中<param-value>**.xml</param-value> 这里可以使用多种写法:

  1、不写,使用默认值:/WEB-INF/<servlet-name>-servlet.xml

  2、<param-value>/WEB-INF/classes/springMVC.xml</param-value>

  3、<param-value>classpath*:springMVC-mvc.xml</param-value>

  4、多个值用逗号分隔


  Servlet拦截匹配规则可以自已定义,拦截哪种URL合适?
  当映射为@RequestMapping(“/user/add”)时,为例:
  1、拦截*.do、*.htm, 例如:/user/add.do
  这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。

  2、拦截/,例如:/user/add
  可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。
  弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。想实现REST风格,事情就是麻烦一些。后面有解决办法还算简单。

  3、拦截/*,这是一个错误的方式,请求可以走到Action中,但转到jsp时再次被拦截,不能访问到jsp。
  如:
  <url-pattern>/</url-pattern>这种方式的配置,那么tomcat会把所有访问该应用的请求都分配给org.springframework.web.servlet.DispatcherServlet来进行处理。


  如何配置静态资源?
  所有的请求都会转给前置控制器,而前置控制器的工作原理就是针对每个请求找到相应的Controller进行处理,并返回消息。那么,针对静态资源的请求,前置控制器就只好报异常了,因为它确实找不到。
  所以,我们需要告诉静态资源的请求如何处理,有两种方式:
  方式一:使用容器的默认处理器。

<mvc:default-servlet-handler />

  此配置告诉DispatcherServlet,对于找不到Controller的请求,就把它交给tomcat的默认处理器就行处理,而tomcat的默认处理器会把请求的资源返回给客户端。

 
方式二:在前置控制器的配置文件中指明静态资源的请求路径,该路径下的请求一律直接返回资源即可。

<mvc:resources location="/resources/" mapping="/resources/**"/>

  此配置告诉DispatcherServlet,任何http://ip:port/app/resources/**的请求都将其定位到/resources/目录下,并寻找对应的目录文件,作为资源文件返回给客户端。

  其中,两个*,它表示映射resources/下所有的URL,包括子路径(即接多个/)。

  例如:

  1、访问,浏览器显示webapp/resources/test.png

  2、访问,浏览器显示webapp/resources/scripts/test.js

  3、访问,浏览器显示webapp/resources/css/2012/test.css

 三种访问url,如果只配一个*就只第一个能访问,现在配置两个*那么1、2、3均可访问。

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/sjjsh2/article/details/53054203
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞