十、攔截器
1、攔截器的配置
SpringMVC中的攔截器用于攔截控制器方法的執行
SpringMVC中的攔截器需要實現HandlerInterceptor
SpringMVC的攔截器必須在SpringMVC的配置文件中進行配置:
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上兩種配置方式都是對DispatcherServlet所處理的所有的請求進行攔截 -->
<mvc:interceptor><mvc:mapping path="/**"/><mvc:exclude-mapping path="/testRequestEntity"/><ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!-- 以上配置方式可以通過ref或bean標簽設置攔截器,通過mvc:mapping設置需要攔截的請求,通過mvc:exclude-mapping設置需要排除的請求,即不需要攔截的請求
-->
2、攔截器的三個抽象方法
SpringMVC中的攔截器有三個抽象方法:
preHandle:控制器方法執行之前執行preHandle(),其boolean類型的返回值表示是否攔截或放行,返回true為放行,即調用控制器方法;返回false表示攔截,即不調用控制器方法
postHandle:控制器方法執行之后執行postHandle()
afterComplation:處理完視圖和模型數據,渲染視圖完畢之后執行afterComplation()
3、多個攔截器的執行順序
a>若每個攔截器的preHandle()都返回true
此時多個攔截器的執行順序和攔截器在SpringMVC的配置文件的配置順序有關:
preHandle()會按照配置的順序執行,而postHandle()和afterComplation()會按照配置的反序執行
b>若某個攔截器的preHandle()返回了false
preHandle()返回false和它之前的攔截器的preHandle()都會執行,postHandle()都不執行,返回false的攔截器之前的攔截器的afterComplation()會執行
十一、異常處理器
1、基于配置的異常處理
SpringMVC提供了一個處理控制器方法執行過程中所出現的異常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的實現類有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMVC提供了自定義的異常處理器SimpleMappingExceptionResolver,使用方式:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><props><!--properties的鍵表示處理器方法執行過程中出現的異常properties的值表示若出現指定異常時,設置一個新的視圖名稱,跳轉到指定頁面--><prop key="java.lang.ArithmeticException">error</prop></props></property><!--exceptionAttribute屬性設置一個屬性名,將出現的異常信息在請求域中進行共享--><property name="exceptionAttribute" value="ex"></property>
</bean>
2、基于注解的異常處理
//@ControllerAdvice將當前類標識為異常處理的組件
@ControllerAdvice
public class ExceptionController {//@ExceptionHandler用于設置所標識方法處理的異常@ExceptionHandler(ArithmeticException.class)//ex表示當前請求處理中出現的異常對象public String handleArithmeticException(Exception ex, Model model){model.addAttribute("ex", ex);return "error";}}
十二、注解配置SpringMVC
使用配置類和注解代替web.xml和SpringMVC配置文件的功能
1、創建初始化類,代替web.xml
在Servlet3.0環境中,容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類,如果找到的話就用它來配置Servlet容器。
Spring提供了這個接口的實現,名為SpringServletContainerInitializer,這個類反過來又會查找實現WebApplicationInitializer的類并將配置的任務交給它們來完成。Spring3.2引入了一個便利的WebApplicationInitializer基礎實現,名為AbstractAnnotationConfigDispatcherServletInitializer,當我們的類擴展了AbstractAnnotationConfigDispatcherServletInitializer并將其部署到Servlet3.0容器的時候,容器會自動發現它,并用它來配置Servlet上下文。
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {/*** 指定spring的配置類* @return*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}/*** 指定SpringMVC的配置類* @return*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{WebConfig.class};}/*** 指定DispatcherServlet的映射規則,即url-pattern* @return*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}/*** 添加過濾器* @return*/@Overrideprotected Filter[] getServletFilters() {CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();encodingFilter.setEncoding("UTF-8");encodingFilter.setForceRequestEncoding(true);HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();return new Filter[]{encodingFilter, hiddenHttpMethodFilter};}
}
2、創建SpringConfig配置類,代替spring的配置文件
@Configuration
public class SpringConfig {//ssm整合之后,spring的配置信息寫在此類中
}
3、創建WebConfig配置類,代替SpringMVC的配置文件
@Configuration
//掃描組件
@ComponentScan("com.atguigu.mvc.controller")
//開啟MVC注解驅動
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {//使用默認的servlet處理靜態資源@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}//配置文件上傳解析器@Beanpublic CommonsMultipartResolver multipartResolver(){return new CommonsMultipartResolver();}//配置攔截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {FirstInterceptor firstInterceptor = new FirstInterceptor();registry.addInterceptor(firstInterceptor).addPathPatterns("/**");}//配置視圖控制/*@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");}*///配置異常映射/*@Overridepublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();Properties prop = new Properties();prop.setProperty("java.lang.ArithmeticException", "error");//設置異常映射exceptionResolver.setExceptionMappings(prop);//設置共享異常信息的鍵exceptionResolver.setExceptionAttribute("ex");resolvers.add(exceptionResolver);}*///配置生成模板解析器@Beanpublic ITemplateResolver templateResolver() {WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();// ServletContextTemplateResolver需要一個ServletContext作為構造參數,可通過WebApplicationContext 的方法獲得ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());templateResolver.setPrefix("/WEB-INF/templates/");templateResolver.setSuffix(".html");templateResolver.setCharacterEncoding("UTF-8");templateResolver.setTemplateMode(TemplateMode.HTML);return templateResolver;}//生成模板引擎并為模板引擎注入模板解析器@Beanpublic SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {SpringTemplateEngine templateEngine = new SpringTemplateEngine();templateEngine.setTemplateResolver(templateResolver);return templateEngine;}//生成視圖解析器并未解析器注入模板引擎@Beanpublic ViewResolver viewResolver(SpringTemplateEngine templateEngine) {ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();viewResolver.setCharacterEncoding("UTF-8");viewResolver.setTemplateEngine(templateEngine);return viewResolver;}}
4、測試功能
@RequestMapping("/")
public String index(){return "index";
}
十三、SpringMVC執行流程
1、SpringMVC常用組件
- DispatcherServlet:前端控制器,不需要工程師開發,由框架提供
作用:統一處理請求和響應,整個流程控制的中心,由它調用其它組件處理用戶的請求
- HandlerMapping:處理器映射器,不需要工程師開發,由框架提供
作用:根據請求的url、method等信息查找Handler,即控制器方法
- Handler:處理器,需要工程師開發
作用:在DispatcherServlet的控制下Handler對具體的用戶請求進行處理
- HandlerAdapter:處理器適配器,不需要工程師開發,由框架提供
作用:通過HandlerAdapter對處理器(控制器方法)進行執行
- ViewResolver:視圖解析器,不需要工程師開發,由框架提供
作用:進行視圖解析,得到相應的視圖,例如:ThymeleafView、InternalResourceView、RedirectView
- View:視圖
作用:將模型數據通過頁面展示給用戶
2、DispatcherServlet初始化過程
DispatcherServlet 本質上是一個 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏觀上是 Servlet 生命周期來進行調度。
a>初始化WebApplicationContext
所在類:org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local one// 創建WebApplicationContextwac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.synchronized (this.onRefreshMonitor) {// 刷新WebApplicationContextonRefresh(wac);}}if (this.publishContext) {// Publish the context as a servlet context attribute.// 將IOC容器在應用域共享String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}
b>創建WebApplicationContext
所在類:org.springframework.web.servlet.FrameworkServlet
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");}// 通過反射創建 IOC 容器對象ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());// 設置父容器wac.setParent(parent);String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}configureAndRefreshWebApplicationContext(wac);return wac;
}
c>DispatcherServlet初始化策略
FrameworkServlet創建WebApplicationContext后,刷新容器,調用onRefresh(wac),此方法在DispatcherServlet中進行了重寫,調用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各個組件
所在類:org.springframework.web.servlet.DispatcherServlet
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
}
3、DispatcherServlet調用組件處理請求
a>processRequest()
FrameworkServlet重寫HttpServlet中的service()和doXxx(),這些方法中調用了processRequest(request, response)
所在類:org.springframework.web.servlet.FrameworkServlet
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 {// 執行服務,doService()是一個抽象方法,在DispatcherServlet中進行了重寫doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}
b>doService()
所在類:org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 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());if (this.flashMapManager != null) {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);}RequestPath requestPath = null;if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {requestPath = ServletRequestPathUtils.parseAndCache(request);}try {// 處理請求和響應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);}}if (requestPath != null) {ServletRequestPathUtils.clearParsedRequestPath(request);}}
}
c>doDispatch()
所在類:org.springframework.web.servlet.DispatcherServlet
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./*mappedHandler:調用鏈包含handler、interceptorList、interceptorIndexhandler:瀏覽器發送的請求所匹配的控制器方法interceptorList:處理控制器方法的所有攔截器集合interceptorIndex:攔截器索引,控制攔截器afterCompletion()的執行*/mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 通過控制器方法創建相應的處理器適配器,調用所對應的控制器方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {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.// 由處理器適配器調用具體的控制器方法,最終獲得ModelAndView對象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) {// 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(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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}
d>processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {// 處理模型數據和渲染視圖render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..// 調用攔截器的afterCompletion()mappedHandler.triggerAfterCompletion(request, response, null);}
}
4、SpringMVC的執行流程
-
用戶向服務器發送請求,請求被SpringMVC 前端控制器 DispatcherServlet捕獲。
-
DispatcherServlet對請求URL進行解析,得到請求資源標識符(URI),判斷請求URI對應的映射:
a) 不存在
i. 再判斷是否配置了mvc:default-servlet-handler
ii. 如果沒配置,則控制臺報映射查找不到,客戶端展示404錯誤
iii. 如果有配置,則訪問目標資源(一般為靜態資源,如:JS,CSS,HTML),找不到客戶端也會展示404錯誤
b) 存在則執行下面的流程
-
根據該URI,調用HandlerMapping獲得該Handler配置的所有相關的對象(包括Handler對象以及Handler對象對應的攔截器),最后以HandlerExecutionChain執行鏈對象的形式返回。
-
DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter。
-
如果成功獲得HandlerAdapter,此時將開始執行攔截器的preHandler(…)方法【正向】
-
提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller)方法,處理請求。在填充Handler的入參過程中,根據你的配置,Spring將幫你做一些額外的工作:
a) HttpMessageConveter: 將請求消息(如Json、xml等數據)轉換成一個對象,將對象轉換為指定的響應信息
b) 數據轉換:對請求消息進行數據轉換。如String轉換成Integer、Double等
c) 數據格式化:對請求消息進行數據格式化。 如將字符串轉換成格式化數字或格式化日期等
d) 數據驗證: 驗證數據的有效性(長度、格式等),驗證結果存儲到BindingResult或Error中
-
Handler執行完成后,向DispatcherServlet 返回一個ModelAndView對象。
-
此時將開始執行攔截器的postHandle(…)方法【逆向】。
-
根據返回的ModelAndView(此時會判斷是否存在異常:如果存在異常,則執行HandlerExceptionResolver進行異常處理)選擇一個適合的ViewResolver進行視圖解析,根據Model和View,來渲染視圖。
-
渲染視圖完畢執行攔截器的afterCompletion(…)方法【逆向】。
-
將渲染結果返回給客戶端。