springboot中的HandlerMapping
在上篇文章中已經說明HandlerMapping
的作用是根據當前請求request
獲取一個包含當前請求處理器handler
的HandlerExecutionChain
對象。handler
經HandlerApapter
適配後,可以將handler
轉換爲一個特定的對象,以此確定哪個類的哪個個方法來處理該請求。springboot中默認註冊的HandlerMapping
有:RequestMappingHandlerMapping
,BeanNameUrlHandlerMapping
,SimpleUrlHandlerMapping
,RouterFunctionMapping
,在這裏我們只說前三種。
RequestMappingHandlerMapping
這個就是我們常見的基於註解的映射方式,例如:
@Controller
@RequestMapping("/testA")
public class MappingTest1 {
@ResponseBody
@RequestMapping("/index")
public String index(){
return "RequestMappingHandlerMapping test!";
}
}
對於加上上面的註解後,簡單解釋一下我們直接可以訪問localhost/testA/index
的原因:在RequestMappingHandlerMapping
的源碼中有一段代碼如下:
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
springboot
在初始化RequestMappingHandlerMapping
時,會掃描容器中的bean
,判斷它上面是否存在@Controller
或@RequestMapping
兩種註解,通過上面的方法,判斷該bean
是否是一個handler
,如果是,則會將其註冊到RequestMappingHandlerMapping
,用來處理和它匹配的請求。通過上面的方法我們還可以發現,@Controller
註解不是必須的,我們還可以寫成下面的方式:
@Component
@RequestMapping("/testA")
public class MappingTest1 {
@ResponseBody
@RequestMapping("/index")
public String index(){
return "RequestMappingHandlerMapping test!";
}
}
SimpleUrlHandlerMapping
這種方式直接通過簡單的url匹配的方式將其映射到一個處理器。首先像容器註冊一個自定義的SimpleUrlHandlerMapping
@Configuration
public class MyConfig extends SimpleUrlHandlerMapping{
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(){
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Properties properties = new Properties();
properties.setProperty("simpleUrl","mappingTest2");
simpleUrlHandlerMapping.setMappings(properties);
//設置該handlermapping的優先級爲1,否則會被默認的覆蓋,導致訪問無效
simpleUrlHandlerMapping.setOrder(1);
return simpleUrlHandlerMapping;
}
}
定義一個名稱爲mappingTest2
的bean
,並實現org.springframework.web.servlet.mvc.Controller
接口
@Component("mappingTest2")
public class MappingTest2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("SimpleUrlHandlerMapping test!");
return null;
}
}
在這個例子中,我們訪問localhost/simpleUrl
就會直接進入容器中名稱爲mappingTest2
的bean
的handleRequest
方法。
BeanNameUrlHandlerMapping
這個最簡單:直接以bean
的名稱作爲訪問路徑,但有個硬性條件就是bean
的名稱必須以/
開始。
@Component("/mappingTest3")
public class MappingTest3 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("BeanNameUrlHandlerMapping test!");
return null;
}
}
在該例子中,訪問方式爲localhost/mappingTest3
爲什麼必須以bean
的名稱必須以/
開頭呢,我們看BeanNameUrlHandlerMapping
的源碼可以發現,它判斷是否是一個handler
的依據就是該bean
的名稱是否以/
開頭。如下:
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
上面的兩個例子都實現了Controller
接口,爲什麼要實現Controller
接口,必須要實現Controller
嗎?這個後面再進行分析。