java架構-Spring MVC 與 Servlet

相信你們都可以在上網上看到Spring MVC的核心類其實就是DispatherServlet,也就是Spring MVC處理請求的核心分發器。其實核心分發器幾乎是全部MVC框架設計中的核心概念,像在Struts2也有相似的分發器FilterDispatcher。只不過Spring MVC中的是一個Servlet,而Struts2裏面的是一個Filter.既然咱們知道了Spring MVC的中核心類DispatcherServlet是一個Servlet。下面咱們就來了解一下Servlet相關的概念。以助於咱們後面分析Spring MVC的總體框架。前端

一、Servlet容器的加載順序web

相信大多數寫Java Web程序的都接觸過web.xml這個配置文件。下面咱們就來看一下里面最主要的幾個元素。spring

  • context-param : 對於全部Servlet均可用的全局變量,被封裝於ServletContext時。緩存

  • listener : 基於觀察者模式設計的,用於對開發Servlet應用程序提供了一種快捷的手段,可以方便的從另外一個縱向維度控制程序和數據。bash

  • filter : 用於攔截Servlet,能夠在調用Servlet以前或者以後修改Servlet中Request或者Response的值。數據結構

  • servlet : 用於監聽並處理全部的Web請求。在service及其相關方法中對Http請求的處理流程。架構

這裏是Servlet容器中咱們使用到最多的幾個元素。它們在Servlet裏面的初始化順序以下:app

context-param –> listener -> filter -> servlet
複製代碼

具體能夠參見:web.xml 初始化順序框架

二、Servlet的生命週期webapp

瞭解了Servlet裏面的主要元素的加載順序,下面咱們就來看一下Servlet裏面的生命週期:

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
    throwsServletException;, IOException;

    public String getServletInfo();

    public void destroy();

}
複製代碼

咱們能夠看到在Servlet接口裏面有5個方面。

  • init():Servlet的初始化方法,由於Servlet是單例,因此整個系統啓動時,只會運行一次。

  • getServletConfig() : 獲取Servlet配置信息,也就是以前所說的context-param配置的值。

  • service() : 監聽並響應客戶端的請求

  • getServletInfo() : 獲取Servlet信息

  • destroy() : Servlet容器調用,用於銷燬Servlet.

因此,Servlet生命週期分爲三個階段:

1.初始化階段,調用init()方法

2.響應客戶請求階段,調用service()方法

3.終止階段,調用destroy()方法

瞭解了這些Servlet的相關信息,我相信下面再來分析Spring MVC你們就會清楚不少。

三、Spring MVC父子容器及初始化組件

在Spring中咱們都知道,它的核心概念是bean。而Spring容器又是經過BeanFactory來管理bean的。當咱們查看Spring的BeanFactory的體系結構的時候不難發現HierarchicalBeanFactory這個接口。

public interface HierarchicalBeanFactory extends BeanFactory {

    BeanFactory getParentBeanFactory();

    boolean containsLocalBean(String name);`

}
複製代碼

能夠看到它有一個getParentBeanFactory()方法。那麼Spring框架就能夠創建起.器的結構.在使用Spring MVC的時候咱們能夠看出,Spring MVC的體系是基於Spring容器來的。下面咱們就來看一下Spring MVC是如何構建起父子容器的。在此以前咱們先來看一段web.xml配置文件。

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- init root webapplicationcontext -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- init servlet webapplicationcontext -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
複製代碼

相信你們對於以上配置文件不會陌生,以咱們以前的Servlet容器的初始化順序咱們來看一下以上配置文件。

  • 首先,把contextConfigLocation這個參數以及它的值初始化到ServletContext中。

  • 而後,初始化ContextLoaderListener加載contextConfigLocation裏面配置的Spring配置文件,構建起root容器

  • 最後,初始化DispatcherServlet加載它裏面的contextConfigLocation,構造起web容器。再把root容器與web容器創建起父子關係。

下面咱們以源代碼的形式來講明以上的過程:

首先,咱們先來看一下ContextLoaderListener類結構。

能夠看到ContextLoaderListener實現了ServletContextListener這個接口,而這個接口的裏面有2個方法。

  • contextInitialized() : 容器初始化的時候調用。

  • contextDestroyed() : 容器銷燬的時候調用。

因此在Servlet容器初始化的時候會調用contextInitialized方法。

這裏它會調用它的類父ContextLoader的initWebApplicationContext方法。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        try {
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    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);
            }

            return this.context;
        }
    }
複製代碼

3.1 經過反射建立root容器對象

在最開始它會經過反射的方式調用createWebApplicationContext()建立默認的配置在ContextLoader.properties裏面的WebApplicationContext接口實例XmlWebApplicationContext。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // 檢測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() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
複製代碼

由於沒有配置contextClass因此會加載配置在ContextLoader.properties裏面的對象。

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);
            }
        }
    }
複製代碼

3.2 加載相應配置文件,初始化root容器

而後調用configureAndRefreshWebApplicationContext方法獲取到配置在web.xml裏面的configLocationParam值。把它配置成Spring容器的資源位置,最後經過調用wac.refresh()對於整個Spring容器進行加載。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } }

wac.setServletContext(sc);

    // 獲取contextConfigLocation值
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);

    // 加載並刷新Spring容器
    wac.refresh();
}
複製代碼

3.3 把root容器放在ServletContext中

把初始化好的root 容器以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE爲key,放在Servlet容器當中,以備後面使用。

3.4 關聯Servlet WebApplicationContext與 Root WebApplicationContext

咱們知道servlet的規範中,servlet裝載時會調用其init()方法,那麼天然是調用DispatcherServlet的init方法,經過源碼一看,居然沒有,可是並不帶表真的沒有,你會發如今父類的父類中:org.springframework.web.servlet.HttpServletBean有這個方法,以下圖所示:

public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // 把DispatcherServlet裏面的init-param轉化成PropertyValues
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                // 把DispatcherServlet轉換成BeanWrapper,這樣就把DispatcherServlet轉換成了Spring中的Bean管理
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

複製代碼

注意代碼:initServletBean(); 其他的都和加載bean關係並非特別大,跟蹤進去會發I發現這個方法是在類:org.springframework.web.servlet.FrameworkServlet類中(是DispatcherServlet的父類、HttpServletBean的子類),內部經過調用initWebApplicationContext()來初始化一個WebApplicationContext,源碼片斷(篇幅所限,不拷貝全部源碼,僅僅截取片斷)

接下來須要知道的是如何初始化這個context的(按照使用習慣,其實只要獲得了ApplicationContext,就獲得了bean的信息,因此在初始化ApplicationCotext的時候,就已經初始化好了bean的信息,至少至少,它初始化好了bean的路徑,以及描述信息),因此咱們一旦知道ApplicationCotext是怎麼初始化的,就基本知道bean是如何加載的了。

// 獲取到root 容器
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // 設置父容器
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            onRefresh(wac);
        }

        if (this.publishContext) {
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }
複製代碼

最後調用configureAndRefreshWebApplicationContext方法獲取到DispatcherServlet裏面的contextConfigLocation值,建立並這個Servlet WebApplicationContex.

3.5 初始化Spring MVC默認組件

DispatcherServlet的初始化主線的執行體系是順着其繼承結構依次進行的,咱們在以前曾經討論過它的執行次序。因此,只有在FrameworkServlet完成了對於WebApplicationContext和組件的初始化以後,執行權才被正式轉移到DispatcherServlet中。咱們能夠來看看DispatcherServlet此時究竟幹了哪些事:

/** 
 * This implementation calls {@link #initStrategies}. 
 */  
@Override  
protected void onRefresh(ApplicationContext context) {  
    initStrategies(context);  
}  

/** 
 * Initialize the strategy objects that this servlet uses. 
 * <p>May be overridden in subclasses in order to initialize further strategy objects. 
 */  
protected void initStrategies(ApplicationContext context) {  
    initMultipartResolver(context);  
    initLocaleResolver(context);  
    initThemeResolver(context);  
    initHandlerMappings(context);  
    initHandlerAdapters(context);  
    initHandlerExceptionResolvers(context);  
    initRequestToViewNameTranslator(context);  
    initViewResolvers(context);  
    initFlashMapManager(context);  
}  
複製代碼

onRefresh是FrameworkServlet中預留的擴展方法,在DispatcherServlet中作了一個基本實現:initStrategies。咱們粗略一看,很容易就能明白DispatcherServlet到底在這裏幹些什麼了:初始化組件。

讀者或許會問,組件不是已經在WebApplicationContext初始化的時候已經被初始化過了嘛?這裏所謂的組件初始化,指的又是什麼呢?讓咱們來看看其中的一個方法的源碼:

/** 
 * Initialize the MultipartResolver used by this class. 
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 
 * no multipart handling is provided. 
 */  
private void initMultipartResolver(ApplicationContext context) {  
    try {  
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);  
        if (logger.isDebugEnabled()) {  
            logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");  
        }  
    } catch (NoSuchBeanDefinitionException ex) {  
        // Default is no multipart resolver.  
        this.multipartResolver = null;  
        if (logger.isDebugEnabled()) {  
            logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +  
                    "': no multipart request handling provided");  
        }  
    }  
} 
複製代碼

原來,這裏的初始化,指的是DispatcherServlet從容器(WebApplicationContext)中讀取組件的實現類,並緩存於DispatcherServlet內部的過程。還記得咱們以前給出的DispatcherServlet的數據結構嗎?這些位於DispatcherServlet內部的組件實際上只是一些來源於容器緩存實例,不過它們一樣也是DispatcherServlet進行後續操做的基礎。

到了這裏,把Spring MVC與Servlet容器的關係,以及Spring MVC經過Servlet容器的初始化順序建立父子容器,以及根據Servlet的生命週期的init()方法來初始化Spring MVC的默認組件展示了出來。

四、Spring MVC的處理HTTP請求 Spring MVC要處理http請求,它首先要解決3個問題。

  • URL到框架的映射。

  • http請求參數綁定

  • http響應的生成和輸出

4.1 URL到框架的映射

在Spring MVC咱們只須要建立一個對象在上面標註@Controller註解。而後建立一個方法標註@RequestMapping而後這個方法就可以處理對應的url了。具體解析能夠參看 – Spring MVC @RequestMapping

4.2 http請求參數綁定

對於http請求參數的綁定,在Spring MVC生成URL到框架的映射關係時會建立一個HandlerMethod對象。這個對象裏面包括了這個url對應Spring對應標註了@RequestMapping方法的MethodParameter(方法參數)。經過HttpServletRequest獲取到請求參數,而後再經過HandlerMethodArgumentResolver接口把請求參數綁定到方法參數中。

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}
複製代碼

具體能夠參考 – Spring MVC DataBinder

4.3 http響應的生成和輸出

在整個系統運行的過程當中處於Servlet的service()方法都處於偵聽模式,偵聽並處理全部的Web請求。所以,在service及其相關方法中,咱們看到的則是對Http請求的處理流程。

在Spring MVC容器在初始的時候建立了URL到框架的映射,當前端請求的到來的時候。Spring MVC會獲取到對應的HandlerMethod對象,HandlerMethod包括裏面的屬性。

public class HandlerMethod {

    /** Logger that is available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    private final Object bean;

    private final BeanFactory beanFactory;

    private final Class<?> beanType;

    private final Method method;

    private final Method bridgedMethod;

    private final MethodParameter[] parameters;

    private HttpStatus responseStatus;

    private String responseStatusReason;

    private HandlerMethod resolvedFromHandlerMethod;
}
複製代碼

其中最主要的屬性是如下三個:

  • bean : @Controller對象實例。

  • method :@Controller對象實例標註了@RequestMapping的方法對象,也就是處理對應url的方法

  • parameters : @RequestMapping裏面的方法參數。

經過http請求參數綁定,把Request請求的參數綁定解析成對應的方法參數。而後經過反射:

method.invoke(bean, paramters);
複製代碼

完成對http請求的整個響應。 具體能夠參見 – Spring MVC DispatcherServlet

爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!

合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!

To-陌霖Java架構

分享互聯網最新文章 關注互聯網最新發展

相關文章
相關標籤/搜索