友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
Spring MVC 之初體驗
環境搭建
在 IDEA 中新建一個 web 專案,用 Maven 管理專案的話,在 pom.xml 中加入 Spring MVC 和 Servlet 依賴即可。
org.springframework
Spring MVC 簡單配置
-
在 web.xml 中配置 Servlet
-
建立 Spring MVC 的 xml 配置檔案
-
建立 Controller 和 View
1、web.xml
spring
org.springframework.web.context.ContextLoaderListener
2、spring-servlet.xml
xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context href="http://www.springframework.org/schema/context/spring-context-3.0.xsd">http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
3、Controller
package controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import entity.User;
@Controller //類似Struts的Action
public class TestController {
@RequestMapping("/test/login.do") // 請求url地址對映,類似Struts的action-mapping
public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {
// @RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)
// @RequestParam可簡寫為:@RequestParam("username")
if (!"admin".equals(username) || !"admin".equals(password)) {
return "loginError"; // 跳轉頁面路徑(預設為轉發),該路徑不需要包含spring-servlet配置檔案中配置的字首和字尾
}
return "loginSuccess";
}
@RequestMapping("/test/login2.do")
public ModelAndView testLogin2(String username, String password, int age){
// request和response不必非要出現在方法中,如果用不上的話可以去掉
// 引數的名稱是與頁面控制元件的name相匹配,引數型別會自動被轉換
if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {
return new ModelAndView("loginError"); // 手動實體化ModelAndView完成跳轉頁面(轉發),效果等同於上面的方法傳回字串
}
return new ModelAndView(new RedirectView("../index.jsp")); // 採用重定向方式跳轉頁面
// 重定向還有一種簡單寫法
// return new ModelAndView("redirect:../index.jsp");
}
@RequestMapping("/test/login3.do")
public ModelAndView testLogin3(User user) {
// 同樣支援引數為表單物件,類似於Struts的ActionForm,User不需要任何配置,直接寫即可
String username = user.getUsername();
String password = user.getPassword();
int age = user.getAge();
if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {
return new ModelAndView("loginError");
}
return new ModelAndView("loginSuccess");
}
@Resource(name = "loginService") // 獲取applicationContext.xml中bean的id為loginService的,並註入
private LoginService loginService; //等價於spring傳統註入方式寫get和set方法,這樣的好處是簡潔工整,省去了不必要得程式碼
@RequestMapping("/test/login4.do")
public String testLogin4(User user) {
if (loginService.login(user) == false) {
return "loginError";
}
return "loginSuccess";
}
}
@RequestMapping 可以寫在方法上,也可以寫在類上,上面程式碼方法上的 RequestMapping 都含有 /test
, 那麼我們就可以將其抽出直接寫在類上,那麼方法裡面就不需要寫 /test
了。
如下即可:
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/login.do") // 請求url地址對映,類似Struts的action-mapping
public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {
// @RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)
// @RequestParam可簡寫為:@RequestParam("username")
if (!"admin".equals(username) || !"admin".equals(password)) {
return "loginError"; // 跳轉頁面路徑(預設為轉發),該路徑不需要包含spring-servlet配置檔案中配置的字首和字尾
}
return "loginSuccess";
}
//省略其他的
}
上面的程式碼方法的引數中可以看到有一個 @RequestParam
註解,其實還有 @PathVariable
。這兩個的區別是啥呢?
-
@PathVariable
標記在方法的引數上,利用它標記的引數可以利用請求路徑傳值。 -
@RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)
看如下例子:
@RequestMapping("/user/{userId}") // 請求url地址對映
public String userinfo(Model model, @PathVariable("userId") int userId, HttpSession session) {
System.out.println("進入 userinfo 頁面");
//判斷是否有使用者登入
User user1 = (User) session.getAttribute("user");
if (user1 == null) {
return "login";
}
User user = userService.selectUserById(userId);
model.addAttribute("user", user);
return "userinfo";
}
上面例子中如果瀏覽器請求的是 /user/1
的時候,就表示此時的使用者 id 為 1,此時就會先從 session 中查詢是否有 “user” 屬性,如果有的話,就代表使用者此時處於登入的狀態,如果沒有的話,就會讓使用者傳回到登入頁面,這種機制在各種網站經常會使用的,然後根據這個 id = 1 ,去查詢使用者的資訊,然後把查詢的 “user” 放在 model 中,然後傳回用戶詳情頁面,最後在頁面中用 $!{user.name}
獲取使用者的名字,同樣的方式可以獲取使用者的其他資訊,把所有的使用者詳情資訊展示出來。
建立 Spring MVC 之器
Spring MVC 核心 Servlet 架構圖如下:
Java 中常用的 Servlet 我在另外一篇文章寫的很清楚了,有興趣的請看:透過原始碼詳解 Servlet ,這裡我就不再解釋了。
這裡主要講 Spring 中的 HttpServletBean、FrameworkServlet、DispatcherServlet 這三個類的建立過程。
透過上面的圖,可以看到這三個類直接實現三個介面:EnvironmentCapable、EnvironmentAware、ApplicationContextAware。下麵我們直接看下這三個介面的內部是怎樣寫的。
EnvironmentCapable.java
public interface EnvironmentCapable {
//傳回元件的環境,可能傳回 null 或者預設環境
@Nullable
Environment getEnvironment();
}
EnvironmentAware.java
public interface EnvironmentAware extends Aware {
//設定元件的執行環境
void setEnvironment(Environment environment);
}
ApplicationContextAware.java
public interface ApplicationContextAware extends Aware {
//設定執行物件的應用背景關係
//當類實現這個介面後,這個類可以獲取ApplicationContext中所有的bean,也就是說這個類可以直接獲取Spring配置檔案中所有有取用到的bean物件
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
怎麼使用這個這個介面呢?
參考文章:org.springframework.context.ApplicationContextAware使用理解
HttpServletBean
這裡就直接看其中最重要的 init() 方法的程式碼了:
/**
* 將配置引數對映到此servlet的bean屬性,並呼叫子類初始化。
* 如果 bean 配置不合法(或者需要的引數丟失)或者子類初始化發生錯誤,那麼就會丟擲 ServletException 異常
*/
@Override
public final void init() throws ServletException {
//日誌程式碼刪除了
// 從init引數設定bean屬性。
//獲得web.xml中的contextConfigLocation配置屬性,就是spring MVC的配置檔案
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//獲取伺服器的各種資訊
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法,可以在子類中呼叫,做一些初始化工作,bw代表DispatcherServelt
initBeanWrapper(bw);
//將配置的初始化值設定到DispatcherServlet中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
//日誌程式碼
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//模板方法,子類初始化的入口方法
initServletBean();
//日誌程式碼刪除了
}
FrameworkServlet
其中重要方法如下:裡面也就兩句關鍵程式碼,日誌程式碼我直接刪掉了
protected final void initServletBean() throws ServletException {
//日誌程式碼刪除了
long startTime = System.currentTimeMillis();
//就是 try 陳述句裡面有兩句關鍵程式碼
try {
//初始化 webApplicationContext
this.webApplicationContext = initWebApplicationContext();
//模板方法,
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
//日誌程式碼刪除了
}
再來看看上面程式碼中呼叫的 initWebApplicationContext() 方法
protected WebApplicationContext initWebApplicationContext() {
//獲取 rootContext
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()) {
// 如果背景關係尚未掃清 -> 提供諸如設定父背景關係,設定應用程式背景關係ID等服務
if (cwac.getParent() == null) {
// 背景關係實體被註入沒有顯式的父類 -> 將根應用程式背景關係(如果有的話可能為null)設定為父級
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 當 WebApplicationContext 已經存在 ServletContext 中時,透過配置在 servlet 中的 ContextAttribute 引數獲取
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果 WebApplicationContext 還沒有建立,則建立一個
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 當 ContextRefreshedEvent 事件沒有觸發時呼叫此方法,模板方法,可以在子類重寫
onRefresh(wac);
}
if (this.publishContext) {
// 將 ApplicationContext 儲存到 ServletContext 中去
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;
}
initWebApplicationContext 方法做了三件事:
-
獲取 Spring 的根容器 rootContext
-
設定 webApplicationContext 並根據情況呼叫 onRefresh 方法
-
將 webApplicationContext 設定到 ServletContext 中
這裡在講講上面程式碼中的 wac == null 的幾種情況:
1)、當 WebApplicationContext 已經存在 ServletContext 中時,透過配置在 servlet 中的 ContextAttribute 引數獲取,呼叫的是 findWebApplicationContext() 方法
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
2)、如果 WebApplicationContext 還沒有建立,呼叫的是 createWebApplicationContext 方法
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//獲取建立型別
Class> contextClass = getContextClass();
//刪除了列印日誌程式碼
//檢查建立型別
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());
wac.setParent(parent);
//並設定的 contextConfigLocation 引數傳給 wac,預設是 WEB-INFO/[ServletName]-Servlet.xml
wac.setConfigLocation(getContextConfigLocation());
//呼叫的是下麵的方法
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
裡面還有 doXXX() 方法,大家感興趣的可以去看看。
DispatcherServlet
DispatcherServlet 繼承自 FrameworkServlet,onRefresh 方法是 DispatcherServlet 的入口方法,在 initStrategies 方法中呼叫了 9 個初始化的方法。
這裡分析其中一個初始化方法:initLocaleResolver() 方法
private void initLocaleResolver(ApplicationContext context) {
try {
//在 context 中獲取
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
//刪除了列印日誌的程式碼
}
catch (NoSuchBeanDefinitionException ex) {
//使用預設的策略
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
//刪除了列印日誌的程式碼
}
}
檢視預設策略程式碼:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
//呼叫 getDefaultStrategies 方法
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
}
return strategies.get(0);
}
/**
* Create a List of default strategy objects for the given strategy interface.
*
The default implementation uses the "DispatcherServlet.properties" file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context's BeanFactory.
*/
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
//根據策略介面的名字從 defaultStrategies 獲取所需策略的型別
String value = defaultStrategies.getProperty(key);
if (value != null) {
//如果有多個預設值的話,就以逗號分隔為陣列
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
//按獲取到的型別初始化策略
for (String className : classNames) {
try {
Class> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
其他幾個方法大概也類似,我就不再寫了。
小結
主要講了 Spring MVC 自身建立過程,分析了 Spring MVC 中 Servlet 的三個層次:HttpServletBean、FrameworkServlet 和 DispatcherServlet。HttpServletBean 繼承自 Java 的 HttpServlet,其作用是將配置的引數設定到相應的屬性上;FrameworkServlet 初始化了 WebApplicationContext;DispatcherServlet 初始化了自身的 9 個元件。
Spring MVC 之用
分析 Spring MVC 是怎麼處理請求的。首先分析 HttpServletBean、FrameworkServlet 和 DispatcherServlet 這三個 Servlet 的處理過程,最後分析 doDispatcher 的結構。
HttpServletBean
參與了建立工作,並沒有涉及請求的處理。
FrameworkServlet
在類中的 service() 、doGet()、doPost()、doPut()、doDelete()、doOptions()、doTrace() 這些方法中可以看到都呼叫了一個共同的方法 processRequest() ,它是類在處理請求中最核心的方法。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//獲取 LocaleContextHolder 中原來儲存的 LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//獲取當前請求的 LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//獲取 RequestContextHolder 中原來儲存的 RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//獲取當前請求的 ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//將當前請求的 LocaleContext 和 ServletRequestAttributes 設定到 LocaleContextHolder 和 RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
try {
//實際處理請求的入口,這是一個模板方法,在 Dispatcher 類中才有具體實現
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 {
//將 previousLocaleContext,previousAttributes 恢復到 LocaleContextHolder 和 RequestContextHolder 中
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//刪除了日誌列印程式碼
//釋出了一個 ServletRequestHandledEvent 型別的訊息
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
DispatcherServlet
上一章中其實還沒把該類講清楚,在這個類中,裡面的智行處理的入口方法應該是 doService 方法,方法裡面呼叫了 doDispatch 進行具體的處理,在呼叫 doDispatch 方法之前 doService 做了一些事情:首先判斷是不是 include 請求,如果是則對 request 的 Attribute 做個快照備份,等 doDispatcher 處理完之後(如果不是非同步呼叫且未完成)進行還原 ,在做完快照後又對 request 設定了一些屬性。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 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<>();
Enumeration> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)){
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 方法
doDispatch(request, response);
}finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doDispatch() 方法:
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. 根據 request 找到 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.根據 Handler 找到對應的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified essay-header, if supported by the handler.
//處理 GET 、 HEAD 請求的 LastModified
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;
}
}
//執行相應的 Interceptor 的 preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. HandlerAdapter 使用 Handler 處理請求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果需要非同步處理,直接傳回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//當 view 為空時,根據 request 設定預設 view
applyDefaultViewName(processedRequest, mv);
//執行相應 Interceptor 的 postHandler
mappedHandler.applyPostHandle(processedRequest, response, mv);
}catch (Exception ex) {
dispatchException = ex;
}catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//呼叫 processDispatchResult 方法處理上面處理之後的結果(包括處理異常,渲染頁面,發出完成通知觸發 Interceptor 的 afterCompletion)
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()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}else {
// Clean up any resources used by a multipart request. 刪除上傳請求的資源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
Handler,HandlerMapping,HandlerAdapter 三個區別:
-
Handler:處理器,對應 MVC 的 C層,也就是 Controller 層,具體表現形式有很多種,可以是類,方法,它的型別是 Object,只要可以處理實際請求就可以是 Handler。
-
HandlerMapping:用來查詢 Handler 的。
-
HandlerAdapter :Handler 配接器,
另外 View 和 ViewResolver 的原理與 Handler 和 HandlerMapping 的原理類似。
小結
本章分析了 Spring MVC 的請求處理的過程。
相關文章
看透 Spring MVC 原始碼分析與實踐 —— 網站基礎知識