DispatcherServlet 通过 HandlerMapping 查找到 Handler,然后委托 HandlerAdapter 去执行 Handler,生成 ModelAndView。
回想下,日常的开发中,我们需要针对 HttpServeletRequest 做各种处理,我们的处理逻辑,其实就是一个 Handler。
有了Handler 之后,我们还需要配置什么时候,DispatcherServlet 需要去使用这些 Handler 处理 HttpServeletRequest,HandlerMapping 就是提供了url 和 Handler 的映射。这样,DispatcherServlet 可以通过 HandlerMapping 获取到对应的 Handler ,并执行 Handler 中,我们定义的业务逻辑。
那么 HandlerAdapter 的作用呢?其实是为了使 DispatcherServlet 不关注具体的 Handler。考虑下面的场景,@RequestMapping 修饰的方法和实现Controller 接口的类,其实都是 Handler,这两种 Handler 有很大不同,那么如何使得 DispatcherServlet 可以直接调用 Handler 的逻辑,而不需要关注 Handler的形态呢?这个时候可以使用「适配器模式」,为各种 Handler 提供不同的 HanderAdapter,DispatcherServlet 只需要调用 HandlerAdapter 的方法即可,不需要关注 HandlerAdapter 是如何通过各种形态的 Handler 去执行业务逻辑的。
日常开发中,我们一般只需要配置 Handler 和 HandlerMapping,因为 springmvc 已经提供了常用的 HandlerAdapter,一般情况下,我们不需要指定 HandlerAdapter(除非需要自定义 Handler 的执行)。
Handler 定位是处理 HttpServletRequest。Handler 只是一个概念,springmvc 并没有设计统一的接口,进行规范。
springmvc 内置的 Handler:
handler | 作用(场景) |
---|---|
@RequestMapping方式 | 一个@RequestMapping 注解修饰的方法,可以看作一个 Handler(HandlerMethod(InvocableHandlerMethod)) |
实现HttpRequestHandler接口 (ResourceHttpRequestHandler) | 实现 HttpRequestHandler 接口的类,可以看作是一个 Handler,常用的是 springmvc 提供的 ResourceHttpRequestHandler ,ResourceHttpRequestHandler 可以处理静态资源请求 |
实现Controller 接口 | 实现Controller 接口的类,可以看作是 Handler。由于这种方式比较古老,不做过多纠结 |
HandlerMapping 定位是通过 url,匹配到合适的 Handler,并将 Handler 和 拦截器链捆绑在 HandlerExecutionChain 对象中,返回给调用方。
上文可以看出:一个handler可能是一个方法,也可能是一个 Controller 对象或者 HttpRequestHandler 对象。
针对这种情况, HandlerMapping分为两个分支来处理
它们又统一继承于 AbstractHandlerMapping。
springmvc 内置的 AbstractHandlerMethodMapping 实现类
HandlerMapping | 作用(场景) |
---|---|
RequestMappingHandlerMapping | 用于处理 @RequestMapping 修饰的方法 Handler |
springmvc 内置的 AbstractUrlHandlerMapping 实现类
HandlerMapping | 作用(场景) |
---|---|
SimpleUrlHandlerMapping | 针对已经注册到 SimpleUrlHandlerMapping 的 urlMap 中的所有的映射注册 handler。(注册时机:容器初始化之后) |
BeanNameUrlHandlerMapping | 针对容器中,所有以"/" 开头的,beanName,进行 handler 注册(注册 handler 时机:容器初始化之后) |
这里需要注意下,SimpleUrlHandlerMapping 只会注册其 urlMap 属性中包含的 handler,而 BeanNameUrlHandlerMapping 则是针对整个容器中 beanName 以"/"开头的 bean,进行 Handler 注册。
HandlerAdapter 的定位是,调用 Handler 处理请求。
Springmvc 提供的 HandlerAdapter 实现
HandlerAdapter | 作用(场景) |
---|---|
HttpRequestHandlerAdapter | 用来调用实现了 HttpRequestHandler 接口的 handler 类 |
SimpleControllerHandlerAdapter | 用来调用实现了 Controller 接口的 handler 类 |
RequestMappingHandlerAdapter | 用来调用 @RequestMapping 修饰的 handler 方法 |
场景:如果我们需要将 html 文件以及 css img 等静态文件从 servlet 服务器中返回,其实我们不需要自定义 handler,直接使用 Spring 提供的 ResourceHttpRequestHandler 即可做到
将 ResourceHttpRequestHandler 配置为一个 Spring 容器中的 bean,不过需要注意,bean 的名字,必须以"/" 开头,这是因为,BeanNameUrlHandlerMapping 只会处理注册bean 名称以“/” 开头的 ResourceHttpRequestHandler。
举例: /*.html 的静态资源请求直接返回对应的静态文件(由于我这边使用的是 springboot,所以我的静态资源文件都在 classpath:static/ 下)
@Bean("/*.html")
public ResourceHttpRequestHandler mm(){
ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
List locations = new ArrayList<>();
locations.add("classpath:static/");
resourceHttpRequestHandler.setLocationValues(locations);
return resourceHttpRequestHandler;
}
这样,如果请求的 url 与 /*.html 匹配,则直接响应 classpath:static/ 目录下的资源文件
@Bean
public SimpleUrlHandlerMapping ss(ApplicationContext applicationContext) throws Exception {
ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
List locations = new ArrayList<>();
locations.add("classpath:static/");
locations.add("classpath:static/css/");
locations.add("classpath:static/img/");
resourceHttpRequestHandler.setLocationValues(locations);
List resourceResolvers = new ArrayList<>();
resourceResolvers.add(new PathResourceResolver());
resourceHttpRequestHandler.setApplicationContext(applicationContext);
resourceHttpRequestHandler.afterPropertiesSet();
Map urlMap = new LinkedHashMap<>();
urlMap.put("/*.html", resourceHttpRequestHandler);
urlMap.put("/*.css", resourceHttpRequestHandler);
urlMap.put("/*.img", resourceHttpRequestHandler);
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
方式一的缺点:
方式三是通过 Springmvc 提供的配置 API—WebMvcConfigurer 的 addResourceHandlers api,来注册 ResourcehttpRequestHandler
@Configuration
@EnableWebMvc
public class MyConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("*.html").addResourceLocations("classpath:static/");
registry.addResourceHandler("css/**").addResourceLocations("classpath:/META-INF/resources/css/");
registry.addResourceHandler("img/**").addResourceLocations("classpath:/META-INF/resources/img/");
registry.addResourceHandler("js/**").addResourceLocations("classpath:/META-INF/resources/js/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
这种方式,其实跟第二种没有太大的区别,只是 springmvc 在WebMvcConfigurationSupport 的基础上提供了更加便捷的 API。使用的时候,需要在 @Configuration 类上,使用 @EnableWebMvc 修饰(@EnableWebMvc 的主要作用是注册一个 DelegatingWebMvcConfiguration 收集 WebMvcConfigurer 实现类中的各种配置)
到这里, Handler、HandlerMapping、HandlerAdapter 各自的定位已经十分明显了。需要记住的是,Springmvc 提供的两类 handler
参数解析大致分为以下几种类型:
定义注解:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ContentType {
}
定义解析器
public class ContentTypeResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ContentType.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getHeader("content-type");
}
}
注册解析器到配置中:
@Configuration
@EnableWebMvc
public class MyConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(new ContentTypeResolver());
}
}
测试:
@Controller
public class MyController3 {
@RequestMapping("/test999")
public void test(@ContentType String contentType){
System.out.println(contentType);
}
}