Spring原始碼之springMVC
web.xml
它的作用是配置初始化資訊,如web頁面、servlet、servlet-mapping、filter、listener、啟動載入級別等。
SpringMVC 通過servlet攔截所有的URL來達到控制的目的,所以它必須要有web.xml
比較關鍵的配置是:
-
contextConfigLocation 配置spring配置檔案地址
-
DispatcherServlet 前端控制器
程式入口
ContextLoaderListener.initWebApplicationContext().createWebApplicationContext()
載入ApplicationContext 並注入Servlet容器
先判斷contextClass 屬性是否配置,否則載入預設的: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 { // 預設值:org.springframework.web.context.support.XmlWebApplicationContext 可繼承它修改容器配置 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); } } }
ContextLoader 中的static 靜態語句塊可以知道載入的配置檔案是: ContextLoader.properties
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { // DEFAULT_STRATEGIES_PATH = ContextLoader.properties ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
以上為容器載入階段,詳細細節之前章節已經講述,不再贅述
ContextLoader 的 initWebApplicationContext 方法中,發現如下程式碼:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
servlet容器持有該引用
servlet
Servlet是按照servlet規範使用Java編寫的程式,基於HTTP協議執行在伺服器端,它的宣告週期分為:初始化、執行、銷燬。
初始化
- servlet容器載入servlet類,把它的.class位元組碼檔案讀取到記憶體中
- servlet容器建立一個ServletConfig物件,它包含該servlet的初始化配置資訊
- servlet容器建立一個 servlet 物件
- servlet容器呼叫servlet物件的init() 方法進行初始化
執行階段
- servlet容器接收到請求時,會根據請求建立一個servletRequest(請求資訊) 物件和servletResponse(封裝返回資訊) 物件,
呼叫service方法並處理請求,通過servletResponse相應請求後銷燬這兩個物件。
銷燬階段
- Web應用終止,servlet容器呼叫servlet物件的destory方法,然後銷燬servlet物件以及相關的 servletConfig物件。
DispatcherServlet
它是SpringMVC的核心, 是servlet的一個實現類
初始化
在它的父類HttpServletBean中找到了init方法的呼叫
該方法只是初始的配置資訊載入
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
// 解析 <init-param> 並驗證
// requiredProperties 配置必須要的引數,否則丟擲異常
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// servlet 轉為 BeanWrapper 從而可以像spring那樣 對 init-param 的值進行注入
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");
}
}
繼續,在父類 FrameworkServlet 中找到了鉤子函式:initServletBean方法的具體實現:
有模板方法模式那味兒了,很遺憾這裡還是準備工作
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 對WebApplicationContext進一步初始化和補充
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
然後 initWebApplicationContext() 方法對容器進一步初始化和補充
protected WebApplicationContext initWebApplicationContext() {// 對WebApplicationContext進一步初始化和補充
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());// 從 容器 servletContext 中獲取
WebApplicationContext wac = null;
if (this.webApplicationContext != null) { // webApplicationContext 是否在建構函式中被注入 (未解析過) new DispatcherServlet()->.super(WebApplicationContext)
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) {
// WebApplicationContext 是否被 contextAttribute 屬性注入
wac = findWebApplicationContext();
}
if (wac == null) {
// 既無構造器注入,也無contextAttribute屬性注入,那麼通過初始化的 WebApplicationContext 構造新的容器
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);// 載入配置 鉤子,由子類 DispatcherServlet 實現,用於 Spring Web功能的 相關解析器的初始化
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
跟進 方法configureAndRefreshWebApplicationContext() 發現了我們的老朋友 refresh() 方法,是不是很眼熟?
ApplicationContext 容器載入過程中 它近乎是一切的起點了,檢視預設的容器類XmlWebApplicationContext 的類圖不難證實這點
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
......
.........
wac.refresh();// ConfigurableApplicationContext.refresh() <- AbstractApplicationContext.refresh() <- XmlWebApplicationContext
}
然後看initWebApplicationContext()方法內呼叫的,onRefresh()方法
FrameworkServlet 類中找到的onRefresh() 又是空方法,不解釋,鉤子函式它又來了,最後回到DispatcherServlet類,發現了該方法的具體定義:
該方法的主要功能是重新整理Spring在Web功能實現中所必須使用的全域性變數的初始化
從配置檔案:DispatcherServlet.properties 可得知部分全域性變數所使用的預設值
@Override
protected void onRefresh(ApplicationContext context) {// 用於 Spring Web功能的 相關解析器的初始化
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);// 國際化配置? 基於:url session cookie 支援國際化
initThemeResolver(context);// Theme主題控制網頁風格
// 可以有多個HandleMapping,根據優先順序訪問,直到獲取到可用的Handle 為止 (Ordered 介面控制)
initHandlerMappings(context);// 處理客戶端發起的Request請求,根據WebApplicationContext的配置來,回傳給 DispatcherServler 對應的Controller
// DispatcherServlet 通過處理器對映(HandleMapping)配置,得到處理器(Handle),之後會輪詢處理器(Handle)的<配適器模組>
// 並查詢能夠處理當前HTTP請求的處理器(Handle),的配適器實現(Adapter)
// org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter 對Http請求處理器進行配適 OtherClass <- HttpAdapter <- HttpHandle
// org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 將Http請求配飾到一個Controller 的實現進行處理
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 用於執行Controller方法的配適器,對註解方式的支援
initHandlerAdapters(context);// 配適器
initHandlerExceptionResolvers(context);// 異常處理
initRequestToViewNameTranslator(context);// 當Controller沒有返回任何View物件或者邏輯檢視名稱,並在該方法中沒有向response的輸出流裡面寫任何資料,那麼spring會使用約定方式提供一個邏輯檢視名稱。
// resolverViewName 方法 根據 viewName建立合適的View 實現
initViewResolvers(context);// Controller 計算結束後將結果封裝到ModleAndView,DispatcherServlet 會根據ModleAndView 選擇合適的檢視進行渲染
initFlashMapManager(context);// SpringMVC Flash attributes 提供了屬性儲存功能,可夠重定向時其它請求使用
}
DispatcherServlet 的邏輯處理
看 HttpServlet 類的結構 看關鍵的doGet和doPost,在FrameworkServlet類中找到了如下方法實現:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
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 {
// 準備工作 具體實現由 DispatcherServlet 提供
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);// 設定提取的請求引數,用於重定向
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
publishRequestHandledEvent(request, response, startTime, failureCause);// 事件通知
}
}
忽略準備工作:doService().doDispatch(HttpServletRequest request, HttpServletResponse response)
如下為核心程式碼邏輯,之前提到的全部變數配置將登上舞臺了
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 {
// 檢查全域性變數
// 如果請求型別為:multipartContent 將 HttpServletRequest 轉為 MultipartHttpServletRequest (包裝器 ? 策略模式)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);// 檢查原型別 包裝 / 代理
// Determine handler for the current request.
// 根據URL 匹配
mappedHandler = getHandler(processedRequest);// 按優先順序從各個HandleMapping 中獲取Handle
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 根據handle 獲取匹配的配適器 Adapter
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {// last-modified(快取處理機制) 請求頭處理 <最後修改時間>
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 未變化? 最後修改時間未變? 過濾重複請求???
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 呼叫攔截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 啟用Handle 並返回檢視 (由配適器的 handle方法完成 )
// 檢視配置檔案DispatcherServlet.properties 可以知道 HandlerAdapter ha 的具體實現類,跟蹤handle方法:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);// 檢視名稱處理
// 呼叫所有攔截器的postHandle 方法,如果存在的話
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 完成處理後,啟用觸發器
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 完成處理後,啟用觸發器
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
檢視配置檔案 DispatcherServlet.properties 可以知道 HandlerAdapter ha 的具體實現類,跟蹤handle方法:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
- getHandle()
AbstractHandlerMapping.getHandel().[ AbstractUrlHandlerMapping.getHandlerInternal() ]
根據請求url獲取handel
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 擷取 url
Object handler = lookupHandler(lookupPath, request);// 根據 url 獲取 handle
if (handler == null) {// 獲取到的解析器為空
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {// 請求路徑僅為根路徑: 則使用RootHandle處理
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();// 否則設定預設的 Handle
}
if (rawHandler != null) { // 預設 Handle 可能為空
// Bean name or resolved handler?
if (rawHandler instanceof String) { // 若查詢的 Handle 型別為String 則為beanName 否則為 Handle 本身
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);// 從容器中獲取
}
validateHandler(rawHandler, request);// 校驗鉤子函式
// 初始化 Handle ??? HandlerExecutionChain 對 Handle 進行包裝
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
2.getHandlerAdapter
根據handel獲取配飾器Adapter
SimpleControllerHandlerAdapter.suport
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
看到controller主要解析就完成了,剩下的事情就是處理請求,並繫結檢視放回,以及當發生異常時對異常檢視進行處理。