言
學了一遍SpringMVC以后,想著做一個總結,復習一下。復習寫下面的總結的時候才發現,其實自己學得并不徹底、牢固、也沒有學全,視頻跟書本是要結合起來一起,每一位老師的視頻可能提到的東西都不一致,也導致也不是很全面,書本上會講的筆記系統、全面。同時我自己也是一個初學者,下面總結的可能并不完善、正確,希望看到的大神給我指出,在此非常感謝。
目錄
Spring Web MVC (下文簡稱為 SpringMVC )是 Spring 提供 Web 應用的框架設計,屬于表現層的框架。SpringMVC是Spring框架的一部分。
Spring框架包括大致六大模塊,核心容器(Core Container)、AOP和設備支持、數據訪問及集成、Web、報文發送、Test
圖片來源于Spring官網5.0.0.M5:
? https://docs.spring.io/spring-framework/docs/5.0.0.M5/spring-framework-reference/html/overview.html#overview-modules
對于Spring5模塊圖,有2點疑問:
1、不清楚為什么在Spring官網上5.0版本以后,Release版(穩定版)的都未找到模塊圖,但是在M(里程碑版)版找到 了,如果有人在5.0以后的Release版(穩定版)找到,麻煩給我留個言,謝謝。
2、在其他博文中看到Spring5模塊結構圖是這樣的:
挺奇怪這個圖是哪里來的?(路過的大神請指點)
對于問題2,我在Spring5.2.13.RELEASE GA中,找到了如下所示信息:
拷貝以上信息:
Spring Framework Documentation
Version 5.2.13.RELEASE
What’s New, Upgrade Notes, Supported Versions, and other topics, independent of release cadence, are maintained externally on the project’s Github Wiki.
Overview | history, design philosophy, feedback, getting started. |
Core | IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP. |
Testing | Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient. |
Data Access | Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling. |
Web Servlet | Spring MVC, WebSocket, SockJS, STOMP Messaging. |
Web Reactive | Spring WebFlux, WebClient, WebSocket. |
Integration | Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching. |
Languages | Kotlin, Groovy, Dynamic Languages. |
按照以上信息的Web Servlet、Web Reactive已經是分屬于不同的模塊了。
Spring官方文檔:https://spring.io/projects/spring-framework#learn/
上面提到了Spring有不同的版本,在此記錄一下各個版本的意義。
描述方式 | 說明 | 含義 |
Snapshot | 快照版 | 尚不穩定,仍處于開發中的版本 |
Release | 穩定版 | 功能相對穩定,可以對外發行,但有時間限制 |
GA | 正式版 | 代表廣泛可用的穩定版(General Availability) |
M | 里程碑版 | (M是Milestone的意思)具有一些全新的功能或是有意義的版本 |
RC | 終測版 | Release Candidate(最終測試),即將作為正式版發布 |
SpringMVC執行流程圖
圖片來源:三、引用參考資料
(1) 處理器映射器:springmvc框架中的一種對象,框架把實現了HandlerMapping接口的類都叫做映射器(多個);
(2) 處理器映射器作用:根據請求,從springmvc容器對象中獲取處理器對象(MyController controller=ctx.getBean("some")
(3) 框架把找到的處理器對象放到一個叫做處理器執行鏈(HandlerExecutionChain)的類保存
(4) HandlerExecutionchain:類中保存著
?a:處理器對象(MyController);
?b:項目中的所有的攔截器List
(5) 方法調用:HandlerExecutionChain mappedHandler - getHandler (processedRequest);
(1) HandlerExecutionChain執行鏈找到對應的處理器映射器HandlerAdapter。
(2) 處理器適配器:springmvc框架中的對象,需要實現HandlerAdapter接口,
(3) 處理器適配器作用:執行處理器方法(調用MyController.doSome()得到返回值ModelAndView )
(4) 前端控制器中調用適配器:HandlerAdapter ha=getHandlerAdapter (mappedHandler.getHandler());
(5) 執行處理器方法:mv=ha.handle (processedRequest, response, mappedHandler.getHandler());
第08說明:
(1) 視圖解析器:springmvc中的對象,需要實現ViewResoler接口(可以有多個)
(2) 視圖解析器作用:組成視圖完整路徑,使用前綴,后綴。并創建View對象。
(3) view是一個接口,表示視圖的,在框架中jsp,htm1不是string表示,而是使用view和他的實現類表示視圖。
InternalResourceview:視圖類,表示jsp文件,視圖解析器會創建InternalResourceView類對象。 這個對象的里面,有一個屬性url-/WEB-INF/view/show.jsp
綜上所述,總結下SpringMVC的詳細流程圖:
圖片來源:三、引用參考資料
以下源碼來源jar包:spring-webmvc-5.25.RELEASE.jar
? ApplicationContext初始化入口類:ApplicationObjectSupport的setApplicationContext方法,setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子類AbstractDetectingUrlHandlerMapping實現了該方法。
類圖:
UML圖:
? RequestMappingHandlerMapping ,用于注解@Controller,@RequestMapping來定義controller.
初始化時,3個類的大致分工如下:
? 從上面的流程圖可以看到前端控制器(中央處理器)DistepcherServlet是SpringMVC核心,查看DistepcherServlet類的繼承情況。
UML圖:

從繼承關系看出:
? DistepcherServlet ---> FrameworkServlet ---> HttpServletBean---> HttpServlet
? 那就說明DistepcherServlet 類也是一個Servlet類,那最終核心的方法就是service()方法,即Servlet的核心方法。
? 那就找service()方法,在DistepcherServlet中沒有servic()方法,在父類FrameworkServlet有service()方法,源碼如下:
來源:
org.springframework.web.servlet.FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)
/**
* Override the parent class implementation in order to intercept PATCH requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod=HttpMethod.resolve(request.getMethod());
if (httpMethod==HttpMethod.PATCH || httpMethod==null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
可以看到:
FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)拿到request請求,判斷當前請求是否是PATCH請求,不是的就調用父類的servic()方法,調用父類中的service方法就是去調用該類中doPost(),doGet()方法,根據不同的請求方式然后走doPost()或者doGet(),調用中以doGet()為例,
FrameworkServlet類的doGet()源碼:
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
* with a {@code NoBodyResponse} that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
? doGet()又調用FrameworkServlet類中的processRequest(request, response);
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
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(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);
}
}
? processRequest(request, response)方法中最關鍵的又調用了doService(request, response);查看FrameworkServlet類中的doService(request, response),或者是調試跟蹤可知,doService(request, response)由子類DispatcherServlet實現。
源碼來源:
org.springframework.web.servlet.FrameworkServlet.doService(HttpServletRequest request, HttpServletResponse response)
/**
* Subclasses must implement this method to do the work of request handling,
* receiving a centralized callback for GET, POST, PUT and DELETE.
* <p>The contract is essentially the same as that for the commonly overridden
* {@code doGet} or {@code doPost} methods of HttpServlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see javax.servlet.http.HttpServlet#doGet
* @see javax.servlet.http.HttpServlet#doPost
*/
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
? 查看DispatcherServlet中的doService(HttpServletRequest request, HttpServletResponse response)方法
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@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);
}
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);
}
}
}
}
? DispatcherServlet的doService()方法中最終調用doDispatch(request, response),查看源碼如下:
org.springframework.web.servlet.DispatcherServlet.doDispatch()
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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);
// 取得處理當前請求的controller,這里也稱為hanlder處理器,第一個步驟的意義就在這里體現了.這里并不是直接返回controller,而是返回的HandlerExecutionChain請求處理器鏈對象,該對象封裝了handler和攔截器interceptors.
// Determine handler for the current request.
mappedHandler=getHandler(processedRequest);
// 如果handler為空,則返回404
if (mappedHandler==null) {
noHandlerFound(processedRequest, response);
return;
}
//3. 獲取處理request的處理器適配器HandlerAdapter
// 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;
}
}
//處理器適配器執行之前,檢查攔截器的方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//處理器適配器根據找到,執行handler,返回ModelAndView
// Actually invoke the handler.
mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
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 afterCompletion
if (mappedHandler !=null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
? 可以看出doDispatch()就是SpringMVC的核心代碼了,分析doDispatch():
? 首先看下處理器映射器HandlerMapping類圖:
doDispatch()關鍵代碼:
HandlerExecutionChain mappedHandler=null;
mappedHandler=getHandler(processedRequest);
? mappedHandler是一個執行鏈HandlerExecutionChain 對象,這里封裝了handler和攔截器interceptors,getHandler(processedRequest)方法就是從處理器映射器HandlerMapping中找到url和controller的對應關系,并返回給前端控制器DispatchServlet。
查看getHandler(processedRequest);源碼:
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings !=null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler=mapping.getHandler(request);
if (handler !=null) {
return handler;
}
}
}
return null;
}
調試代碼如下:
從代碼調試中可以看到handlerMapping中有三個對象:
this.handlerMappings={ArrayList@4662} size=3
0={BeanNameUrlHandlerMapping@4791}
1={RequestMappingHandlerMapping@4792}
2={RouterFunctionMapping@4793}
接著看getHandler(HttpServletRequest request)方法,先遍歷HandlerMappers,查找控制器找到之后就返回執行鏈HandlerExecutionChain類型的Handler。
可以看到返回的Handler中,拿到的就是我們自己編碼的Controller類,以及攔截器(演示項目中未編寫,所以調試匯總返回的Handler最后是0 interceptors)
HandlerExecutionChain with [com.bjpowernode.controller.MyController#doSome()] and 0 interceptors
將正在調試的idea打開自己編寫的Controller來對照,發現一致:
doDispatch()里面的關鍵代碼:
HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler());
源碼如下:
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters !=null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
為什么還要獲取處理器適配器HandlerAdapter:與獲取處理器映射器HandlerMapping一樣,Spring提供了不通的處理器適配器。
調試如下:
查看DEBUG調試模式中getHandlerAdapter()方法在中的:
handler、adapter、this.handlerAdapters
以下是拷貝的結果:
handler
handler={HandlerMethod@4792} "com.bjpowernode.controller.MyController#doSome()"
logger={LogAdapter$JavaUtilLog@4858}
bean={MyController@4859}
beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
beanType={Class@3782} "class com.bjpowernode.controller.MyController"
method={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
bridgedMethod={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
parameters={MethodParameter[0]@4861}
responseStatus=null
responseStatusReason=null
resolvedFromHandlerMethod={HandlerMethod@4863} "com.bjpowernode.controller.MyController#doSome()"
interfaceParameterAnnotations=null
description="com.bjpowernode.controller.MyController#doSome()"
adapter
adapter={RequestMappingHandlerAdapter@4827}
customArgumentResolvers=null
argumentResolvers={HandlerMethodArgumentResolverComposite@4833}
initBinderArgumentResolvers={HandlerMethodArgumentResolverComposite@4834}
customReturnValueHandlers=null
returnValueHandlers={HandlerMethodReturnValueHandlerComposite@4835}
modelAndViewResolvers=null
contentNegotiationManager={ContentNegotiationManager@4836}
messageConverters={ArrayList@4837} size=4
requestResponseBodyAdvice={ArrayList@4838} size=0
webBindingInitializer=null
taskExecutor={SimpleAsyncTaskExecutor@4839}
asyncRequestTimeout=null
callableInterceptors={CallableProcessingInterceptor[0]@4840}
deferredResultInterceptors={DeferredResultProcessingInterceptor[0]@4842}
reactiveAdapterRegistry={ReactiveAdapterRegistry@4844}
ignoreDefaultModelOnRedirect=false
cacheSecondsForSessionAttributeHandlers=0
synchronizeOnSession=false
sessionAttributeStore={DefaultSessionAttributeStore@4845}
parameterNameDiscoverer={DefaultParameterNameDiscoverer@4846}
beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
sessionAttributesHandlerCache={ConcurrentHashMap@4848} size=0
initBinderCache={ConcurrentHashMap@4849} size=0
initBinderAdviceCache={LinkedHashMap@4850} size=0
modelAttributeCache={ConcurrentHashMap@4851} size=0
modelAttributeAdviceCache={LinkedHashMap@4852} size=0
order=2147483647
supportedMethods=null
allowHeader="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"
requireSession=false
cacheControl=null
cacheSeconds=-1
varyByRequestHeaders=null
useExpiresHeader=false
useCacheControlHeader=true
useCacheControlNoStore=true
alwaysMustRevalidate=false
servletContext={ApplicationContextFacade@4754}
logger={LogAdapter$JavaUtilLog@4854}
applicationContext={XmlWebApplicationContext@4665} "WebApplicationContext for namespace 'myweb-servlet', started on Tue Mar 02 23:25:35 CST 2021"
messageSourceAccessor={MessageSourceAccessor@4855}
this.handlerAdapters
this.handlerAdapters={ArrayList@4658} size=4
0={HttpRequestHandlerAdapter@4810}
1={SimpleControllerHandlerAdapter@4820} //XML方式
2={RequestMappingHandlerAdapter@4827} //注解方式
3={HandlerFunctionAdapter@4832}
可以看到找到4個處理器適配器。通過DEBUG模式可以看到,此次取到的處理器適配器HandlerAdapter是:RequestMappingHandlerAdapter
ha={RequestMappingHandlerAdapter@4827}
doDispatch()中的關鍵代碼:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
applyPreHandle(processedRequest, response)源碼:
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors=getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i=0; i < interceptors.length; i++) {
HandlerInterceptor interceptor=interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex=i;
}
}
return true;
}
doDispatch()中的關鍵代碼:
mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
DEBUG模式調試,是調到了:
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
源碼如下:
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
再往下看handleInternal(request, response, (HandlerMethod) handler)方法,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session=request.getSession(false);
if (session !=null) {
Object mutex=WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav=invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav=invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav=invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
注意,handleInternal(request, response, (HandlerMethod) handler)方法的返回值是ModelAndView ,這里就完成了處理器適配器HandlerAdapter執行Handler(Controller)并將結果ModelAndView返回給前端控制器DistepchServlet
??接上2.3:前端控制器DistepchServlet接收到處理器適配器HandlerAdapter返回的ModelAndView以后,這里分2種情況:
總結一下:
視圖解析器ViewResolver接口主要作用是解析前端控制器DispatcherServlet傳遞的邏輯視圖名,并將解析結果的真正的視圖對象View傳回給前端控制器DispatcherServlet
ViewResolverd的實現類:
ViewResolver的UML:
.png)
視圖類型 | 簡介 | |
URL視圖資源圖 | InternalResourceView | 將JSP或其他資源封裝成一個視圖。被視圖解析器InternalResourceViewResolver默認使用。 |
JstlView | InternalResourceView的子類。如果JSP中使用了JSTL的國際化標簽,就需要使用該視圖類。 | |
文檔視圖 | AbstractExcelView | Excel文檔視圖的抽象類。 |
AbstractPdfView | PDF文檔視圖的抽象類 | |
報表視圖 | ConfigurableJasperReportsView | 常用的JasperReports報表視圖 |
JasperReportsHtmlView | ||
JasperReportsPdfView | ||
JasperReportsXlsView | ||
JSON視圖 | MappingJackson2JsonView | 將數據通過Jackson框架的ObjectMapper對象,以JSON方式輸出 |
DispatcherServlet.properties文件是在SpringMVC架包中:
DispatcherServlet.properties內容:
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
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.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
SpringMVC為什么能加載不同處理器映射器HandlerMapping、處理器適配器handlerAdapter,就是因為框架配置了這個DispatcherServlet.properties文件。
轉載于:https://www.cnblogs.com/chuchq/p/14489716.html
一篇[79、Spring MVC]
下一篇[80、使用 Spring 安全進行測試]
在學習 SpringMVC 之前我們先看看在使用 Servlet 的時候我們是如何處理用戶請求的:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--將 Servlet接口實現類交給Tomcat管理-->
<servlet>
<servlet-name>userServlet</servlet-name> <!--Servlet接口實現類名稱-->
<servlet-class>com.xxl.controller.UserServlet</servlet-class><!--聲明servlet接口實現類類路徑-->
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/user</url-pattern> <!--設置Servlet類的請求路徑,必須以 / 開頭-->
</servlet-mapping>
</web-app>
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 這里用來處理用戶的 get 請求
System.out.println("哈哈哈哈哈哈我頭上有注解");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 這里用來處理用戶的 post 請求
System.out.println("接收到用戶的 post 請求");
}
}
String name=request.getParameter("name");
String age=request.getParameter("age");
....
一頓操作下來,我們發現用 Servlet 處理用戶的請求也太麻煩了吧。
每個 Servlet 都要繼承 HttpServlet、重寫兩個方法,我們需要寫一堆 getParameter() 方法來獲取請求參數,而且還要做數據類型的轉換。
那有沒有一個別人封裝好的工具或者是框架讓我少寫這些重復性的代碼呢?
SpringMVC閃亮登場。
SpringMVC 是一種輕量級的、基于 MVC 的 Web 層應用框架,它屬于 Spring 框架的一部分。SpringMVC 說白了就是對 Servlet 進行了封裝,方便大家使用。
總之就是好用!
這里我們先來開發一個基于 SpringMVC 的程序,感受一下 SpringMVC 的迷人特性。
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--springmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
DispatcherServlet 是 SpringMVC 的中央調度器,它主要負責加載 SpringMVC 的配置。
從它的名字來看,他也屬于一個 Servlet,遵守 Servlet 規范。所以我們需要在 web.xml 中創建 DispatcherServlet。
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<!--servlet: DispatcherServlet-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
這里我們在 src/resources 資源目錄下創建 SpringMVC的配置文件 springmvc.xml,該文件名字可以任意命名。
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
TestController:
/**
* @Desc: 處理器
* @Author: 知否技術
* @date: 下午7:39 2022/4/29
*/
@Controller
public class TestController {
@RequestMapping("/hello")
public ModelAndView sayHello() {
ModelAndView mv=new ModelAndView();
mv.addObject("msg", "你好啊,李銀河,我是王小波。");
mv.setViewName("/hello.jsp");
return mv;
}
}
我們在 springmvc.xml 中注冊組件掃描器,
<!-- 掃描組件,將加上@Controller注解的類作為SpringMVC的控制層 --> <context:component-scan base-package="com.zhifou"></context:component-scan>
我們需要在 springmvc.xml 中配置請求文件的路徑和文件后綴。
<!--
配置視圖解析器
作用:將prefix + 視圖名稱 + suffix 確定最終要跳轉的頁面
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
因為我們指定了請求文件的后綴是 .jsp,所以這里可以省略。
@RequestMapping 注解用來指定處理哪些 URL 請求。
value 用來表示請求的 url,可以省略不寫
@RequestMapping(value="/hello")
簡寫
@RequestMapping("/hello")
method 用來表示請求方式,不寫的話默認是 GET 請求。常用的請求方式:
POST、GET、PUT、DELETE
如果使用 method 屬性,不能省略 value 屬性。
@RequestMapping(value="/hello",method=RequestMethod.GET)
public ModelAndView sayHello() {
ModelAndView mv=new ModelAndView();
mv.addObject("message", "你好啊,李銀河,我是王小波。");
mv.setViewName("/hello");
return mv;
}
一個系統包含很多模塊,例如用戶、商品、訂單等模塊。
我們需要為不同的模塊定義不同的類,然后在類上面添加 @RequestMapping 注解,表示這個模塊下面統一的請求路徑:例如:
// 用戶操作控制器
@Controller
@RequestMapping("/user")
public class UserController {
}
// 訂單操作控制器
@Controller
@RequestMapping("/order")
public class OrderController {
}
...
每個模塊都有很多方法,例如增刪改查等。所以我們一般會在相應的方法上面添加 @RequestMapping 注解,表示請求這個模塊下的某個方法,例如:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/list")
public Object list() {
return null;
}
@RequestMapping(value="/add",method=RequestMethod.POST)
public Object add() {
return null;
}
@RequestMapping(value="/update",method=RequestMethod.POST)
public Object update() {
return null;
}
@RequestMapping(value="/delete",method=RequestMethod.DELETE)
public Object delete() {
return null;
}
}
所以當我們獲取用戶列表信息時,我們請求的后臺接口的 url 就是:
ip地址:端口號/項目名/uset/list
//例如:
localhost:8080/ems/user/list
方法上關于不同請求方式的注解都比較長,例如:
@RequestMapping(value="/add",method=RequestMethod.POST)
SpringMVC 為我們提供了簡化寫法:
GET請求:
@GetMapping("/list")
POST 請求:
@PostMapping("/login")
DELETE 請求:
@DeleteMapping("/delete/{id}")
PUT 請求
@PutMapping("/update")
@PostMapping("/login") public Result<User> login(String username,String password) { User user=userService.login(username, password); return Result.success(user); }
Spring MVC 會按請求參數名和 POJO 屬性名進行自動匹配,自動為該對象填充屬性值。
@PostMapping("/login") public Result<User> login(User user){ User user=userService.login(user.getUsername(), user.getPassword); return Result.success(user); }
使用 @RequestParam 可以把請求參數傳遞給請求方法。
屬性:
@PostMapping("/login")
public Result<User> login(@RequestParam String username, @RequestParam String password) {
User user=userService.login(username, password);
return Result.success(user);
}
通過 @PathVariable 可以將 URL 中占位符參數綁定到控制器處理方法的入參中。
URL 中的 {xxx} 占位符可以通過 @PathVariable("xxx")綁定到操作方法的入參中。
@DeleteMapping(value="/delete/{id}")
public Result<?> delete(@PathVariable("id") int id) {
userService.remove(id);
return Result.success();
}
請求參數如果含有中文,會出現中文亂碼問題。我們可以通過在 web.xml 中配置字符過濾器來解決中文亂碼問題。
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注:filter 標簽要在 servlet 標簽之上
SpringMVC 提供了以下幾種方式處理響應數據。
@GetMapping("/hello")
public String toHello() {
return "/hello";
}
使用 redirect 關鍵字可以重定向到其他頁面。
@GetMapping("/hello")
public String toHello() {
return "redirect:/hello";
}
控制器在處理完用戶的請求之后,如果既想跳轉頁面,又想傳遞數據,可以使用 ModelAndView 對象。
@RequestMapping(value="/hello",method=RequestMethod.GET)
public ModelAndView sayHello() {
ModelAndView mv=new ModelAndView();
mv.addObject("message", "你好啊,李銀河,我是王小波。");
mv.setViewName("/hello");
return mv;
}
Mode 對象也可以在跳轉頁面的時候傳遞數據。
@GetMapping("/print")
public String print(Model model) {
model.addAttribute("msg", "祝大家五一快樂!");
return "/hello";
}
返回對象,需要使用 @ResponseBody 注解將對象轉換為 json 格式的數據響應給瀏覽器。
@GetMapping(value="/print", produces="application/json;charset=utf8")
@ResponseBody
public Object print() {
User user=new User(11111L, "張無忌", 12);
String json=JSONUtil.toJsonStr(user);
return json;
}
如果遇到中文亂碼的問題,我們可以在 @GetMapping 注解里面設置 produces 屬性。
@RestController 注解是 @Controller 和 @ResponseBody 注解的組合注解,表示這個控制器中所有的方法全都返回 json 格式的數據。
有時候我們不需要跳轉頁面,也不需要轉發數據,我們只是希望瀏覽器能夠渲染數據(例如后臺向前臺輸出驗證碼)。這時候可以使用 HttpServletResponse 向瀏覽器輸出數據。
@GetMapping("/print")
public void print(HttpServletResponse response) throws IOException {
/*解決響應數據中文亂碼問題*/
response.setContentType("text/html;charset=utf8");
PrintWriter writer=response.getWriter();
writer.print("祝大家五一快樂");
writer.flush();
writer.close();
}
我們在 web.xml 中配置 url-pattern 為 /,當我們請求項目的靜態資源的時候,SpringMVC 的中央調度器會先根據處理器映射器尋找相應的處理器,結果沒找到,所以請求靜態資源會報 404。
我們可以使用 mvc:resources 標簽來解決無法訪問靜態資源的問題。
但是 DispatcherServlet 的映射路徑為 /,而 Tomcat 默認的 Servlet 的映射路徑也為/,所以 DispatcherServlet 會覆蓋 Tomcat 中默認的 Servlet,去處理除 jsp 之外的所有資源,導致靜態資源無法被訪問。
所以這里還需要結合另外一個標簽使用:
<!-- static/**表示該目錄下所有文件-->
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:annotation-driven/>
這里我們測試訪問項目中的照片:
@GetMapping("/hello")
public String toHello() {
return "/hello";
}
在開發過程中我們會遇到各種各樣的異常,我們會有兩種處理方式:
但是用戶不希望看到一堆異常信息:
即便是程序出錯了,他們也希望看到一些看得懂的錯誤信息,例如:
{
code: 500,
message: '該商品信息不存在,請聯系管理員'
}
所以我們需要搞一個全局異常處理器先捕獲這些錯誤信息,然后返回給用戶統一的結果信息。
@RestControllerAdvice
public class GlobalExceptionHandler {
//自定義異常處理器
@ExceptionHandler(CustomException.class)
public Result CustomExceptionHandler(Exception e) {
return Result.fail(500,e.getMessage());
}
}
@RestControllerAdvice:表示這個類是全局異常處理器,返回的格式是 json 格式。
@ExceptionHandler:捕獲的異常類型
/**
* @Desc: 自定義異常
* @Author: 知否技術
* @date: 下午8:05 2022/5/3
*/
public class CustomException extends RuntimeException{
public CustomException(String message) {
super(message);
}
}
因為大部分異常是運行時異常,所以這里自定義的異常繼承了運行時異常。
/**
* @Desc: 統一結果類
* @Author: 知否技術
* @date: 下午9:02 2022/5/3
*/
public class Result implements Serializable {
private int code;
private String message;
private Object data;
private Result(String message) {
this.code=200;
this.message=message;
}
private Result(String message, Object data) {
this.code=200;
this.message=message;
this.data=data;
}
private Result(int code, String message) {
this.code=code;
this.message=message;
}
public static Result success(String message) {
return new Result(message);
}
public static Result success(String message, Object data) {
return new Result(message, data);
}
public static Result fail(int code, String message) {
return new Result(code, message);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code=code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data=data;
}
}
4.測試
@GetMapping("/hello")
@ResponseBody
public Result toHello() {
throw new CustomException("臥槽!!出錯了");
}
SpringMVC 中的攔截器主要是用來攔截用戶的請求,并進行相關的處理。
實現攔截器:
/**
* @Desc:
* @Author: 知否技術
* @date: 下午2:09 2022/5/3
*/
public class MyInterceptor implements HandlerInterceptor {
// 在處理器中方法執行之前執行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
// 在處理器中方法執行之后執行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
}
<!-- 配置攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!--攔截所有請求-->
<mvc:mapping path="/**"/>
<!--排除請求-->
<mvc:exclude-mapping path="/user/login"/>
<bean class="com.zhifou.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
測試:
1.Tomcat 服務器啟動的時候會立即創建 DispatcherServlet(中央調度器),同時會創建 SpringMVC 容器。
2.SpringMVC 容器初始化的時候會先根據配置文件中的組件掃描器先掃描一下哪些類上面有 @Controller 注解,并將這些類作為處理器類。
然后通過 @RequestMapping 注解生成對應的映射關系。這些對應關系由處理器映射器管理。
3.當收到用戶的請求,中央調度器將請求轉發給處理器映射器。
4.處理器映射器根據用戶請求的 URL 從映射關系中找到處理該請求的處理器,然后封裝成處理器執行鏈返回給中央處理器。
5.中央調度器根據處理器執行鏈中的處理器,找到能夠執行該處理器的處理器適配器,處理器適配器調用執行處理器。
6.處理器將處理結果及要跳轉的視圖封裝到 ModelAndView 中,并將其返回給處理器適配器。
7.處理器適配器直接將結果返回給中央調度器,中央調度器調用視圖解析器,將 ModelAndView 中的視圖名稱封裝為視圖對象。
8.視圖解析器將封裝了的視圖對象返回給中央調度器,中央調度器調用視圖對象,填充數據,生成響應對象。
9.中央調度器將結果響應給瀏覽器。
原文鏈接:https://juejin.cn/post/7093670296888016933
*請認真填寫需求信息,我們會在24小時內與您取得聯系。