1、整体调用过程

本文承接本专题上一篇文章《Spring/Boot/Cloud系列知识:HttpMessageConverter转换器使用方式》,在上一篇文章中讲解了HttpMessageConverter转换器的使用方式。本文透过上文讲解的使用方式,重点介绍SpringMVC是如何将HTTP/HTTPS请求映射成SpringMVC Controller层的具体方法,并完成调用的(本文基于的Spring版本为5.1.X,不同的版本对于相关实现有细微差异)。

1.1、关于Filter部分的说明

为了专注于介绍本文的核心内容,本文不涉及Servlet中对Filter过滤器的管理和执行过程的讨论,关于这个过程读者只需要知晓几个关键事实即可:

  • Filter过滤器并不是Spring MVC的知识点,而是Servlet容器的知识点。Spring MVC自身也有一套以GenericFilterBean为代表的Filter过滤器体系,后者实现了Servlet容器的Filter接口并将Spring中的上下文、Spring MVC上下文等关键信息封装起来便于开发人员使用。

  • 在一次HTTTP请求中,所有需要执行的Filter过滤器将形成一个调用链,并由ApplicationFilterChain类进行调用控制。这是一个典型的责任链模式,但关键点在于整个调用链的控制并不是由“循环”思路完成,而是由“递归”思路完成。这种责任链的管理方式,实际上是所有类似的生产环境中推荐的责任链模式实现方式——因为递归思路可以将责任链中节点与节点的上下文关系紧密传导,如下图:

  • 所有Filter调用完成后,ApplicationFilterChain将会把处理过程交给servlet进行后续处理,实现逻辑很简单:既是在ApplicationFilterChain中记录了一个servlet属性,该属性是一个javax.servlet.Servlet类型(interface)的属性。在正确完成所有Filter的处理后,调用这个属性的service(ServletRequest , ServletResponse)方法,关键代码片段如下所示:

public final class ApplicationFilterChain implements FilterChain {// ......private static final Class<?>[] classTypeUsedInService = new Class[]{ServletRequest.class, ServletResponse.class};// ......private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// ......// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal = ((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};// 如果是Web容器设置了安全模式,则通过这种方式完成service方法的调用SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);} else {// 普通情况下就直接调用servlet属性的service方法servlet.service(request, response);}}// ......
}

1.2、整个HTTP请求过程中需要考虑的需求问题

在ApplicationFilterChain完成上述处理过程后,剩下的工作就交由servlet来处理了。所有的servlet都必须实现javax.servlet.Servlet接口,Spring MVC为了遵循这个规则,设计了一个org.springframework.web.servlet.DispatcherServlet类,ApplicationFilterChain对象的servlet属性就引用了DispatcherServlet类的实例:

public final class ApplicationFilterChain implements FilterChain {// ....../*** 这个属性的引用就是DispatcherServlet类的实例* The servlet instance to be executed by this chain.*/private Servlet servlet = null;// ....../*** Set the servlet that will be executed at the end of this chain.* @param servlet The Wrapper for the servlet to be executed*/void setServlet(Servlet servlet) {this.servlet = servlet;}// ......
}

DispatcherServlet类的实例的作用就是要Spring MVC组件完成与本次HTTP调用相匹配的Controller层方法,那么这个过程主要就包括几个设计需求需要着重解决

1.2.1、寻找匹配的Controller处理方法:

这个步骤需要解决的问题是:如何依据当前HTTP请求的URL信息、Head信息、Body信息等找到匹配的Controller层方法(或者匹配某种特殊的处理方式)。这个过程需要考虑的问题很多,例如同一个URL信息,根据携带信息的位置不一样匹配的controller方法可能是不一样的,并且某一个URL信息可能不对应任何的Controller层方法,而是一些特别的处理方式。例如以下示例中的多个URL信息,可能对应一个相同的或者多个不同的Controller层处理方法:

// 以下三个URL请求,可能使用同一个controller层方法进行处理
GET http://xxxxx/AAA/BBB/CCC
POST http://xxxxx/AAA/BBB/CCC
POST http://xxxxx/AAA/BBB/CCC?param1=value1&param2=value2// 以下两个URL请求,可能使用不同的controller层方法进行处理
GET http://aaaa/XXXX/YYYY
GET http://aaaa/XXXX/YYYY?param1=value1&param2=value2# 也就是说,根据URL本身根本区分不出来controller方法的匹配结果

另外,由于Spring MVC组件的发展过程影响,controller层方法的定义方式也有很多种。例如现在最常使用的注解方式,开发人员可以使用“@RestController”注解、“@Controller”注解、“@PostMapping”注解、“@RequestMapping”等注解的方式定义Controller层方法;Spring MVC还可以使用单纯的XML配置文件定义controller层方法 ,例如一下就是一种XML的配置方式:

<!-- ...... -->
<bean name="userController"  class="cn.raffaello.controller.UserController"><property name="userIService" ref="userIService"></property>
</bean>
<!-- ...... -->

Spring MVC还提供了一个基于XML的简单配置方式,提供给开发人员使用XML配置的方式为固定的URL信息指定专门的Controller层处理方法,使用方式如下所示:

<!-- ...... -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><value>/homepage=simpleController/xxxxxx=xxxxxxController/yyyyyy=yyyyyyController/zzzzzz=zzzzzzController......还可以添加很多</value></property>
</bean>
<!-- ...... -->

最后,一个URL信息也可能对应一些需要特别单独处理的场景,例如URL可能是一个欢迎页面,不需要带入开发人员书写的任何Controller方法;URL信息还可能是一些放置在Web容器中的静态资源,例如给定的是“/xxx/yyy/zzz.png”这样的URL信息。

1.2.2、为方法中的各个入参绑定实际的参数值:

为URL请求信息匹配到正确的Controller方法后,第二个需要解决的问题是:针对匹配到的方法的各种入参进行参数值转换和传入。这个场景下Spring MVC需要为多种参数绑定方式提供支持(这里只描述问题场景,并不是介绍这些注解的使用细节):

  • 开发人员可以在Controller层使用@RequestParam注解进行参数映射
    使用@RequestParam注解,可以从URL信息的QUERY部分接收参数值,使用方法如下所示:

    @GetMapping("/findSomething")
    public void findSomething(@RequestParam("some") String something , @RequestParam("other") String other) {// ......
    }
    
  • 开发人员可以在Controller层使用@RequestAttribute注解进行参数映射

    使用@RequestAttribute注解,可以从当前请求的servlet容器的上下文中取得数据,使用方法如下所示:

    @PostMapping("/doSomeOtherthing")
    public String doSomeOtherthing(@RequestAttribute("org.springframework.session.SessionRepository.CURRENT_SESSION") HttpSession session) {// ......
    }
    
  • 开发人员可以在Controller层使用@PathVariable注解进行参数映射

    使用@PathVariable注解,可以将URL信息中的某个地址部分映射成参数值,使用方法如下所示:

    @GetMapping("/find/{param1}/{param2}")
    public ModelAndView find(@PathVariable("param1") String param1 , @PathVariable("param2") String param2) {// ......
    }
    
  • 开发人员可以在Controller层使用@RequestBody注解进行参数映射

    使用@RequestBody注解,可以从当前请求的Body部分映射数据,使用方法如下所示:

    @PostMapping("/doSomeOtherthing")
    public String doSomeOtherthing(@RequestBody YourBusiness yourBusiness) {// ......
    }
    
  • 开发人员可以在Controller层使用@RequestHeader注解进行参数映射

    使用@RequestHeader注解,可以从当前请求的Head部分映射数据,使用方法如下所示:

    @GetMapping("/findSomethingFromHead")
    public void findSomethingFromHead(@RequestHeader("some") String something , @RequestHeader("other") String other) {// ......
    }
    
  • 开发人员还可以在不使用任何注解的情况下要求进行参数映射

    在这种情况下,Spring MVC可以根据参数名自行映射URL信息的Head部分信息,使用方法如下所示:

    @GetMapping("/findParams")
    public void findParams(String param1 , String param2) {// ......
    }
    

    这还只是其中一部分常见的使用场景,Spring MVC甚至支持在不使用注解的情况下,为指定对象的指定属性进行参数映射(赋值)。很显然Spring MVC需要一种技术方案来支持这些参数映射的场景,甚至还应该允许开发人员基于这个技术方案自行定义参数值的接收方式。

1.2.3、进行实际的方法调用,并处理返回值:

找到了正确的Controller层方法并完成了对应参数值的转换后,Controller层方法就可以被正式调用了。无论Controller层方法的调用过程是否成功(业务逻辑的处理是很可能出现错误的),HTTP请求的调用结果信息,如何转换成适当的HTTP响应信息返回给调用者,又成为一个新的需要解决的问题。要知道Controller方法根据业务处理要求,会有很多形式的返回值:

  • Controller层方法没有返回值的情况:

    开发人员完全可以定义一个没有任何返回值的Controller方法,如下所示:

    // 该Controller层方法没有任何返回值
    @PostMapping("/findx")
    public void findx(YourBusiness queryBusiness) {// ......
    }
    

    但是方法没有任何返回值(或者返回值为void)不代表开发人员放任处理过程返回“白页”。Spring MVC组件在这种情况下需要支持一种“默认”值的返回要求,例如帮助所有返回值为void的Controller层方法一段统一的、默认的json信息:

  • 使用ModelAndView对象进行调用信息返回:

    Controller层方法可以返回一个ModelAndView对象,该对象帮助Spring MVC组件描述了本次HTTP请求处理完成后需要向调用者呈现的视图信息和模型信息,例如一个JSP页面资源:

    @GetMapping("/find/aaaa/bbbb")
    public ModelAndView find() {return new ModelAndView("/uuuu");
    }
    

    以上代码中实例化的ModelAndView对象,描述了一个JSP页面资源的名称。当然JSP资源在当下流行的开发模式下已经不经常使用了,但这种返回值的场景仍然是Spring MVC组件组要支持的。

  • 使用自定义对象进行调用信息返回:

    开发人员可以自行定义一个类作为Controller层方法的返回信息:

    // 该Controller层方法
    @PostMapping("/createYourBusiness")
    public DynamicTaskSchedulerVo createYourBusiness() {// ......
    }
    

    Spring MVC一旦发现这样的情况,就应该支持将这些自定义的返回对象转换为适当的能够被HTTP响应携带的信息。以上内容所描述的Spring MVC组件需要支持的返回值场景,只是其中一部分场景,实际上需要考虑的场景还有很多,例如当Controller层方法处理过程出现异常时,进行影响信息返回。

以上描述中本文详细阐述了Spring MVC为了支持一次完成的HTTP请求操作,需要考虑的主要问题以及这些问题的示例情况。包括,如何找到HTTP请求对应的处理方式、如何为匹配的Controller层方法进行参数转换和正式调用、如何处理Controller层方法的各种返回值,等等。看来Spring MVC组件确实需要多种完善的设计方式来解决这些问题,下一篇文章将为读者进行详细的讲解。

(接下文)

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程(1)相关推荐

  1. Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

    (接上文<Spring/Boot/Cloud系列知识:SpringMVC 传参详解(上)>) 2.3.通过@PathVariable注解基于URL匹配符接收请求传参 为了便于开发人员实现更 ...

  2. Spring/Boot/Cloud系列知识(2)— — 代理模式

    本文转自:https://blog.csdn.net/yinwenjie/article/details/77848285 代理模式是23种设计模式中的一种,属于一种结构模式.用一句大白话解释这个设计 ...

  3. Spring/Boot/Cloud系列知识(2)——代理模式

    代理模式是23种设计模式中的一种,属于一种结构模式.用一句大白话解释这个设计模式:当外部调用者调用A来完成一件事情/一个动作时,并不直接调用A本身,而是调用一个代理者,并再由代理者负责调用真实的执行者 ...

  4. Spring Boot干货系列:(六)静态资源和拦截器处理 | 掘金技术征文

    原本地址:Spring Boot干货系列:(六)静态资源和拦截器处理 博客地址:tengj.top/ 前言 本章我们来介绍下SpringBoot对静态资源的支持以及很重要的一个类WebMvcConfi ...

  5. Spring Boot/Cloud干货汇总(持续更新:20180226版)

    抽空对本号推送的Spring相关内容做个汇总整理,包括: - Spring - Spring Boot - Spring cloud - 其他Spring干货内容 Spring Boot专题 基础入门 ...

  6. Spring Boot 实战系列课程终于齐啦~

    Spring Boot 如今已成为 Java 开发必学技术,其可以大大简化 Spring 应用的初始搭建以及开发过程. Spring Boot 不仅支持直接嵌入 Tomcat,Jetty 或 Unde ...

  7. Spring Boot/Cloud 界面与安全设计

    Spring Boot/Cloud 界面与安全设计 一.后端渲染 Thymeleaf 二.后端渲染 Freemarker 三.后端渲染 jsp 四.前端渲染 vue 五.原生 json 请求(自) 六 ...

  8. Spring Boot干货系列:(十二)Spring Boot使用单元测试 | 嘟嘟独立博客

    原文地址 2017-12-28 开启阅读模式 Spring Boot干货系列:(十二)Spring Boot使用单元测试 Spring Boot干货系列 Spring Boot 前言 这次来介绍下Sp ...

  9. Spring Initializr 构建Spring Boot/Cloud工程

    2019独角兽企业重金招聘Python工程师标准>>> 在之前的所有Spring Boot和Spring Cloud相关博文中,都会涉及Spring Boot工程的创建.而创建的方式 ...

最新文章

  1. hibernate分页
  2. D - Mike and strings
  3. mysql存储引擎处理_[MySql学习笔记] 四 存储引擎事务处理
  4. 简简单单日赚200元,当日结算
  5. 接口 DataOutput
  6. ExtJs2.0学习系列(1)--Ext.MessageBox
  7. 防止IE不支持console.log报错
  8. 在Windows 7 专业版 SP1安装多语言包的办法 摘录
  9. Python写数据结构:二叉树的创建和遍历
  10. 【数学建模】第一讲-层次分析法
  11. 计算机课如何断开学生端,极域课堂管理系统怎么连接老师 学生端连接问题解决方法...
  12. java 姓名生成器_java自动机器人自动生成修姓名工具类
  13. uniapp获取当前经纬度 地图 支持搜索
  14. win10安装Ubuntu16.04
  15. 听课感悟——信息与信息特征
  16. Prim算法java实现
  17. dbeaver连接gaussdb
  18. java list转json报错_一个fastjson转换JSON字符串的报错排查
  19. sql数据库连接字符串(Persist Security Info)
  20. 北京数字认证bjca证书垃圾,报错:错误代码 EC999

热门文章

  1. 大学生当裁缝专做西服 一年收入100万
  2. 快速入门 | 篇十四:运动控制器基础轴参数与基础运动控制指令
  3. 长安链---从零到一部署Chainmaker-2.0.0
  4. PostGreSQL语法及高级功能(2022-06-08补充中)
  5. including用法
  6. 阅读报告Maneuvering periods of 2D quantum walks with the coin operator
  7. 什么是POJO?没有你想象中那么复杂!
  8. 华为手机下拉菜单变大_华为手机下拉菜单变少了 华为下拉通知栏变白色
  9. java 混淆war,java yguard混淆web工程
  10. Mysql 安装,及其闪退的问题解决