相信你們都可以在上網上看到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架構
分享互聯網最新文章 關注互聯網最新發展