SpringMVC源碼之HandlerMapping處理器映射器解析
1. 什么是HandlerMapping
在Spring MVC中,HandlerMapping(處理器映射器)用于確定請求處理器對象。請求處理器可以是任何對象,只要它們使用了@Controller注解或注解@RequestMapping。
HandlerMapping負(fù)責(zé)將請求(url)映射到適當(dāng)?shù)奶幚砥鲗ο螅–ontroller)。
注:Handler即綁定了注解@RequestMapping或@Controller的類
HandlerMapping接口定義了一個(gè)方法:
public interface HandlerMapping { HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
getHandler方法用于查找處理器對象并返回處理程序的執(zhí)行鏈,HandlerExecutionChain包含了處理器對象和一系列攔截器,這些攔截器可以在處理程序執(zhí)行之前和之后執(zhí)行一些操作。
HandlerMapping接口繼承結(jié)構(gòu)體系
2. HandlerMapping
2.1 HandlerMapping初始化
Http請求由servlet容器分發(fā)到DispatcherServlet,在其中會(huì)進(jìn)行九大核心組件的初始化
protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context); this.initHandlerMappings(context); this.initHandlerAdapters(context); this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context); }
這里我們主要關(guān)注 initHandlerMappings
public class DispatcherServlet extends FrameworkServlet { private void initHandlerMappings(ApplicationContext context) {...} } private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; //private boolean detectAllHandlerMappings = true; //該值默認(rèn)為 true,查詢所有的handlerMapping // 如果設(shè)置為 false , Spring MVC就只會(huì)查找名為“handlerMapping”的bean,并作為當(dāng)前系統(tǒng)的唯一的HandlerMapping if (this.detectAllHandlerMappings) { //在ApplicationContext中查找所有handler映射,包括父類上下文。 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); //存在Handler if (!matchingBeans.isEmpty()) { //將獲取的 HandlerMapping 轉(zhuǎn)換成集合并排序 this.handlerMappings = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { //沒有找到 try { //查找名為“handlerMapping”的bean,并作為當(dāng)前系統(tǒng)的唯一的HandlerMapping,確保至少有一個(gè)HandlerMapping HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException var4) { } } this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class); } Iterator var6 = this.handlerMappings.iterator(); //迭代HandlerMapping while(var6.hasNext()) { HandlerMapping mapping = (HandlerMapping)var6.next(); //判斷HandlerMapping實(shí)例是否啟用了解析的PathPatterns //如果啟用則DispatcherServlet會(huì)自動(dòng)解析RequestPath,以便在HandlerMappings,HandlerInterceptors和其他組件中訪問 if (mapping.usesPathPatterns()) { this.parseRequestPath = true; break; } } }
Spring MVC框架中有多個(gè)HandlerMapping實(shí)現(xiàn),每個(gè)實(shí)現(xiàn)都可以使用不同的策略來確定請求處理器對象。
最常見的HandlerMapping實(shí)現(xiàn)是RequestMappingHandlerMapping和SimpleUrlHandlerMapping。
2.2 getHandler解析
getHandler方法根據(jù)請求找到對應(yīng)的處理器對象, 在 DispatcherServlet 類中, doDispatch() 方法調(diào)用 getHandler() 方法得到 HandlerExecutionChain 對象。
注: HandlerExecutionChain 是一個(gè)類,用于封裝處理器對象和攔截器列表。它是 HandlerMapping 的 getHandler 方法的返回值。
HandlerExecutionChain 的作用是在請求處理流程中,提供一種責(zé)任鏈模式(看圖),讓攔截器可以在處理器執(zhí)行前后進(jìn)行一些額外的操作,例如驗(yàn)證、日志、事務(wù)等。
拿到這個(gè)對象后,DispatcherServlet會(huì)調(diào)用它的applyPreHandle方法,執(zhí)行所有攔截器的preHandle方法。如果都返回true,則繼續(xù)調(diào)用處理器的方法;否則,返回響應(yīng)或者跳轉(zhuǎn)到其他頁面。
處理器執(zhí)行完畢后,DispatcherServlet會(huì)調(diào)用它的applyPostHandle方法,執(zhí)行所有攔截器的postHandle方法。這些方法可以對模型和視圖進(jìn)行修改或增強(qiáng)。
最后,在渲染視圖之后,DispatcherServlet會(huì)調(diào)用它的triggerAfterCompletion方法,執(zhí)行所有攔截器的afterCompletion方法。這些方法可以進(jìn)行一些清理工作或異常處理
源碼解析:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { mappedHandler = this.getHandler(processedRequest); }
//返回一個(gè)HandlerExecutionChain對象 @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { Iterator var2 = this.handlerMappings.iterator(); //遍歷所有的初始化的HandlerMapping列表 while(var2.hasNext()) { HandlerMapping mapping = (HandlerMapping)var2.next(); //獲取一個(gè)HandlerExecutionChain對象,它包含了處理請求的handler對象和任何配置的攔截器 //接口方法 HandlerExecutionChain handler = mapping.getHandler(request); //匹配到就進(jìn)行返回 if (handler != null) { return handler; } } } return null; }
實(shí)現(xiàn)類為 AbstractHandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware { public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {……}; } @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 根據(jù)請求從緩存中查找handler,如果沒有則調(diào)用子類實(shí)現(xiàn)的getHandlerInternal()方法 該方法在本類中有定義,是一個(gè)protected型的抽象方法 Object handler = this.getHandlerInternal(request); // 沒有找到,則會(huì)使用getDefaultHandler()方法獲取默認(rèn)的處理器 if (handler == null) { handler = this.getDefaultHandler(); } if (handler == null) { return null; } else { //檢查handler對象是否是一個(gè)字符串類型。如果是的話,就從應(yīng)用上下文中根據(jù)字符串名稱獲取對應(yīng)的bean作為handler對象 if (handler instanceof String) { String handlerName = (String)handler; handler = this.obtainApplicationContext().getBean(handlerName); } //如果請求對象中沒有緩存的路徑信息,就調(diào)用initLookupPath方法來解析請求的路徑信息,并且保存在請求對象的一個(gè)屬性中 if (!ServletRequestPathUtils.hasCachedPath(request)) { this.initLookupPath(request); } //根據(jù)handler對象和請求創(chuàng)建一個(gè)HandlerExecutionChain對象,包含處理器對象和攔截器列表 HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request); return executionChain; } }
到此為止就獲得了executionChain對象實(shí)例了,也就是第一張圖的第4步。
3. getHandlerInternal()子類實(shí)現(xiàn)
getHandlerInternal() 方法在 AbstractUrlHandlerMapping 類和 AbstractHandlerMethodMapping 類中均有實(shí)現(xiàn), 均繼承于AbstractHandlerMapping類
3.1 AbstractUrlHandlerMapping與AbstractHandlerMethodMapping的區(qū)別
AbstractUrlHandlerMapping 是基于URL映射的 HandlerMapping ,它支持字面匹配和模式匹配,如"/test/*“,”/test/"等, 它返回的Handler是一個(gè)類級別的對象**,例如一個(gè)Controller類或一個(gè)Bean對象
AbstractHandlerMethodMapping 是基于方法級別的 HandlerMapping ,它支持使用@RequestMapping注解來指定請求路徑和方法。它返回的Handler是一個(gè)方法級別的對象,例如一個(gè)Controller類中的某個(gè)方法或一個(gè)Bean對象中的某個(gè)方法
示例:
AbstractUrlHandlerMapping 的一個(gè)子類是 SimpleUrlHandlerMapping ,它可以在XML配置文件中定義URL和Handler的映射關(guān)系
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/test.do">testController</prop> </props> </property> </bean> <bean id="testController" class="com.example.TestController"/>
AbstractHandlerMethodMapping 的一個(gè)子類是 RequestMappingHandlerMapping ,它可以在Java類中使用@RequestMapping注解來定義URL和方法的映射關(guān)系
@Controller @RequestMapping("/test") public class TestController { @RequestMapping("/do") public String doSomething() { // ... } }
這兩種方式都可以實(shí)現(xiàn)URL和Handler的綁定,但是后者更靈活和簡潔
3.2 AbstractUrlHandlerMapping
//根據(jù)用戶請求信息中的URL查找handler protected Object getHandlerInternal(HttpServletRequest request) throws Exception { //初始化請求的查找路徑,也就是去掉上下文路徑和后綴名的URL路徑 String lookupPath = this.initLookupPath(request); Object handler; //是否使用路徑模式來選擇不同的查找handler的方法 // if (this.usesPathPatterns()) { //獲取請求的RequestPath對象,它封裝了請求的URL路徑、上下文路徑、后綴名等信息。 RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); //根據(jù)RequestPath對象、查找路徑和請求對象,調(diào)用lookupHandler()方法來查找匹配的handler handler = this.lookupHandler(path, lookupPath, request); } else { handler = this.lookupHandler(lookupPath, request); } //沒有找到匹配的handler時(shí),嘗試獲取根handler或者默認(rèn)handler,并進(jìn)行驗(yàn)證和包裝。 if (handler == null) { Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = this.getRootHandler(); } if (rawHandler == null) { rawHandler = this.getDefaultHandler(); } if (rawHandler != null) { if (rawHandler instanceof String) { String handlerName = (String)rawHandler; rawHandler = this.obtainApplicationContext().getBean(handlerName); } this.validateHandler(rawHandler, request); handler = this.buildPathExposingHandler(rawHandler, lookupPath, lookupPath, (Map)null); } } //返回獲取到的handler對象 return handler; }
3.3 AbstractHandlerMethodMapping
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //初始化請求的查找路徑,也就是去掉上下文路徑和后綴名的URL路徑 String lookupPath = this.initLookupPath(request); //獲取映射注冊表的讀鎖,以保證線程安全 this.mappingRegistry.acquireReadLock(); HandlerMethod var4; //獲取讀鎖后,嘗試查找匹配的handler方法,并在最后釋放讀鎖 try { //根據(jù)查找路徑和請求對象,在映射注冊表中查找匹配的handler方法 HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request); //判斷是否找到了handler方法 //如果找到了,則調(diào)用createWithResolvedBean()方法來創(chuàng)建一個(gè)新的HandlerMethod實(shí)例,并解析其關(guān)聯(lián)的bean對象; //如果沒有找到,則返回null。 var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null; } finally { this.mappingRegistry.releaseReadLock(); } //返回獲取到的handler對象 return var4; }
下面簡單介紹一下AbstractUrlHandlerMapping與AbstractHandlerMethodMapping的常用子類實(shí)現(xiàn)
4. RequestMappingHandlerMapping
RequestMappingHandlerMapping 是Spring MVC中最常用的HandlerMapping實(shí)現(xiàn)之一。
它使用 @RequestMapping 注解來確定請求處理器對象。
下面是一個(gè)簡單的控制器示例:
@Controller @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public String getUser(@PathVariable int id, Model model) { User user = userRepository.findById(id); model.addAttribute("user", user); return "user"; } }
在這個(gè)示例中, UserController 類被@Controller注解標(biāo)記為一個(gè)控制器,并且所有請求路徑以/user開頭。
@GetMapping注解用于處理HTTP GET請求,{id}是一個(gè)路徑變量,它將匹配任何非空字符串,并將其解析為一個(gè)整數(shù)。
getUser方法將通過id獲取用戶對象并將其添加到模型中,最后返回一個(gè)視圖名為"user"的字符串。
RequestMappingHandlerMapping 會(huì)掃描所有@Controller注解標(biāo)記的類,并將所有帶有@RequestMapping注解的方法添加到HandlerMapping中。
在請求到達(dá)服務(wù)器時(shí), RequestMappingHandlerMapping 將匹配請求路徑和請求方法,并將請求映射到適當(dāng)?shù)奶幚砥鞣椒ā?/p>
4.1 加載過程
- RequestMappingHandlerMapping 實(shí)現(xiàn)了接口 InitializingBean ,在bean加載完成后會(huì)自動(dòng)調(diào)用 afterPropertiesSet 方法,在此方法中調(diào)用了initHandlerMethods()來實(shí)現(xiàn)初始化
- initHandlerMethods() 會(huì)遍歷所有bean,如果bean實(shí)現(xiàn)帶有注解@Controller或者@RequestMapping 則進(jìn)一步調(diào)用 detectHandlerMethods 處理,處理邏輯大致就是根據(jù)@RequestMapping配置的信息,把解析結(jié)果封裝成 RequestMappingInfo 對象,也就是說 RequestMappingInfo 對象是用來裝載方法的匹配相關(guān)信息,每個(gè)匹配的方法都會(huì)對應(yīng)一個(gè) RequestMappingInfo 對象,然后注冊到 MappingRegistry 中
具體流程:
- 當(dāng)Spring容器啟動(dòng)時(shí),它會(huì)掃描所有帶有@Controller或@RestController注解的類,并將它們作為處理器對象注冊到RequestMappingHandlerMapping中。
- RequestMappingHandlerMapping會(huì)遍歷每個(gè)處理器對象中的所有方法,并使用getMappingForMethod()方法來獲取每個(gè)方法上定義或繼承的@RequestMapping注解,然后將這些注解轉(zhuǎn)換為RequestMappingInfo對象,包含了請求路徑、請求方法、請求參數(shù)、請求頭等信息。
- RequestMappingHandlerMapping會(huì)將每個(gè)RequestMappingInfo對象和對應(yīng)的處理器方法封裝成一個(gè)HandlerMethod對象,并將這些對象存儲在一個(gè)Map結(jié)構(gòu)中,以便于后續(xù)查找。
- 當(dāng)一個(gè)HTTP請求到達(dá)DispatcherServlet時(shí),它會(huì)調(diào)用RequestMappingHandlerMapping的getHandler()方法來根據(jù)請求URI找到匹配的處理器方法。
- RequestMappingHandlerMapping會(huì)遍歷Map中的所有鍵值對(即每個(gè)RequestMappingInfo和HandlerMethod),并使用PathMatcher或PathPatternParser來判斷請求URI是否與RequestMappingInfo中定義的路徑匹配。如果匹配,就返回對應(yīng)的HandlerMethod;如果不匹配,就繼續(xù)查找下一個(gè)鍵值對。
- 如果找到了匹配的HandlerMethod,DispatcherServlet就會(huì)根據(jù)其類型選擇合適的HandlerAdapter來執(zhí)行它,并返回響應(yīng)結(jié)果;如果沒有找到匹配的HandlerMethod,DispatcherServlet就會(huì)拋出異?;蛘叻祷?04錯(cuò)誤頁面。
步驟1的注冊過程
public void afterPropertiesSet() { //…… super.afterPropertiesSet(); }
//父類AbstractHandlerMethodMapping 的方法 public void afterPropertiesSet() { this.initHandlerMethods(); } //掃描容器中的bean,檢測和注冊handler方法 protected void initHandlerMethods() { //獲取候選的bean名稱數(shù)組 String[] var1 = this.getCandidateBeanNames(); int var2 = var1.length; //遍歷 for(int var3 = 0; var3 < var2; ++var3) { String beanName = var1[var3]; //用于過濾掉以"scopedTarget."開頭的bean名稱的。 //這些bean名稱是由Spring創(chuàng)建的代理對象,用于支持不同作用域的bean //例如session或request。這些代理對象不是真正的handler,所以要排除掉。 if (!beanName.startsWith("scopedTarget.")) { //處理候選的bean,判斷候選的bean是否是一個(gè)handler,也就是是否有@Controller注解或者@RequestMapping注解 //如果是的話,就調(diào)用detectHandlerMethods方法,用于檢測和注冊handler方法 this.processCandidateBean(beanName); } } //初始化handler方法, 對所有handler方法進(jìn)行排序和日志輸出 this.handlerMethodsInitialized(this.getHandlerMethods()); } //檢測和注冊handler方法 protected void detectHandlerMethods(Object handler) { //存儲handler對象的類信息 //如果handler是一個(gè)字符串,那么就使用應(yīng)用程序上下文來獲取對應(yīng)的類類型;否則就直接使用handler.getClass()方法來獲取類類型。 Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass(); if (handlerType != null) { //存儲handlerType去除代理和增強(qiáng)后的原始類類型。這是為了避免AOP對方法檢測造成干擾。 Class<?> userType = ClassUtils.getUserClass(handlerType); //存儲userType中所有帶有映射注解(如@RequestMapping)的方法及其對應(yīng)的映射信息 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> { try { //獲取每個(gè)方法上定義或繼承的映射信息 return this.getMappingForMethod(method, userType); } catch (Throwable var4) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4); } }); //遍歷methods中每個(gè)鍵值對(即每個(gè)方法及其映射信息) methods.forEach((method, mapping) -> { //存儲經(jīng)過AopUtils.selectInvocableMethod()方法處理后可以被調(diào)用(即沒有被final修飾) //且與userType匹配(即沒有被覆蓋) 的原始或橋接(即泛型擦除后生成) 方法 Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); //注冊到處理器映射 this.registerHandlerMethod(handler, invocableMethod, mapping); }); } }
注: RequestMappingInfo 類,主要用來記錄方法上 @RequestMapping() 注解里面的參數(shù),針對 RequestMappingHandlerMapping 映射器來使用。
步驟2: RequestMappingHandlerMapping 會(huì)遍歷每個(gè)處理器對象中的所有方法,并使用 getMappingForMethod() 方法來獲取每個(gè)方法上定義或繼承的@RequestMapping注解,然后將這些注解轉(zhuǎn)換為 RequestMappingInfo 對象,包含了請求路徑、請求方法、請求參數(shù)、請求頭等信息。
//根據(jù)處理器類和方法上的@RequestMapping注解來創(chuàng)建一個(gè)RequestMappingInfo對象(封裝了請求映射的信息,如請求路徑、請求方法、請求參數(shù)、請求頭等) protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { //獲取方法上定義或繼承的@RequestMapping注解,并將其轉(zhuǎn)換為一個(gè)RequestMappingInfo對象 RequestMappingInfo info = this.createRequestMappingInfo(method); if (info != null) { //獲取處理器類上定義或繼承的@RequestMapping注解,并將其轉(zhuǎn)換為另一個(gè)RequestMappingInfo對象 RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType); if (typeInfo != null) { //合并兩個(gè)RequestMappingInfo對象 info = typeInfo.combine(info); } //獲取處理器類上定義或繼承的@PathPrefix注解,并將其轉(zhuǎn)換為一個(gè)字符串前綴 String prefix = this.getPathPrefix(handlerType); if (prefix != null) { //根據(jù)指定的路徑前綴和配置選項(xiàng)來構(gòu)建一個(gè)新的請求映射信息,并且與原有信息進(jìn)行合并 info = RequestMappingInfo.paths(new String[]{prefix}).options(this.config).build().combine(info); } } return info; }
步驟3: RequestMappingHandlerMapping 會(huì)將每個(gè) RequestMappingInfo 對象和對應(yīng)的處理器方法封裝成一個(gè) HandlerMethod 對象,并將這些對象存儲在一個(gè)Map結(jié)構(gòu)中,以便于后續(xù)查找。
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { super.registerHandlerMethod(handler, method, mapping); this.updateConsumesCondition(mapping, method); } //AbstractHandlerMethodMapping類中的方法 protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { try { // 創(chuàng)建HandlerMethod對象 HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method); this.validateMethodMapping(handlerMethod, mapping); //獲取匹配條件對應(yīng)的直接路徑,添加到pathLookup中 Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); Iterator var6 = directPaths.iterator(); while(var6.hasNext()) { String path = (String)var6.next(); this.pathLookup.add(path, mapping); } //如果有命名策略,獲取handler方法的名稱,添加到nameLookup中 String name = null; if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) { name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping); this.addMappingName(name, handlerMethod); } //將匹配條件和MappingRegistration對象(封裝了handler方法、直接路徑、名稱、跨域配置等信息)添加到registry中 this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directPaths, name, corsConfig != null)); } finally { } }
步驟4:當(dāng)一個(gè)HTTP請求到達(dá) DispatcherServlet 時(shí),它會(huì)調(diào)用 RequestMappingHandlerMapping 的 getHandler() 方法來根據(jù)請求URI找到匹配的處理器方法。(即3.3節(jié)提到的 AbstractHandlerMethodMapping 父類中的 getHandler() 方法)
步驟5: RequestMappingHandlerMapping 會(huì)遍歷Map中的所有鍵值對(即每個(gè) RequestMappingInfo和HandlerMethod ),并使用 PathMatcher 或 PathPatternParser 來判斷請求URI是否與 RequestMappingInfo 中定義的路徑匹配。如果匹配,就返回對應(yīng)的 HandlerMethod ;如果不匹配,就繼續(xù)查找下一個(gè)鍵值對。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { // 創(chuàng)建一個(gè)空的Match列表,用來存放匹配的RequestMappingInfo和HandlerMethod對象 List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList(); // 從mappingRegistry中獲取直接路徑匹配(即沒有通配符或變量)的RequestMappingInfo列表 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { // 調(diào)用addMatchingMappings方法,判斷直接路徑匹配的RequestMappingInfo是否與請求條件匹配 //如果匹配,就添加到matches列表中 this.addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // 如果matches列表為空,說明沒有直接路徑匹配的RequestMappingInfo與請求條件匹配 //那么就從mappingRegistry中獲取所有注冊過的RequestMappingInfo, //并調(diào)用addMatchingMappings方法判斷是否與請求條件匹配 //如果匹配,就添加到matches列表中 this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (matches.isEmpty()) { // 如果matches列表仍然為空,說明沒有任何一個(gè)RequestMappingInfo與請求條件匹配 //那么就調(diào)用handleNoMatch方法處理沒有匹配結(jié)果的情況,并返回null或拋出異常 return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } else { // 如果matches列表不為空,說明至少有一個(gè)RequestMappingInfo與請求條件匹配 // 那么就從matches列表中獲取第一個(gè)Match對象(即最先添加進(jìn)去的),作為最佳匹配結(jié)果 AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0); if (matches.size() > 1) { // 如果matches列表中有多個(gè)Match對象(即有多個(gè)RequestMappingInfo與請求條件匹配) //那么就需要進(jìn)行排序和篩選 // 首先創(chuàng)建一個(gè)比較器comparator,根據(jù)getMappingComparator方法返回的比較器對每個(gè)Match對象進(jìn)行比較(主要比較它們包含的RequestMappingInfo) Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request)); // 然后對matches列表進(jìn)行排序,根據(jù)comparator比較器確定每個(gè)Match對象之間的優(yōu)先級和順序 matches.sort(comparator); // 再次從matches列表中獲取第一個(gè)Match對象(即排序后最優(yōu)先級最高、順序最靠前、 //最符合請求條件、最具體化、最少參數(shù)化等等)作為最佳匹配結(jié)果 bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0); } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); this.handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.getHandlerMethod(); } }
步驟6:如果找到了匹配的 HandlerMethod , DispatcherServlet 就會(huì)根據(jù)其類型選擇合適的 HandlerAdapter 來執(zhí)行它,并返回響應(yīng)結(jié)果;如果沒有找到匹配的 HandlerMethod , DispatcherServlet 就會(huì)拋出異?;蛘叻祷?04錯(cuò)誤頁面。
5. SimpleUrlHandlerMapping
SimpleUrlHandlerMapping 是另一個(gè)常用的 HandlerMapping 實(shí)現(xiàn)。
它允許指定URL模式和Handler的映射關(guān)系。
下面是一個(gè)簡單的SimpleUrlHandlerMapping示例:
public class MySimpleUrlHandlerMapping extends SimpleUrlHandlerMapping { public MySimpleUrlHandlerMapping() { Properties mappings = new Properties(); mappings.setProperty("/hello", "helloController"); setMappings(mappings); } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 MySimpleUrlHandlerMapping 的自定義 HandlerMapping 類。我們通過創(chuàng)建一個(gè) Properties 對象并將請求路徑"/hello"映射到控制器名為"helloController"來設(shè)置URL映射。
當(dāng)請求到達(dá)服務(wù)器時(shí), SimpleUrlHandlerMapping 將查找請求路徑并將其與已注冊的URL模式進(jìn)行匹配。如果找到匹配項(xiàng),則返回關(guān)聯(lián)的處理程序?qū)ο蟆?/p>
下面是一個(gè)使用SimpleUrlHandlerMapping的控制器示例:
public class HelloController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mav = new ModelAndView("hello"); mav.addObject("message", "Hello, World!"); return mav; } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 HelloController 的控制器類,并實(shí)現(xiàn)了Controller接口。 handleRequest 方法將返回一個(gè)名為"hello"的視圖,并將一個(gè)名為"message"的字符串添加到模型中。
我們將 HelloController 添加到 MySimpleUrlHandlerMapping 中:
MySimpleUrlHandlerMapping handlerMapping = new MySimpleUrlHandlerMapping(); Map<String, Object> urlMap = new HashMap<>(); urlMap.put("/hello", new HelloController()); handlerMapping.setUrlMap(urlMap);
在這個(gè)示例中,我們將 HelloController 添加到 MySimpleUrlHandlerMapping 的URL映射中,并將其注冊到Spring MVC應(yīng)用程序上下文中。
介紹一下這個(gè)類中的一個(gè)重要方法 registerHandlers() , 這個(gè)方法是在Spring框架中用來注冊URL映射器的,它可以將URL模式和請求處理器(handler)關(guān)聯(lián)起來
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { //不作操作 } else { //遍歷urlMap中的每一對鍵值對,鍵是URL模式,值是handler對象 urlMap.forEach((url, handler) -> { //對于每一個(gè)URL模式,如果它沒有以斜杠(/)開頭,則在前面加上一個(gè)斜杠 if (!url.startsWith("/")) { url = "/" + url; } //對于每一個(gè)handler對象,如果它是一個(gè)字符串,則去掉字符串兩端的空白字符 if (handler instanceof String) { handler = ((String)handler).trim(); } //調(diào)用父類AbstractUrlHandlerMapping的registerHandler方法,將URL模式和handler對象注冊到內(nèi)部的handlerMap中 this.registerHandler(url, handler); }); } }
6. 使用HandlerMapping
在Spring MVC中,使用 HandlerMapping 非常簡單。在控制器中,您可以使用@Autowired注解來注入 HandlerMapping 實(shí)現(xiàn),并使用它來查找適當(dāng)?shù)奶幚沓绦驅(qū)ο蟆?/p>
@Controller public class MyController { @Autowired private HandlerMapping handlerMapping; @RequestMapping("/my/path") public String handleRequest(HttpServletRequest request) throws Exception { HandlerExecutionChain chain = handlerMapping.getHandler(request); Object handler = chain.getHandler(); // ... } }
在這個(gè)示例中,我們使用@Autowired注解將HandlerMapping注入到控制器中。在handleRequest方法中,我們使用HandlerMapping的getHandler方法來獲取請求的處理程序?qū)ο?。我們可以進(jìn)一步操作處理程序?qū)ο?,例如調(diào)用其方法或檢查其注解。
6.1 如何選擇合適的HandlerMapping
SpringMVC提供了多種HandlerMapping實(shí)現(xiàn)類,你可以根據(jù)不同的方式來配置URL和Handler的映射關(guān)系,例如:
- 使用XML配置文件來定義SimpleUrlHandlerMapping或BeanNameUrlHandlerMapping,這種方式比較傳統(tǒng),但是可以集中管理所有的映射關(guān)系,也可以自定義攔截器。
- 使用@RequestMapping注解來定義RequestMappingHandlerMapping,這種方式比較流行,但是需要在每個(gè)Controller類或方法上添加注解,也可以使用@PathVariable或@RequestParam等注解來獲取請求參數(shù)。
- 使用@ControllerAdvice注解來定義ExceptionHandlerExceptionResolver,這種方式可以用來統(tǒng)一處理異常情況,也可以使用@ExceptionHandler或@ResponseStatus等注解來自定義異常處理邏輯。
你可以根據(jù)你的喜好和需求來選擇一種或多種HandlerMapping實(shí)現(xiàn)類,并且可以通過設(shè)置order屬性來調(diào)整它們的優(yōu)先級
7. 總結(jié)
HandlerMapping是Spring MVC中非常重要的一個(gè)組件,它負(fù)責(zé)將請求映射到適當(dāng)?shù)奶幚沓绦驅(qū)ο蟆?/p>
RequestMappingHandlerMapping和SimpleUrlHandlerMapping是兩個(gè)常用的HandlerMapping實(shí)現(xiàn)。
RequestMappingHandlerMapping使用@RequestMapping注解來確定處理程序?qū)ο螅鳶impleUrlHandlerMapping使用URL模式來確定處理程序?qū)ο蟆?/p>
到此這篇關(guān)于SpringMVC源碼之HandlerMapping處理器映射器解析的文章就介紹到這了,更多相關(guān)SpringMVC的HandlerMapping內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot之解決多個(gè)定時(shí)任務(wù)阻塞的問題
這篇文章主要介紹了SpringBoot之解決多個(gè)定時(shí)任務(wù)阻塞的問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04Java語言的11大特點(diǎn)(Java初學(xué)者必知)
Java是一種簡單的,面向?qū)ο蟮?,分布式的,解釋型的,健壯安全的,結(jié)構(gòu)中立的,可移植的,性能優(yōu)異、多線程的靜態(tài)語言。這篇文章主要介紹了Java語言的11大特點(diǎn),需要的朋友可以參考下2020-07-07java編程實(shí)現(xiàn)郵件定時(shí)發(fā)送的方法
這篇文章主要介紹了java編程實(shí)現(xiàn)郵件定時(shí)發(fā)送的方法,涉及Java基于定時(shí)器實(shí)現(xiàn)計(jì)劃任務(wù)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11SpringBoot?docker項(xiàng)目部署實(shí)戰(zhàn)
本文主要介紹了SpringBoot?docker項(xiàng)目部署實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08關(guān)于使用Lambda表達(dá)式簡化Comparator的使用問題
這篇文章主要介紹了關(guān)于使用Lambda表達(dá)式簡化Comparator的使用問題,文中圖文講解了Comparator對象的方法,需要的朋友可以參考下2023-04-04