springmvc(三)Handler、HandlerMapping和HandlerAdapter

前言

DispatcherServlet 通过 HandlerMapping 查找到 Handler,然后委托 HandlerAdapter 去执行 Handler,生成 ModelAndView。

为什么需要 Handler、HandlerMapping和HandlerAdapter

日常开发中的Handler、HandlerMapping和HandlerAdapter

回想下,日常的开发中,我们需要针对 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

Handler 定位是处理 HttpServletRequest。Handler 只是一个概念,springmvc 并没有设计统一的接口,进行规范。
springmvc 内置的 Handler:

handler作用(场景)
@RequestMapping方式一个@RequestMapping 注解修饰的方法,可以看作一个 Handler(HandlerMethod(InvocableHandlerMethod))
实现HttpRequestHandler接口 (ResourceHttpRequestHandler)实现 HttpRequestHandler 接口的类,可以看作是一个 Handler,常用的是 springmvc 提供的 ResourceHttpRequestHandler ,ResourceHttpRequestHandler 可以处理静态资源请求
实现Controller 接口实现Controller 接口的类,可以看作是 Handler。由于这种方式比较古老,不做过多纠结

HandlerMapping

HandlerMapping 定位是通过 url,匹配到合适的 Handler,并将 Handler 和 拦截器链捆绑在 HandlerExecutionChain 对象中,返回给调用方。
上文可以看出:一个handler可能是一个方法,也可能是一个 Controller 对象或者 HttpRequestHandler 对象。
针对这种情况, HandlerMapping分为两个分支来处理

  • AbstractHandlerMethodMapping:处理 url 与 Method 级别 handler 的映射
  • AbstractUrlHandlerMapping:处理 url 与 类级别 Handler 的映射(例如 Controller 接口的实现类,或者是 HttpRequestHandler 接口的实现类)

它们又统一继承于 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

HandlerAdapter 的定位是,调用 Handler 处理请求。
Springmvc 提供的 HandlerAdapter 实现

HandlerAdapter作用(场景)
HttpRequestHandlerAdapter用来调用实现了 HttpRequestHandler 接口的 handler 类
SimpleControllerHandlerAdapter用来调用实现了 Controller 接口的 handler 类
RequestMappingHandlerAdapter用来调用 @RequestMapping 修饰的 handler 方法

使用场景:利用 ResourceHttpRequestHandler 进行静态资源处理

场景:如果我们需要将 html 文件以及 css img 等静态文件从 servlet 服务器中返回,其实我们不需要自定义 handler,直接使用 Spring 提供的 ResourceHttpRequestHandler 即可做到

方式一:将 ResourceHttpRequestHandler 配置为一个 Spring 容器中的 bean

将 ResourceHttpRequestHandler 配置为一个 Spring 容器中的 bean,不过需要注意,bean 的名字,必须以"/" 开头,这是因为,BeanNameUrlHandlerMapping 只会处理注册bean 名称以“/” 开头的 ResourceHttpRequestHandler。

举例: /*.html 的静态资源请求直接返回对应的静态文件(由于我这边使用的是 springboot,所以我的静态资源文件都在 classpath:static/ 下)

    @Bean("/*.html")
    public ResourceHttpRequestHandler mm(){
        ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
        List<String> locations =  new ArrayList<>();
        locations.add("classpath:static/");
        resourceHttpRequestHandler.setLocationValues(locations);
        return resourceHttpRequestHandler;
    }

这样,如果请求的 url 与 /*.html 匹配,则直接响应 classpath:static/ 目录下的资源文件

方式二:直接生成 SimpleUrlHandlerMapping,并向其注册 ResourceHttpRequestHandler

    @Bean
    public SimpleUrlHandlerMapping ss(ApplicationContext applicationContext) throws Exception {

        ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
        List<String> locations = new ArrayList<>();
        locations.add("classpath:static/");
        locations.add("classpath:static/css/");
        locations.add("classpath:static/img/");
        resourceHttpRequestHandler.setLocationValues(locations);
        List<ResourceResolver> resourceResolvers = new ArrayList<>();
        resourceResolvers.add(new PathResourceResolver());
        resourceHttpRequestHandler.setApplicationContext(applicationContext);
        resourceHttpRequestHandler.afterPropertiesSet();

        Map<String, HttpRequestHandler> 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,注册 ResourcehttpRequestHandler

方式一的缺点:

  • 每个 ResourcehttpRequestHandler 都需要作为一个 bean,而且跟请求 url 映射是通过 bean 名称来确定的
    方式二的缺点:
  • 代码复杂度稍高,必须非常熟悉ResourceHttpRequestHandler 和 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 实现类中的各种配置)

小结1

到这里, Handler、HandlerMapping、HandlerAdapter 各自的定位已经十分明显了。需要记住的是,Springmvc 提供的两类 handler

  • 基于 @RequestMapping 修饰的 handler 方法
  • 实现 Controller / HttpRequestHandler 接口的 handler 类
    因为有了这两类 handler,才有了后面对应的的 HandlerMapping 和 HandlerAdapter

HandlerMethod、RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 工作流程

流程一:RequestMappingHandlerMapping 根据 url 映射 HandlerMethod

  • step 1 注册 HandlerMethod:RequestMappingHandlerMapping 的 afterPropertiesSet 方法中,会扫描所有被 @Controller 修饰的类的 bean,并找到被 @RequestMapping 修饰的方法,并将方法注册到 mappingRegistry 中
  • step 2 根据 url 匹配:RequestMappingHandlerMapping 对请求的 url 在 mappingRegistry 中进行 HandlerMethod 匹配

流程二:RequestMappingHandlerAdapter 执行 HandlerMethod

  • step 1 参数解析:为HandlerMethod 中的各个参数,匹配合适的参数解析器,并 通过 WebDataBinder 或者 HttpMessageConverter 解析参数
  • step 2 方法执行:通过反射,执行方法,传入 step 1 中解析好的参数
  • step 3 处理返回值

参数解析大致分为以下几种类型:

  • 继承 AbstractNamedValueMethodArgumentResolver 的参数解析类
    Spring Web使用AbstractNamedValueMethodArgumentResolver抽象命名值(named value)控制器方法参数解析的逻辑。像是一般意义上我们通过POST FORM方式传递的参数,GET URL中通过QueryString传递的参数,请求的头部信息,URL路径变量这种命名了的参数,都可以称作"命名值"(named value)。一般我们获取到的 named value 为 String 类型,这个时候,还需要使用 WebDataBinder 转换为所需的 java bean
  • 继承 AbstractMessageConverterMethodArgumentResolver 的参数解析类
    使用 HttpMessageConverter 解析报文,并反序列化为参数类型

使用场景:自定义@ContentType 注解,并通过自定义参数解析器解析@ContentType 注解修饰的方法参数

定义注解:

@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<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new ContentTypeResolver());
    }


}

测试:

@Controller
public class MyController3 {

    @RequestMapping("/test999")
    public void test(@ContentType String contentType){
        System.out.println(contentType);
    }
}

结果:
在这里插入图片描述

推荐阅读

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值