java?spring?mvc處理器映射器介紹
更新時間:2022年03月29日 15:42:47 投稿:hqx
這篇文章主要介紹了java?spring?mvc處理器映射器,文章圍繞equestMapping解析映射介紹展開源碼內(nèi)容,具有一定的參考價值,需要的小伙伴可以參考一下
前言:
- 本文源碼基于
spring-framework-5.3.10
。 mvc
是spring
源碼中的一個子模塊!
一、RequestMappingHandlerMapping解析映射簡單介紹
- @RequestMapping通過
RequestMappingHandlerMapping
進行解析! - HandlerMapping是一個根據(jù)URL映射到Handler的方法。
RequestMappingHandlerMapping
是HandlerMapping的一個子類!- RequestMappingHandlerMapping他的父類有InitializingBean,所有在spring啟動實例化的時候會調(diào)用afterPropertiesSet()方法。解析邏輯就在這里。
RequestMappingHandlerMapping
有倆個過程:解析、映射
二、@RequestMapping解析源碼流程
- 容器加載,調(diào)用
RequestMappingHandlerMapping
的afterPropertiesSet
()。 - 調(diào)用父類的
afterPropertiesSet()
方法。 - 調(diào)用initHandlerMethods()方法。
- 循環(huán)每一個Bean,看方法上有@RequestMapping或者@Controller的Bean。
- 解析
HandlerMethods
,進行封裝RequestMappingInfo。 - 將封裝好的
RequestMappingInfo
存起來:key為路徑,值為mapping(RequestMappingInfo)
三、@RequestMapping映射源碼流程
- 請求進來,調(diào)用
getHandler
方法。 - 獲取當前請求對應的
HandlerMethod
。 - 通過
UrlPathHelper
對象,用于來解析從們的request中解析出請求映射路徑。 - 更具路徑去
pathLookup
中找。 - 上面沒找到,從所有的里面找有通配符的。
- 找到多個進行排序,優(yōu)先級:? > * > {} >** 。
- 不為空拿到第一個返回。
- 如果為空獲取默認的。默認還是空的,直接返回null。
- 封裝攔截器,返回。
四、@RequestMapping解析源碼
/** * 解析的開始位置。 * 由于實現(xiàn)了InitializingBean,初始化Bean的時候調(diào)用這個方法。 * 源碼位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet() */ public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setTrailingSlashMatch(useTrailingSlashMatch()); // 尾部斜杠 this.config.setContentNegotiationManager(getContentNegotiationManager()); if (getPatternParser() != null) { this.config.setPatternParser(getPatternParser()); Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, "Suffix pattern matching not supported with PathPatternParser."); } else { this.config.setSuffixPatternMatch(useSuffixPatternMatch()); this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch()); this.config.setPathMatcher(getPathMatcher()); } // 調(diào)用父類的afterPropertiesSet方法 super.afterPropertiesSet(); } /** * 父類的afterPropertiesSet方法。 * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet() */ public void afterPropertiesSet() { initHandlerMethods(); } /** * 解析@RequestMapping方法 * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods() */ protected void initHandlerMethods() { // 獲得所有候選beanName—— 當前容器所有的beanName for (String beanName : getCandidateBeanNames()) { // BeanName不是scopedTarget.開頭的 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // *處理候選bean——即解析@RequestMapping和映射路徑 processCandidateBean(beanName); } } // 解析完所有@RequestMapping的時候調(diào)用 handlerMethodsInitialized(getHandlerMethods()); } /** * 處理候選bean——即解析@RequestMapping和映射路徑 * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.processCandidateBean(String) */ protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { // 得到當前BeanName得到這個Bean的類型 beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } // 這一步判斷是關鍵:是否有Controller 或 RequestMapping注解 if (beanType != null && isHandler(beanType)) { // 解析HandlerMethods detectHandlerMethods(beanName); } } /** * 解析HandlerMethods * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(Object) */ protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); // 循環(huán)所有方法 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { // 根據(jù)Method得到Mapping映射 return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isTraceEnabled()) { logger.trace(formatMappings(userType, methods)); } else if (mappingsLogger.isDebugEnabled()) { mappingsLogger.debug(formatMappings(userType, methods)); } // 遍歷每一個方法,這里是所有Bean的所有方法 methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); // pathLookup放入:key為路徑,值為mapping(RequestMappingInfo) registerHandlerMethod(handler, invocableMethod, mapping); }); } } /** * 根據(jù)Method得到Mapping映射 * 源碼位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(Method, Class<?>) */ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { // 如果方法上面有@RequestMapping:解析出RequestMappingInfo // RequestMappingInfo 是用來在請求的時候做匹對的 RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { // 如果方法上面有@RequestMapping,看看類上面是不是有@RequestMapping RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); // 類上面也有@RequestMapping 那就合并 // 比如 類:/user 方法:/info 合并為 /user/info if (typeInfo != null) { info = typeInfo.combine(info); } // 合并前綴 5.1新增 默認null // 可通過 WebMvcConfigurer#configurePathMatch 進行定制 String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; } /** * 創(chuàng)建請求映射信息的外部邏輯 * 源碼位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(AnnotatedElement) */ private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { // 獲取RequestMapping注解信息 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); // 獲取請求調(diào)解:[可擴展], 如果有:該條件會在請求時匹對 RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); // 如果有RequestMapping注解,封裝成RequestMappingInfo return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); } /** * 創(chuàng)建請求映射信息的內(nèi)部邏輯 * 源碼位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMapping, RequestCondition<?>) */ protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { // 將@RequestMapping注解屬性的值構(gòu)建成一個 RequestMappingInfo RequestMappingInfo.Builder builder = RequestMappingInfo //構(gòu)建路徑 .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) //構(gòu)建方法(get還是post等) .methods(requestMapping.method()) //參數(shù) 對應http request parameter .params(requestMapping.params()) //頭部 .headers(requestMapping.headers()) //request的提交內(nèi)容類型content type,如application/json, text/html .consumes(requestMapping.consumes()) //指定返回的內(nèi)容類型的content type,僅當request請求頭中的(Accept)類型中包含該指定類型才返回 .produces(requestMapping.produces()) .mappingName(requestMapping.name()); if (customCondition != null) { builder.customCondition(customCondition); } // 構(gòu)造RequestMappingInfo:將上面的屬性構(gòu)建成一個個的RequestCondition對象方便在請求的時候組合匹對 return builder.options(this.config).build(); } /** * 得到所有的方法 * 源碼位置:org.springframework.core.MethodIntrospector.selectMethods(Class<?>, MetadataLookup<T>) */ public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { final Map<Method, T> methodMap = new LinkedHashMap<>(); Set<Class<?>> handlerTypes = new LinkedHashSet<>(); Class<?> specificHandlerType = null; //獲取原始的class對象 if (!Proxy.isProxyClass(targetType)) { specificHandlerType = ClassUtils.getUserClass(targetType); handlerTypes.add(specificHandlerType); } //獲取class的接口 handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType)); //循環(huán)我們的class集合 for (Class<?> currentHandlerType : handlerTypes) { final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); ReflectionUtils.doWithMethods(currentHandlerType, method -> { //獲取具體的方法對象 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); /**回調(diào) 即解析@RequestMapping 返回RequestMappingInfo * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)*/ T result = metadataLookup.inspect(specificMethod); if (result != null) { // 看看有沒有橋接方法:泛型實現(xiàn)類jvm會自動生成橋接類 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { //把方法對象作為key,RequestMappingInfo對象作為value保存到map中 methodMap.put(specificMethod, result); } } }, ReflectionUtils.USER_DECLARED_METHODS); } return methodMap; }
五、@RequestMapping映射源碼
/** * 獲取@RequestMapping映射 * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(HttpServletRequest) */ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 獲取當前請求對應的HandlerMethod Object handler = getHandlerInternal(request); // 獲取默認的handler if (handler == null) { handler = getDefaultHandler(); } // 還是沒有handler的時候返回null,404了 if (handler == null) { return null; } // Bean name or resolved handler? // String類型?獲取Bean if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } // 獲取攔截器相關的調(diào)用鏈 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; } /** * 獲取當前請求對應的HandlerMethod * 源碼位置:org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(HttpServletRequest) */ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); try { // 直接調(diào)用父類的getHandlerInternal方法 return super.getHandlerInternal(request); } finally { ProducesRequestCondition.clearMediaTypesAttribute(request); } } /** * 獲取當前請求對應的HandlerMethod---父類的 * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(HttpServletRequest) */ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 通過UrlPathHelper對象,用于來解析從們的request中解析出請求映射路徑 String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { // 通過lookupPath解析最終的handler——HandlerMethod對象 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } /** * 通過lookupPath解析最終的handler * 源碼位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(String, HttpServletRequest) */ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 根據(jù)uri從mappingRegistry.pathLookup獲取 RequestMappingInfo // pathLookup<path,RequestMappingInfo>會在初始化階段解析好 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { // 如果根據(jù)path能直接匹配的RequestMappingInfo 則用該mapping進行匹配其他條件(method、header等) addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // 如果無path匹配,用所有的RequestMappingInfo 通過AntPathMatcher匹配 addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { // 選擇第一個為最匹配的 Match bestMatch = matches.get(0); /** * 如果匹配到多個 @RequestMapping(value="/mappin?") @RequestMapping(value="/mappin*") @RequestMapping(value="/{xxxx}") @RequestMapping(value="/**") */ if (matches.size() > 1) { //創(chuàng)建MatchComparator的匹配器對象 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); /** 根據(jù)精準度排序 大概是這樣的: ? > * > {} >** 具體可以去看: * @see org.springframework.util.AntPathMatcher.AntPatternComparator#compare(java.lang.String, java.lang.String)*/ matches.sort(comparator); // 排完序后拿到優(yōu)先級最高的 bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } // 是否配置CORS并且匹配 if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { //獲取第二最匹配的 Match secondBestMatch = matches.get(1); //若第一個和第二個是一樣的 拋出異常 if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } //把最匹配的設置到request中 request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); //返回最匹配的 return bestMatch.getHandlerMethod(); } else { // return null return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } }
到此這篇關于java spring mvc處理器映射器介紹的文章就介紹到這了,更多相關spring mvc處理器映射器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用SpringBoot打jar包并部署到Tomcat詳細步驟
今天帶大家來學習怎么使用SpringBoot打jar包并部署到Tomcat,文中有非常詳細的步驟及代碼示例,對正在學習java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05SpringCloud中分析講解Feign組件添加請求頭有哪些坑梳理
在spring?cloud的項目中用到了feign組件,簡單配置過后即可完成請求的調(diào)用。又因為有向請求添加Header頭的需求,查閱了官方示例后,就覺得很簡單,然后一頓操作之后調(diào)試報錯...下面我們來詳細了解2022-06-06SpringBoot內(nèi)部調(diào)用事務不起作用問題的解決方案
這篇文章主要介紹了SpringBoot事務不起作用問題的解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10java 避免出現(xiàn)NullPointerException(空指針)的方法總結(jié)
這篇文章主要介紹了java 避免出現(xiàn)NullPointerException(空指針)的方法總結(jié)的相關資料,需要的朋友可以參考下2017-09-09Java爬蟲范例之使用Htmlunit爬取學校教務網(wǎng)課程表信息
htmlunit 是一款開源的java 頁面分析工具,讀取頁面后,可以有效的使用htmlunit分析頁面上的內(nèi)容。項目可以模擬瀏覽器運行,被譽為java瀏覽器的開源實現(xiàn)。今天我們用這款分析工具來爬取學校教務網(wǎng)課程表信息2021-11-11