摘要:通過(guò)上面的圖,可以看到這三個(gè)類(lèi)直接實(shí)現(xiàn)三個(gè)接口。如果配置不合法或者需要的參數(shù)丟失或者子類(lèi)初始化發(fā)生錯(cuò)誤,那么就會(huì)拋出異常日志代碼刪除了從參數(shù)設(shè)置屬性。參與了創(chuàng)建工作,并沒(méi)有涉及請(qǐng)求的處理。小結(jié)本章分析了的請(qǐng)求處理的過(guò)程。
Spring MVC
在 IDEA 中新建一個(gè) web 項(xiàng)目,用 Maven 管理項(xiàng)目的話,在 pom.xml 中加入 Spring MVC 和 Servlet 依賴(lài)即可。
Spring MVC 簡(jiǎn)單配置org.springframework spring-webmvc 4.3.9.RELEASE javax.servlet javax.servlet-api 3.1.0 provided
在 web.xml 中配置 Servlet
創(chuàng)建 Spring MVC 的 xml 配置文件
創(chuàng)建 Controller 和 View
1、web.xml
spring org.springframework.web.servlet.DispatcherServlet 1 spring *.do org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:config/applicationContext.xml
2、spring-servlet.xml
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 //類(lèi)似Struts的Action
public class TestController {
@RequestMapping("/test/login.do") // 請(qǐng)求url地址映射,類(lèi)似Struts的action-mapping
public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {
// @RequestParam是指請(qǐng)求url地址映射中必須含有的參數(shù)(除非屬性 required=false, 默認(rèn)為 true)
// @RequestParam可簡(jiǎn)寫(xiě)為:@RequestParam("username")
if (!"admin".equals(username) || !"admin".equals(password)) {
return "loginError"; // 跳轉(zhuǎn)頁(yè)面路徑(默認(rèn)為轉(zhuǎn)發(fā)),該路徑不需要包含spring-servlet配置文件中配置的前綴和后綴
}
return "loginSuccess";
}
@RequestMapping("/test/login2.do")
public ModelAndView testLogin2(String username, String password, int age){
// request和response不必非要出現(xiàn)在方法中,如果用不上的話可以去掉
// 參數(shù)的名稱(chēng)是與頁(yè)面控件的name相匹配,參數(shù)類(lèi)型會(huì)自動(dòng)被轉(zhuǎn)換
if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {
return new ModelAndView("loginError"); // 手動(dòng)實(shí)例化ModelAndView完成跳轉(zhuǎn)頁(yè)面(轉(zhuǎn)發(fā)),效果等同于上面的方法返回字符串
}
return new ModelAndView(new RedirectView("../index.jsp")); // 采用重定向方式跳轉(zhuǎn)頁(yè)面
// 重定向還有一種簡(jiǎn)單寫(xiě)法
// return new ModelAndView("redirect:../index.jsp");
}
@RequestMapping("/test/login3.do")
public ModelAndView testLogin3(User user) {
// 同樣支持參數(shù)為表單對(duì)象,類(lèi)似于Struts的ActionForm,User不需要任何配置,直接寫(xiě)即可
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; //等價(jià)于spring傳統(tǒng)注入方式寫(xiě)get和set方法,這樣的好處是簡(jiǎn)潔工整,省去了不必要得代碼
@RequestMapping("/test/login4.do")
public String testLogin4(User user) {
if (loginService.login(user) == false) {
return "loginError";
}
return "loginSuccess";
}
}
@RequestMapping 可以寫(xiě)在方法上,也可以寫(xiě)在類(lèi)上,上面代碼方法上的 RequestMapping 都含有 /test , 那么我們就可以將其抽出直接寫(xiě)在類(lèi)上,那么方法里面就不需要寫(xiě) /test 了。
如下即可:
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/login.do") // 請(qǐng)求url地址映射,類(lèi)似Struts的action-mapping
public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {
// @RequestParam是指請(qǐng)求url地址映射中必須含有的參數(shù)(除非屬性 required=false, 默認(rèn)為 true)
// @RequestParam可簡(jiǎn)寫(xiě)為:@RequestParam("username")
if (!"admin".equals(username) || !"admin".equals(password)) {
return "loginError"; // 跳轉(zhuǎn)頁(yè)面路徑(默認(rèn)為轉(zhuǎn)發(fā)),該路徑不需要包含spring-servlet配置文件中配置的前綴和后綴
}
return "loginSuccess";
}
//省略其他的
}
上面的代碼方法的參數(shù)中可以看到有一個(gè) @RequestParam 注解,其實(shí)還有 @PathVariable 。這兩個(gè)的區(qū)別是啥呢?
@PathVariable 標(biāo)記在方法的參數(shù)上,利用它標(biāo)記的參數(shù)可以利用請(qǐng)求路徑傳值。
@RequestParam是指請(qǐng)求url地址映射中必須含有的參數(shù)(除非屬性 required=false, 默認(rèn)為 true)
看如下例子:
@RequestMapping("/user/{userId}") // 請(qǐng)求url地址映射
public String userinfo(Model model, @PathVariable("userId") int userId, HttpSession session) {
System.out.println("進(jìn)入 userinfo 頁(yè)面");
//判斷是否有用戶(hù)登錄
User user1 = (User) session.getAttribute("user");
if (user1 == null) {
return "login";
}
User user = userService.selectUserById(userId);
model.addAttribute("user", user);
return "userinfo";
}
上面例子中如果瀏覽器請(qǐng)求的是 /user/1 的時(shí)候,就表示此時(shí)的用戶(hù) id 為 1,此時(shí)就會(huì)先從 session 中查找是否有 “user” 屬性,如果有的話,就代表用戶(hù)此時(shí)處于登錄的狀態(tài),如果沒(méi)有的話,就會(huì)讓用戶(hù)返回到登錄頁(yè)面,這種機(jī)制在各種網(wǎng)站經(jīng)常會(huì)使用的,然后根據(jù)這個(gè) id = 1 ,去查找用戶(hù)的信息,然后把查找的 “user” 放在 model 中,然后返回用戶(hù)詳情頁(yè)面,最后在頁(yè)面中用 $!{user.name} 獲取用戶(hù)的名字,同樣的方式可以獲取用戶(hù)的其他信息,把所有的用戶(hù)詳情信息展示出來(lái)。
創(chuàng)建 Spring MVC 之器Spring MVC 核心 Servlet 架構(gòu)圖如下:
Java 中常用的 Servlet 我在另外一篇文章寫(xiě)的很清楚了,有興趣的請(qǐng)看:通過(guò)源碼詳解 Servlet ,這里我就不再解釋了。
這里主要講 Spring 中的 HttpServletBean、FrameworkServlet、DispatcherServlet 這三個(gè)類(lèi)的創(chuàng)建過(guò)程。
通過(guò)上面的圖,可以看到這三個(gè)類(lèi)直接實(shí)現(xiàn)三個(gè)接口:EnvironmentCapable、EnvironmentAware、ApplicationContextAware。下面我們直接看下這三個(gè)接口的內(nèi)部是怎樣寫(xiě)的。
EnvironmentCapable.java
public interface EnvironmentCapable {
//返回組件的環(huán)境,可能返回 null 或者默認(rèn)環(huán)境
@Nullable
Environment getEnvironment();
}
EnvironmentAware.java
public interface EnvironmentAware extends Aware {
//設(shè)置組件的運(yùn)行環(huán)境
void setEnvironment(Environment environment);
}
ApplicationContextAware.java
public interface ApplicationContextAware extends Aware {
//設(shè)置運(yùn)行對(duì)象的應(yīng)用上下文
//當(dāng)類(lèi)實(shí)現(xiàn)這個(gè)接口后,這個(gè)類(lèi)可以獲取ApplicationContext中所有的bean,也就是說(shuō)這個(gè)類(lèi)可以直接獲取Spring配置文件中所有有引用到的bean對(duì)象
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
怎么使用這個(gè)這個(gè)接口呢?
參考文章:org.springframework.context.ApplicationContextAware使用理解
HttpServletBean
這里就直接看其中最重要的 init() 方法的代碼了:
/**
* 將配置參數(shù)映射到此servlet的bean屬性,并調(diào)用子類(lèi)初始化。
* 如果 bean 配置不合法(或者需要的參數(shù)丟失)或者子類(lèi)初始化發(fā)生錯(cuò)誤,那么就會(huì)拋出 ServletException 異常
*/
@Override
public final void init() throws ServletException {
//日志代碼刪除了
// 從init參數(shù)設(shè)置bean屬性。
//獲得web.xml中的contextConfigLocation配置屬性,就是spring MVC的配置文件
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//獲取服務(wù)器的各種信息
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法,可以在子類(lèi)中調(diào)用,做一些初始化工作,bw代表DispatcherServelt
initBeanWrapper(bw);
//將配置的初始化值設(shè)置到DispatcherServlet中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
//日志代碼
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//模板方法,子類(lèi)初始化的入口方法
initServletBean();
//日志代碼刪除了
}
FrameworkServlet
其中重要方法如下:里面也就兩句關(guān)鍵代碼,日志代碼我直接刪掉了
protected final void initServletBean() throws ServletException {
//日志代碼刪除了
long startTime = System.currentTimeMillis();
//就是 try 語(yǔ)句里面有兩句關(guān)鍵代碼
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;
}
//日志代碼刪除了
}
再來(lái)看看上面代碼中調(diào)用的 initWebApplicationContext() 方法
protected WebApplicationContext initWebApplicationContext() {
//獲取 rootContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 上下文實(shí)例在構(gòu)造時(shí)注入 - >使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 如果上下文尚未刷新 -> 提供諸如設(shè)置父上下文,設(shè)置應(yīng)用程序上下文ID等服務(wù)
if (cwac.getParent() == null) {
// 上下文實(shí)例被注入沒(méi)有顯式的父類(lèi) -> 將根應(yīng)用程序上下文(如果有的話可能為null)設(shè)置為父級(jí)
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 當(dāng) WebApplicationContext 已經(jīng)存在 ServletContext 中時(shí),通過(guò)配置在 servlet 中的 ContextAttribute 參數(shù)獲取
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果 WebApplicationContext 還沒(méi)有創(chuàng)建,則創(chuàng)建一個(gè)
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 當(dāng) ContextRefreshedEvent 事件沒(méi)有觸發(fā)時(shí)調(diào)用此方法,模板方法,可以在子類(lèi)重寫(xiě)
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
設(shè)置 webApplicationContext 并根據(jù)情況調(diào)用 onRefresh 方法
將 webApplicationContext 設(shè)置到 ServletContext 中
這里在講講上面代碼中的 wac == null 的幾種情況:
1)、當(dāng) WebApplicationContext 已經(jīng)存在 ServletContext 中時(shí),通過(guò)配置在 servlet 中的 ContextAttribute 參數(shù)獲取,調(diào)用的是 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 還沒(méi)有創(chuàng)建,調(diào)用的是 createWebApplicationContext 方法
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//獲取創(chuàng)建類(lèi)型
Class> contextClass = getContextClass();
//刪除了打印日志代碼
//檢查創(chuàng)建類(lèi)型
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");
}
//具體創(chuàng)建
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
//并設(shè)置的 contextConfigLocation 參數(shù)傳給 wac,默認(rèn)是 WEB-INFO/[ServletName]-Servlet.xml
wac.setConfigLocation(getContextConfigLocation());
//調(diào)用的是下面的方法
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 方法中調(diào)用了 9 個(gè)初始化的方法。
這里分析其中一個(gè)初始化方法:initLocaleResolver() 方法
private void initLocaleResolver(ApplicationContext context) {
try {
//在 context 中獲取
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
//刪除了打印日志的代碼
}
catch (NoSuchBeanDefinitionException ex) {
//使用默認(rèn)的策略
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
//刪除了打印日志的代碼
}
}
查看默認(rèn)策略代碼:
protectedT getDefaultStrategy(ApplicationContext context, Class strategyInterface) { //調(diào)用 getDefaultStrategies 方法 List 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
List getDefaultStrategies(ApplicationContext context, Class strategyInterface) { String key = strategyInterface.getName(); //根據(jù)策略接口的名字從 defaultStrategies 獲取所需策略的類(lèi)型 String value = defaultStrategies.getProperty(key); if (value != null) { //如果有多個(gè)默認(rèn)值的話,就以逗號(hào)分隔為數(shù)組 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List strategies = new ArrayList<>(classNames.length); //按獲取到的類(lèi)型初始化策略 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<>(); } }
其他幾個(gè)方法大概也類(lèi)似,我就不再寫(xiě)了。
小結(jié)主要講了 Spring MVC 自身創(chuàng)建過(guò)程,分析了 Spring MVC 中 Servlet 的三個(gè)層次:HttpServletBean、FrameworkServlet 和 DispatcherServlet。HttpServletBean 繼承自 Java 的 HttpServlet,其作用是將配置的參數(shù)設(shè)置到相應(yīng)的屬性上;FrameworkServlet 初始化了 WebApplicationContext;DispatcherServlet 初始化了自身的 9 個(gè)組件。
Spring MVC 之用分析 Spring MVC 是怎么處理請(qǐng)求的。首先分析 HttpServletBean、FrameworkServlet 和 DispatcherServlet 這三個(gè) Servlet 的處理過(guò)程,最后分析 doDispatcher 的結(jié)構(gòu)。
HttpServletBean
參與了創(chuàng)建工作,并沒(méi)有涉及請(qǐng)求的處理。
FrameworkServlet
在類(lèi)中的 service() 、doGet()、doPost()、doPut()、doDelete()、doOptions()、doTrace() 這些方法中可以看到都調(diào)用了一個(gè)共同的方法 processRequest() ,它是類(lèi)在處理請(qǐng)求中最核心的方法。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//獲取 LocaleContextHolder 中原來(lái)保存的 LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//獲取當(dāng)前請(qǐng)求的 LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//獲取 RequestContextHolder 中原來(lái)保存的 RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//獲取當(dāng)前請(qǐng)求的 ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//將當(dāng)前請(qǐng)求的 LocaleContext 和 ServletRequestAttributes 設(shè)置到 LocaleContextHolder 和 RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
try {
//實(shí)際處理請(qǐng)求的入口,這是一個(gè)模板方法,在 Dispatcher 類(lèi)中才有具體實(shí)現(xiàn)
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 恢復(fù)到 LocaleContextHolder 和 RequestContextHolder 中
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//刪除了日志打印代碼
//發(fā)布了一個(gè) ServletRequestHandledEvent 類(lèi)型的消息
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
DispatcherServlet
上一章中其實(shí)還沒(méi)把該類(lèi)講清楚,在這個(gè)類(lèi)中,里面的智行處理的入口方法應(yīng)該是 doService 方法,方法里面調(diào)用了 doDispatch 進(jìn)行具體的處理,在調(diào)用 doDispatch 方法之前 doService 做了一些事情:首先判斷是不是 include 請(qǐng)求,如果是則對(duì) request 的 Attribute 做個(gè)快照備份,等 doDispatcher 處理完之后(如果不是異步調(diào)用且未完成)進(jìn)行還原 ,在做完快照后又對(duì) request 設(shè)置了一些屬性。
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 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 {
//調(diào)用 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 {
//檢查是不是上傳請(qǐng)求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request. 根據(jù) request 找到 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.根據(jù) Handler 找到對(duì)應(yīng)的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
//處理 GET 、 HEAD 請(qǐng)求的 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;
}
}
//執(zhí)行相應(yīng)的 Interceptor 的 preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. HandlerAdapter 使用 Handler 處理請(qǐng)求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果需要異步處理,直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//當(dāng) view 為空時(shí),根據(jù) request 設(shè)置默認(rèn) view
applyDefaultViewName(processedRequest, mv);
//執(zhí)行相應(yīng) 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);
}
//調(diào)用 processDispatchResult 方法處理上面處理之后的結(jié)果(包括處理異常,渲染頁(yè)面,發(fā)出完成通知觸發(fā) 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 {
//判斷是否執(zhí)行異步請(qǐng)求
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. 刪除上傳請(qǐng)求的資源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
Handler,HandlerMapping,HandlerAdapter 三個(gè)區(qū)別:
Handler:處理器,對(duì)應(yīng) MVC 的 C層,也就是 Controller 層,具體表現(xiàn)形式有很多種,可以是類(lèi),方法,它的類(lèi)型是 Object,只要可以處理實(shí)際請(qǐng)求就可以是 Handler。
HandlerMapping:用來(lái)查找 Handler 的。
HandlerAdapter :Handler 適配器,
另外 View 和 ViewResolver 的原理與 Handler 和 HandlerMapping 的原理類(lèi)似。
小結(jié)本章分析了 Spring MVC 的請(qǐng)求處理的過(guò)程。
注:本文首發(fā)于 zhisheng 的博客,可轉(zhuǎn)載但務(wù)必請(qǐng)注明原創(chuàng)地址為:http://www.54tianzhisheng.cn/2017/07/14/Spring-MVC02/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/67391.html
摘要:前提好幾周沒(méi)更新博客了,對(duì)不斷支持我博客的童鞋們說(shuō)聲抱歉了。熟悉我的人都知道我寫(xiě)博客的時(shí)間比較早,而且堅(jiān)持的時(shí)間也比較久,一直到現(xiàn)在也是一直保持著更新?tīng)顟B(tài)。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好幾周沒(méi)更新博客了,對(duì)不斷支持我博客的童鞋們說(shuō)聲:抱歉了!。自己這段時(shí)...
摘要:開(kāi)頭正式開(kāi)啟我入職的里程,現(xiàn)在已是工作了一個(gè)星期了,這個(gè)星期算是我入職的過(guò)渡期,算是知道了學(xué)校生活和工作的差距了,總之,盡快習(xí)慣這種生活吧。當(dāng)時(shí)是看的廖雪峰的博客自己也用做爬蟲(chóng)寫(xiě)過(guò)幾篇博客,不過(guò)有些是在前人的基礎(chǔ)上寫(xiě)的。 showImg(https://segmentfault.com/img/remote/1460000010867984); 開(kāi)頭 2017.08.21 正式開(kāi)啟我...
摘要:兩個(gè)序號(hào),三個(gè)標(biāo)志位含義表示所傳數(shù)據(jù)的序號(hào)。正常通信時(shí)為,第一次發(fā)起請(qǐng)求時(shí)因?yàn)闆](méi)有需要確認(rèn)接收的數(shù)據(jù)所以為。終止位,用來(lái)在數(shù)據(jù)傳輸完畢后釋放連接。手機(jī)網(wǎng)站如,填寫(xiě)。中的用法普通的用法分為和兩大類(lèi)。 網(wǎng)站架構(gòu)及其演變過(guò)程 基礎(chǔ)結(jié)構(gòu) 網(wǎng)絡(luò)傳輸分解方式: 標(biāo)準(zhǔn)的 OSI 參考模型 TCP/IP 參考模型 showImg(https://segmentfault.com/img/remot...
摘要:定制特定異常返回結(jié)果根據(jù)官方文檔的例子,可以使用和對(duì)特定異常返回特定的結(jié)果。下面是用瀏覽器和訪問(wèn)的結(jié)果無(wú)輸出注意上方表格的錯(cuò)誤,產(chǎn)生這個(gè)的原因前面已經(jīng)講過(guò)。不過(guò)需要注意的是,無(wú)法通過(guò)設(shè)定,由或者容器決定里一律是。 github:https://github.com/chanjarste... 參考文檔: Spring Boot 1.5.4.RELEASE Documentation ...
閱讀 3418·2021-11-25 09:43
閱讀 3086·2021-10-15 09:43
閱讀 2039·2021-09-08 09:36
閱讀 3031·2019-08-30 15:56
閱讀 816·2019-08-30 15:54
閱讀 2755·2019-08-30 15:54
閱讀 3069·2019-08-30 11:26
閱讀 1320·2019-08-29 17:27