Java架構實踐-SpringMVC源碼

1. MVC使用

在研究源碼之前,先來回顧以下springmvc?是如何配置的,這將能使我們更容易理解源碼。

1.1 web.xml

1 2mvc-dispatcher 3org.springframework.web.servlet.DispatcherServlet 4 spring -> springmvc 7--> 8 9contextConfigLocation10classpath:spring/spring-*.xml11121314mvc-dispatcher1516/17


值的注意的是contextConfigLocation和DispatcherServlet(用此類來攔截請求)的引用和配置。

1.2 spring-web.xml

1 2 3 7 8 91314151617181920212223


值的注意的是InternalResourceViewResolver,它會在ModelAndView返回的試圖名前面加上prefix前綴,在后面加載suffix指定后綴。

SpringMvc主支源碼分析

引用《Spring?in?Action》中的一張圖來更好的了解執(zhí)行過程:?


上圖流程總體來說可分為三大塊:

Map的建立(并放入WebApplicationContext)

HttpRequest請求中Url的請求攔截處理(DispatchServlet處理)

反射調用Controller中對應的處理方法,并返回視圖

本文將圍繞這三塊進行分析。

1. Map的建立

在容器初始化時會建立所有 url 和 Controller 的對應關系,保存到 Map<url,controller>中,那是如何保存的呢。

ApplicationObjectSupport #setApplicationContext方法

1// 初始化ApplicationContext2@Override3publicvoidinitApplicationContext()throws ApplicationContextException {4super.initApplicationContext();5? ? detectHandlers();6}

AbstractDetectingUrlHandlerMapping?#detectHandlers()方法:

1/** 2 * 建立當前ApplicationContext 中的 所有Controller 和url 的對應關系 3 * Register all handlers found in the current ApplicationContext. 4 * <p>The actual URL determination for a handler is up to the concrete 5 * {@link #determineUrlsForHandler(String)} implementation. A bean for 6 * which no such URLs could be determined is simply not considered a handler. 7 * @throws org.springframework.beans.BeansException if the handler couldn't be registered 8 * @see #determineUrlsForHandler(String) 9*/10protectedvoiddetectHandlers()throws BeansException {11if (logger.isDebugEnabled()) {12logger.debug("Looking for URL mappings in application context: " + getApplicationContext());13? ? }14// 獲取容器中的beanNames15String[] beanNames = (this.detectHandlersInAncestorContexts ?16BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :17getApplicationContext().getBeanNamesForType(Object.class));18// 遍歷 beanNames 并找到對應的 url19// Take any bean name that we can determine URLs for.20for (String beanName : beanNames) {21// 獲取bean上的url(class上的url + method 上的 url)22String[] urls = determineUrlsForHandler(beanName);23if(!ObjectUtils.isEmpty(urls)) {24// URL paths found: Let's consider it a handler.25// 保存url 和 beanName 的對應關系26? ? ? ? ? ? registerHandler(urls, beanName);27? ? ? ? }28else {29if (logger.isDebugEnabled()) {30logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");31? ? ? ? ? ? }32? ? ? ? }33? ? }34}

determineUrlsForHandler()方法:

該方法在不同的子類有不同的實現,我這里分析的是DefaultAnnotationHandlerMapping類的實現,該類主要負責處理@RequestMapping注解形式的聲明。

1/** 2 * 獲取@RequestMaping注解中的url 3 * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} 4 * annotation on the handler class and on any of its methods. 5*/ 6@Override 7protected String[] determineUrlsForHandler(String beanName) { 8ApplicationContext context = getApplicationContext(); 9Class handlerType = context.getType(beanName);10// 獲取beanName 上的requestMapping11RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);12if(mapping !=null) {13// 類上面有@RequestMapping 注解14this.cachedMappings.put(handlerType, mapping);15Set urls =newLinkedHashSet();16// mapping.value()就是獲取@RequestMapping注解的value值17String[] typeLevelPatterns = mapping.value();18if(typeLevelPatterns.length > 0) {19// 獲取Controller 方法上的@RequestMapping20String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType);21for (String typeLevelPattern : typeLevelPatterns) {22if(!typeLevelPattern.startsWith("/")) {23typeLevelPattern = "/" + typeLevelPattern;24? ? ? ? ? ? ? ? }25for (String methodLevelPattern : methodLevelPatterns) {26// controller的映射url+方法映射的url27String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);28// 保存到set集合中29? ? ? ? ? ? ? ? ? ? addUrlsForPath(urls, combinedPattern);30? ? ? ? ? ? ? ? }31? ? ? ? ? ? ? ? addUrlsForPath(urls, typeLevelPattern);32? ? ? ? ? ? }33// 以數組形式返回controller上的所有url34return StringUtils.toStringArray(urls);35? ? ? ? }36else {37// controller上的@RequestMapping映射url為空串,直接找方法的映射url38return determineUrlsForHandlerMethods(handlerType);39? ? ? ? }40? ? }41// controller上沒@RequestMapping注解42elseif(AnnotationUtils.findAnnotation(handlerType, Controller.class) !=null) {43// 獲取controller中方法上的映射url44return determineUrlsForHandlerMethods(handlerType);45? ? }46else {47returnnull;48? ? }49}


更深的細節(jié)代碼就比較簡單了,有興趣的可以繼續(xù)深入。

到這里,Controller和Url的映射就裝配完成,下來就分析請求的處理過程。

2. url的請求處理

我們在xml中配置了DispatcherServlet為調度器,所以我們就來看它的代碼,可以

從名字上看出它是個Servlet,那么它的核心方法就是doService()

DispatcherServlet?#doService():

1/** 2 * 將DispatcherServlet特定的請求屬性和委托 公開給{@link #doDispatch}以進行實際調度。 3 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch} 4 * for the actual dispatching. 5*/ 6@Override 7protectedvoiddoService(HttpServletRequest request, HttpServletResponse response)throws Exception { 8if (logger.isDebugEnabled()) { 9String requestUri =new UrlPathHelper().getRequestUri(request);10logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +11" request for [" + requestUri + "]");12? ? }1314//在包含request的情況下保留請求屬性的快照,15//能夠在include之后恢復原始屬性。16Map attributesSnapshot =null;17if (WebUtils.isIncludeRequest(request)) {18logger.debug("Taking snapshot of request attributes before include");19attributesSnapshot =newHashMap();20Enumeration attrNames = request.getAttributeNames();21while (attrNames.hasMoreElements()) {22String attrName = (String) attrNames.nextElement();23if(this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {24? ? ? ? ? ? ? ? attributesSnapshot.put(attrName, request.getAttribute(attrName));25? ? ? ? ? ? }26? ? ? ? }27? ? }2829// 使得request對象能供 handler處理和view處理 使用30? ? request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());31request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,this.localeResolver);32request.setAttribute(THEME_RESOLVER_ATTRIBUTE,this.themeResolver);33? ? request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());3435try {36? ? ? ? doDispatch(request, response);37? ? }38finally {39// 如果不為空,則還原原始屬性快照。40if(attributesSnapshot !=null) {41? ? ? ? ? ? restoreAttributesAfterInclude(request, attributesSnapshot);42? ? ? ? }43? ? }44}


可以看到,它將請求拿到后,主要是給request設置了一些對象,以便于后續(xù)工作的處理(Handler處理和view處理)。比如WebApplicationContext,它里面就包含了我們在第一步完成的controller與url映射的信息。

DispatchServlet # doDispatch()

? 1/**? 2 * 控制請求轉發(fā)? 3 * Process the actual dispatching to the handler.? 4 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.? 5 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters? 6 * to find the first that supports the handler class.? 7 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers? 8 * themselves to decide which methods are acceptable.? 9 * @param request current HTTP request 10 * @param response current HTTP response 11 * @throws Exception in case of any kind of processing failure 12*/ 13protectedvoiddoDispatch(HttpServletRequest request, HttpServletResponse response)throws Exception { 14HttpServletRequest processedRequest = request; 15HandlerExecutionChain mappedHandler =null; 16intinterceptorIndex = -1; 17 18try { 19 20? ? ? ? ModelAndView mv; 21booleanerrorView =false; 22 23try { 24// 1. 檢查是否是上傳文件 25processedRequest = checkMultipart(request); 26 27// Determine handler for the current request. 28// 2. 獲取handler處理器,返回的mappedHandler封裝了handlers和interceptors 29mappedHandler = getHandler(processedRequest,false); 30if(mappedHandler ==null|| mappedHandler.getHandler() ==null) { 31// 返回404 32? ? ? ? ? ? ? ? noHandlerFound(processedRequest, response); 33return; 34? ? ? ? ? ? } 35 36// Apply preHandle methods of registered interceptors. 37// 獲取HandlerInterceptor的預處理方法 38HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); 39if(interceptors !=null) { 40for(inti = 0; i < interceptors.length; i++) { 41HandlerInterceptor interceptor = interceptors[i]; 42if(!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { 43triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response,null); 44return; 45? ? ? ? ? ? ? ? ? ? } 46interceptorIndex = i; 47? ? ? ? ? ? ? ? } 48? ? ? ? ? ? } 49 50// Actually invoke the handler. 51// 3. 獲取handler適配器 Adapter 52HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 53// 4. 實際的處理器處理并返回 ModelAndView 對象 54mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 55 56// Do we need view name translation? 57if(mv !=null&& !mv.hasView()) { 58? ? ? ? ? ? ? ? mv.setViewName(getDefaultViewName(request)); 59? ? ? ? ? ? } 60 61// HandlerInterceptor 后處理 62if(interceptors !=null) { 63for(inti = interceptors.length - 1; i >= 0; i--) { 64HandlerInterceptor interceptor = interceptors[i]; 65// 結束視圖對象處理 66? ? ? ? ? ? ? ? ? ? interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); 67? ? ? ? ? ? ? ? } 68? ? ? ? ? ? } 69? ? ? ? } 70catch (ModelAndViewDefiningException ex) { 71logger.debug("ModelAndViewDefiningException encountered", ex); 72mv = ex.getModelAndView(); 73? ? ? ? } 74catch (Exception ex) { 75Object handler = (mappedHandler !=null? mappedHandler.getHandler() :null); 76mv = processHandlerException(processedRequest, response, handler, ex); 77errorView = (mv !=null); 78? ? ? ? } 79 80// Did the handler return a view to render? 81if(mv !=null&& !mv.wasCleared()) { 82? ? ? ? ? ? render(mv, processedRequest, response); 83if (errorView) { 84? ? ? ? ? ? ? ? WebUtils.clearErrorRequestAttributes(request); 85? ? ? ? ? ? } 86? ? ? ? } 87else { 88if (logger.isDebugEnabled()) { 89logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 90"': assuming HandlerAdapter completed request handling"); 91? ? ? ? ? ? } 92? ? ? ? } 93 94// Trigger after-completion for successful outcome. 95// 請求成功響應之后的方法 96triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response,null); 97? ? } 98 99catch (Exception ex) {100// Trigger after-completion for thrown exception.101? ? ? ? triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);102throw ex;103? ? }104catch (Error err) {105ServletException ex =newNestedServletException("Handler processing failed", err);106// Trigger after-completion for thrown exception.107? ? ? ? triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);108throw ex;109? ? }110111finally {112// Clean up any resources used by a multipart request.113if(processedRequest != request) {114? ? ? ? ? ? cleanupMultipart(processedRequest);115? ? ? ? }116? ? }117}

該方法主要是

通過request對象獲取到HandlerExecutionChain,HandlerExecutionChain對象里面包含了攔截器interceptor和處理器handler。如果獲取到的對象是空,則交給noHandlerFound返回404頁面。

攔截器預處理,如果執(zhí)行成功則進行3

獲取handler適配器 Adapter

實際的處理器處理并返回 ModelAndView 對象

下面是該方法中的一些核心細節(jié):

DispatchServlet #doDispatch # noHandlerFound核心源碼:

1response.sendError(HttpServletResponse.SC_NOT_FOUND);

DispatchServlet #doDispatch #getHandler方法事實上調用的是AbstractHandlerMapping #getHandler方法,我貼出一個核心的代碼:

1// 拿到處理對象2Object handler = getHandlerInternal(request);3...4String handlerName = (String) handler;5handler = getApplicationContext().getBean(handlerName);6...7// 返回HandlerExecutionChain對象8returngetHandlerExecutionChain(handler, request);

可以看到,它先從request里獲取handler對象,這就證明了之前DispatchServlet #doService為什么要吧WebApplicationContext放入request請求對象中。

最終返回一個HandlerExecutionChain對象.

3. 反射調用處理請求的方法,返回結果視圖

在上面的源碼中,實際的處理器處理并返回 ModelAndView 對象調用的是mv = ha.handle(processedRequest, response, mappedHandler.getHandler());這個方法。該方法由AnnotationMethodHandlerAdapter #handle() #invokeHandlerMethod()方法實現.

`AnnotationMethodHandlerAdapter #handle() #invokeHandlerMethod()`

1/** 2 * 獲取處理請求的方法,執(zhí)行并返回結果視圖 3*/ 4protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) 5throws Exception { 6 7// 1.獲取方法解析器 8ServletHandlerMethodResolver methodResolver = getMethodResolver(handler); 9// 2.解析request中的url,獲取處理request的方法10Method handlerMethod = methodResolver.resolveHandlerMethod(request);11// 3. 方法調用器12ServletHandlerMethodInvoker methodInvoker =new ServletHandlerMethodInvoker(methodResolver);13ServletWebRequest webRequest =new ServletWebRequest(request, response);14ExtendedModelMap implicitModel =new BindingAwareModelMap();15// 4.執(zhí)行方法(獲取方法的參數)16Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);17// 5. 封裝成mv視圖18ModelAndView mav =19? ? ? ? ? ? methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);20methodInvoker.updateModelAttributes(handler, (mav !=null? mav.getModel() :null), implicitModel, webRequest);21return mav;22}

這個方法有兩個重要的地方,分別是resolveHandlerMethod和invokeHandlerMethod。

resolveHandlerMethod 方法

methodResolver.resolveHandlerMethod(request):獲取controller類和方法上的@requestMapping value,與request的url進行匹配,找到處理request的controller中的方法.最終拼接的具體實現是org.springframework.util.AntPathMatcher#combine方法。

invokeHandlerMethod方法

從名字就能看出來它是基于反射,那它做了什么呢。

解析該方法上的參數,并調用該方法。

1//上面全都是為解析方法上的參數做準備2...3// 解析該方法上的參數4Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);5// 真正執(zhí)行解析調用的方法6returndoInvokeMethod(handlerMethodToInvoke, handler, args);

invokeHandlerMethod方法#resolveHandlerArguments方法

代碼有點長,我就簡介下它做了什么事情吧。

如果這個方法的參數用的是注解,則解析注解拿到參數名,然后拿到request中的參數名,兩者一致則進行賦值(詳細代碼在HandlerMethodInvoker#resolveRequestParam),然后將封裝好的對象放到args[]的數組中并返回。

如果這個方法的參數用的不是注解,則需要asm框架(底層是讀取字節(jié)碼)來幫助獲取到參數名,然后拿到request中的參數名,兩者一致則進行賦值,然后將封裝好的對象放到args[]的數組中并返回。

invokeHandlerMethod方法#doInvokeMethod方法

1privateObject doInvokeMethod(Method method, Object target, Object[] args)throws Exception { 2// 將一個方法設置為可調用,主要針對private方法 3? ? ReflectionUtils.makeAccessible(method); 4try { 5// 反射調用 6return method.invoke(target, args); 7? ? } 8catch (InvocationTargetException ex) { 9? ? ? ? ReflectionUtils.rethrowException(ex.getTargetException());10? ? }11thrownewIllegalStateException("Should never get here");12}

到這里,就可以對request請求中url對應的controller的某個對應方法進行調用了。

總結:

看完后腦子一定很亂,有時間的話還是需要自己動手調試一下。本文只是串一下整體思路,所以功能性的源碼沒有全部分析。

其實理解這些才是最重要的。

需要獲取海量最新BATJ視頻資料加群:345353515 備注(簡書007)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容