• SpringMVC原理学习(一)RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter


    一、 DispatcherServlet 初始化

    1、DispatcherServlet 初始化时机

    主启动类:

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. }
    7. }

    配置类 

    1. @Configuration
    2. @ComponentScan
    3. //指定资源文件读取的位置
    4. @PropertySource("classpath:application.properties")
    5. //让使用了 @ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理
    6. @EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
    7. public class WebConfig {
    8. // 内嵌 web 容器工厂
    9. @Bean
    10. public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
    11. return new TomcatServletWebServerFactory(serverProperties.getPort());
    12. }
    13. // 创建 DispatcherServlet
    14. @Bean
    15. public DispatcherServlet dispatcherServlet() {
    16. return new DispatcherServlet();
    17. }
    18. // 向 Tomcat 注册 DispatcherServlet, Spring MVC 的入口
    19. @Bean
    20. public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
    21. DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
    22. DispatcherServletRegistrationBean registrationBean =
    23. new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    24. return registrationBean;
    25. }
    26. }

    启动容器时:

    第一次访问时:

    DispatcherServlet初始化不是 Spring 管理 ,是 Tomcat 服务器在首次使用到 DispatcherServlet时,才由 Tomcat 服务器 进行初始化。

    我们可以设置 LoadOnStartup ,让 Tomcat 启动时初始化 DispatcherServlet。

    一旦设置大于 0 的值,就会在 Tomcat 启动时初始化。数字大小代表优先级,如果有多个 Servlet,数字小的优先级高。

    1. @Bean
    2. public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
    3. DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
    4. DispatcherServletRegistrationBean registrationBean =
    5. new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    6. registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
    7. return registrationBean;
    8. }

    2、DispatcherServlet 初始化都做了什么

    DispatcherServlet 初始化时,会执行 onRefresh() 方法,从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

    1. public class DispatcherServlet extends FrameworkServlet {
    2. protected void onRefresh(ApplicationContext context) {
    3. this.initStrategies(context);
    4. }
    5. protected void initStrategies(ApplicationContext context) {
    6. this.initMultipartResolver(context);
    7. this.initLocaleResolver(context);
    8. this.initThemeResolver(context);
    9. this.initHandlerMappings(context);
    10. this.initHandlerAdapters(context);
    11. this.initHandlerExceptionResolvers(context);
    12. this.initRequestToViewNameTranslator(context);
    13. this.initViewResolvers(context);
    14. this.initFlashMapManager(context);
    15. }
    16. }

    二、RequestMappingHandlerMapping

    RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中

    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息

    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象

    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet

    1、初始化 HandlerMapping

    1. private void initHandlerMappings(ApplicationContext context) {
    2. this.handlerMappings = null;
    3. // 该值默认为 true 但是可以设置为 false
    4. // 如果设置为 false 那 Spring MVC就只会查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMapping
    5. // 如果是 ture 则查询所有的..
    6. if (this.detectAllHandlerMappings) {
    7. //在ApplicationContext中查找所有handler映射,包括父类上下文。
    8. Map matchingBeans =
    9. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    10. //如果不为空
    11. if (!matchingBeans.isEmpty()) {
    12. // 将获取的 HandlerMapping 转换成集合..
    13. this.handlerMappings = new ArrayList<>(matchingBeans.values());
    14. // 排序
    15. AnnotationAwareOrderComparator.sort(this.handlerMappings);
    16. }
    17. }
    18. else {
    19. try {
    20. // 从容器中获取 HandlerMapping ,如果获取不到 下面则会添加默认的..
    21. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
    22. this.handlerMappings = Collections.singletonList(hm);
    23. }
    24. catch (NoSuchBeanDefinitionException ex) {
    25. }
    26. }
    27. //通过注册,确保至少有一个HandlerMapping
    28. //如果找不到其他映射,则为默认的HandlerMapping。
    29. if (this.handlerMappings == null) {
    30. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    31. }
    32. for (HandlerMapping mapping : this.handlerMappings) {
    33. if (mapping.usesPathPatterns()) {
    34. this.parseRequestPath = true;
    35. break;
    36. }
    37. }
    38. }
    1. protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {
    2. if (defaultStrategies == null) {
    3. try {
    4. // 从配置文件加载默认的 简单的说 加载 DispatcherServlet.properties 这个文件...
    5. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    6. // 默认的策略~~~
    7. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    8. }
    9. catch (IOException ex) {
    10. throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    11. }
    12. }
    13. //获取 org.springframework.web.servlet.HandlerMapping
    14. String key = strategyInterface.getName();
    15. // 配置文件中定义了三个 默认组件
    16. // org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
    17. // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    18. // org.springframework.web.servlet.function.support.RouterFunctionMapping
    19. String value = defaultStrategies.getProperty(key);
    20. if (value != null) {
    21. // 将 获取的 value 转换成数组
    22. String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
    23. List strategies = new ArrayList<>(classNames.length);
    24. // 循环
    25. for (String className : classNames) {
    26. try {
    27. // 根据 路径获取 class
    28. Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
    29. // 创建...
    30. Object strategy = createDefaultStrategy(context, clazz);
    31. // 添加到集合中
    32. strategies.add((T) strategy);
    33. }
    34. catch (ClassNotFoundException ex) {
    35. // 抛出异常
    36. }
    37. catch (LinkageError err) {
    38. // 抛出异常
    39. }
    40. }
    41. return strategies;
    42. }
    43. else {
    44. return new LinkedList<>();
    45. }
    46. }

    2、代码测试

    配置类添加RequestMappingHandlerMapping,如果用 DispatcherServlet 初始化时默认添加的组件, 并不会放到 Spring 容器里,给测试带来困扰。

    1. @Bean
    2. public RequestMappingHandlerMapping requestMappingHandlerMapping(){
    3. return new RequestMappingHandlerMapping();
    4. }

     Controller类

    1. @Controller
    2. public class Controller1 {
    3. private static final Logger log = LoggerFactory.getLogger(Controller1.class);
    4. @GetMapping("/test1")
    5. public ModelAndView test1() throws Exception {
    6. log.debug("test1()");
    7. return null;
    8. }
    9. @PostMapping("/test2")
    10. public ModelAndView test2(@RequestParam("name") String name) {
    11. log.debug("test2({})", name);
    12. return null;
    13. }
    14. @PutMapping("/test3")
    15. public ModelAndView test3(String token) {
    16. log.debug("test3({})", token);
    17. return null;
    18. }
    19. @RequestMapping("/test4")
    20. public User test4() {
    21. log.debug("test4");
    22. return new User("张三", 18);
    23. }
    24. public static class User {
    25. private String name;
    26. private int age;
    27. public User(String name, int age) {
    28. this.name = name;
    29. this.age = age;
    30. }
    31. public String getName() {
    32. return name;
    33. }
    34. public int getAge() {
    35. return age;
    36. }
    37. public void setName(String name) {
    38. this.name = name;
    39. }
    40. public void setAge(int age) {
    41. this.age = age;
    42. }
    43. }
    44. }

     主启动类

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    7. //收集所有 @RequestMapping 映射信息,封装为 Map
    8. Map handlerMethods = handlerMapping.getHandlerMethods();
    9. handlerMethods.forEach((k, v) -> {
    10. System.out.println(k + " = " + v);
    11. });
    12. }
    13. }

    结果:

    1. {GET [/test1]} = com.itheima.a20.Controller1#test1()
    2. {PUT [/test3]} = com.itheima.a20.Controller1#test3(String)
    3. { [/test4]} = com.itheima.a20.Controller1#test4()
    4. {POST [/test2]} = com.itheima.a20.Controller1#test2(String)

    有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet。

    可以使用 MockHttpServletRequest 模拟请求, Spring 提供的用于测试使用

    案例一

    1. MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
    2. request.setParameter("name", "张三");
    3. HandlerExecutionChain chain = handlerMapping.getHandler(request);
    4. System.out.println(chain);

    结果:

    HandlerExecutionChain with [com.itheima.a20.Controller1#test2(String)] and 0 interceptors

    案例二

    1. MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
    2. request.addHeader("token", "令牌");
    3. HandlerExecutionChain chain = handlerMapping.getHandler(request);
    4. System.out.println(chain);

    结果: 

    HandlerExecutionChain with [com.itheima.a20.Controller1#test3(String)] and 0 interceptors

    三、RequestMappingHandlerAdapter

    RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:

    • HandlerMethodArgumentResolver 解析控制器方法参数
    • HandlerMethodReturnValueHandler 处理控制器方法返回值

    1、初始化HandlerAdapter

    源码和 初始化HandlerMapping 类似,默认添加的组件如下

    1. org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    2. org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    4. org.springframework.web.servlet.function.support.HandlerFunctionAdapter

    2、代码测试

    继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个HandlerAdapter

    1. @Bean
    2. public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
    3. MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    4. return handlerAdapter;
    5. }

    使用 MyRequestMappingHandlerAdapter 原因:RequestMappingHandlerAdapter的 invokeHandlerMethod 方法作用域是 protected,反射调用太麻烦,就写了个子类,调用子类的方法即可。

    1. public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
    2. @Override
    3. public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    4. return super.invokeHandlerMethod(request, response, handlerMethod);
    5. }
    6. }

    主启动类

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    7. System.out.println(">>>>>>>>>>>>>>所有的参数解析器");
    8. for (HandlerMethodArgumentResolver argumentResolver : handlerAdapter.getArgumentResolvers()) {
    9. System.out.println(argumentResolver);
    10. }
    11. System.out.println(">>>>>>>>>>>>>>所有的返回值解析器");
    12. for (HandlerMethodReturnValueHandler returnValueHandler : handlerAdapter.getReturnValueHandlers()) {
    13. System.out.println(returnValueHandler);
    14. }
    15. }
    16. }

    结果:

     调用 invokeHandlerMethod 执行 控制器方法

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    7. MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    8. //MockHttpServletRequest: Spring提供的模拟request对象,用于测试使用
    9. MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
    10. request.setParameter("name", "张三");
    11. MockHttpServletResponse response = new MockHttpServletResponse();
    12. //返回处理器执行链对象
    13. HandlerExecutionChain chain = handlerMapping.getHandler(request);
    14. handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    15. }
    16. }

    结果:

    [DEBUG] 10:40:09.745 [main] com.itheima.a20.Controller1  - test2(张三) 

    四、自定义参数与返回值处理器

    自定义的注解:

    1. // 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
    2. @Target(ElementType.PARAMETER) // 注解位置:方法参数上
    3. @Retention(RetentionPolicy.RUNTIME) // 注解作用范围:运行期
    4. public @interface Token {
    5. }
    1. // 与 @ResponseBody 注解功能类似,不过返回的是 yml 格式的字符串
    2. @Target(ElementType.METHOD) // 注解位置:方法上
    3. @Retention(RetentionPolicy.RUNTIME) //注解作用范围:运行期
    4. public @interface Yml {
    5. }

    Controller类

    1. @Controller
    2. public class Controller1 {
    3. private static final Logger log = LoggerFactory.getLogger(Controller1.class);
    4. @GetMapping("/test1")
    5. public ModelAndView test1() throws Exception {
    6. log.debug("test1()");
    7. return null;
    8. }
    9. @PostMapping("/test2")
    10. public ModelAndView test2(@RequestParam("name") String name) {
    11. log.debug("test2({})", name);
    12. return null;
    13. }
    14. @PutMapping("/test3")
    15. public ModelAndView test3(@Token String token) {
    16. log.debug("test3({})", token);
    17. return null;
    18. }
    19. @RequestMapping("/test4")
    20. @Yml
    21. public User test4() {
    22. log.debug("test4");
    23. return new User("张三", 18);
    24. }
    25. public static class User {
    26. private String name;
    27. private int age;
    28. public User(String name, int age) {
    29. this.name = name;
    30. this.age = age;
    31. }
    32. public String getName() {
    33. return name;
    34. }
    35. public int getAge() {
    36. return age;
    37. }
    38. public void setName(String name) {
    39. this.name = name;
    40. }
    41. public void setAge(int age) {
    42. this.age = age;
    43. }
    44. }
    45. }

    1、自定义参数处理器

    实现 HandlerMethodArgumentResolver 接口

    1. public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    2. @Override
    3. //是否支持某个参数
    4. public boolean supportsParameter(MethodParameter parameter) {
    5. Token token = parameter.getParameterAnnotation(Token.class);
    6. return token != null;
    7. }
    8. @Override
    9. //解析参数
    10. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    11. return webRequest.getHeader("token");
    12. }
    13. }

    将自定义的参数解析器加到handleAdapter 

    1. @Bean
    2. public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
    3. TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
    4. MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    5. //将自定义的参数解析器加到handleAdapter
    6. handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver));
    7. return handlerAdapter;
    8. }

    主启动类

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    7. MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    8. MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
    9. request.addHeader("token", "令牌");
    10. MockHttpServletResponse response = new MockHttpServletResponse();
    11. //返回处理器执行链对象
    12. HandlerExecutionChain chain = handlerMapping.getHandler(request);
    13. handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    14. }
    15. }

    结果:

    [DEBUG] 10:51:26.150 [main] com.itheima.a20.Controller1  - test3(令牌) 
    

    2、自定义返回值处理器

    实现 HandlerMethodReturnValueHandler 接口

    1. public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    2. @Override
    3. public boolean supportsReturnType(MethodParameter returnType) {
    4. Yml yml = returnType.getMethodAnnotation(Yml.class);
    5. return yml != null;
    6. }
    7. @Override
    8. public void handleReturnValue(Object returnValue, MethodParameter returnType,
    9. ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    10. //returnValue : 返回值对象
    11. //1. 转换返回结果为 yaml 字符串
    12. String str = new Yaml().dump(returnValue);
    13. //2. 将 yaml 字符串写入响应
    14. //获取原始的响应对象
    15. HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    16. //设置发送到客户端的响应的内容类型为文本格式
    17. response.setContentType("text/plain;charset=utf-8");
    18. response.getWriter().print(str);
    19. //3.设置请求已经处理完毕
    20. mavContainer.setRequestHandled(true);
    21. }
    22. }

    将自定义的返回值解析器加到 handleAdapter

    1. @Bean
    2. public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
    3. MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    4. YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
    5. //将自定义的返回值解析器加到handleAdapter
    6. handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler));
    7. return handlerAdapter;
    8. }

    主启动类

    1. public class A20 {
    2. public static void main(String[] args) throws Exception {
    3. //支持内嵌 Tomcat 容器的 Spring 容器实现
    4. AnnotationConfigServletWebServerApplicationContext context =
    5. new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    6. RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    7. MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    8. MockHttpServletRequest request = new MockHttpServletRequest("Get", "/test4");
    9. MockHttpServletResponse response = new MockHttpServletResponse();
    10. //返回处理器执行链对象
    11. HandlerExecutionChain chain = handlerMapping.getHandler(request);
    12. handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    13. //检查响应
    14. byte[] content = response.getContentAsByteArray();
    15. System.out.println(new String(content, StandardCharsets.UTF_8));
    16. }
    17. }

     结果:

    !!com.itheima.a20.Controller1$User {age: 18, name: 张三}
  • 相关阅读:
    按键精灵中的日志、分辨率、找色逻辑、线程
    有属性的自定义注解,如何获取到post请求中RequestBody中对象的一个属性值?
    接雨水问题
    含文档+PPT+源码等]精品微信小程序springboot服装企业人事管理系统+后台管理系统[包运行成功]Java毕业设计SSM项目源码
    2024Guitar Pro 8.1 Mac 最新下载、安装、激活、换机图文教程
    面试官:SpringBoot如何优雅停机?
    归并排序与计数排序
    注意,2022年CCF会士评选结果揭晓
    毕业设计之校园一卡通管理系统的设计与实现
    【postgresql 物化视图】自动刷新物化视图2种方法
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/127760279