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
吗?这个后面再进行分析。