SpringMVC框架

SpringMVC框架

文章目录

springmvc框架思想以及设计思路

在这里插入图片描述

SpringMVC简述

在这里插入图片描述

一个业务我们习惯叫做为控制器

主要的作用:实现JavaBean的封装,json转换,文件上传等操作

SpringMVC快速入门

需要导入的包

在这里插入图片描述

spring-webmvc包整合了之前的包,如spring-web,javax.servlet-api等

web.xml配置

在这里插入图片描述

web服务器启动时候会先加载webapp这个目录下的MATE-INF下的xml文件,并且解析出web服务器所需的对应的标签

标签必须于标签成对的出现

标签用于指定需要的spring容器,也就是说,web服务器容器会根据标签下的的内容对应的spring容器,用户请求到web服务器容器,web服务器再把找对应的service

  1. <param-name>contextConfigLocation</param-name>:这里,参数名称是 “contextConfigLocation”。此参数用于指定 Spring 配置文件的位置。DispatcherServlet 在启动时会读取这个参数,并根据其值加载配置文件。
  2. <param-value>classpath:spring-mvc.xml</param-value>:参数值为 “classpath:spring-mvc.xml”。这意味着 Spring 配置文件名为 “spring-mvc.xml”,并位于类路径(classpath)下。当 DispatcherServlet 创建 Spring 应用上下文(Application Context)时,它会根据这个参数值找到并加载 “spring-mvc.xml” 文件。

总结一下,这段代码通过设置 <init-param>,告诉 DispatcherServlet 在启动时从类路径下的 “spring-mvc.xml” 文件中加载 Spring MVC 的配置。这样,Spring 框架可以根据这个配置文件为你的 Web 应用程序创建和初始化相应的组件、控制器等。

web.xml的执行流程

web.xml 文件中的配置被加载时,以下是关于 <init-param> 的执行流程:

  1. 当 Web 应用程序启动时,Servlet 容器(如 Tomcat)会读取并解析 web.xml 文件。
  2. 通过 <servlet> 标签定义了一个名为 “DispatcherServlet” 的 Servlet 实例。它使用 org.springframework.web.servlet.DispatcherServlet 类作为实现。
  3. <servlet> 中,通过 <init-param> 定义了一个初始化参数。参数名称为 “contextConfigLocation”,参数值为 “classpath:spring-mvc.xml”。这是一个指向 Spring 配置文件的路径。
  4. <load-on-startup>2</load-on-startup> 表示在 Web 应用程序启动时立即加载并初始化这个 Servlet。数值 2 是优先级,数字越小,启动优先级越高。
  5. 当 Servlet 容器根据 <load-on-startup> 加载 DispatcherServlet 时,它会创建一个该类的实例,并调用其 init() 方法。
  6. DispatcherServletinit() 方法中,它会间接地调用继承自 FrameworkServlet 类的 initWebApplicationContext() 方法。在这个方法中,Spring 框架会查找名为 “contextConfigLocation” 的参数,并根据其值加载相应的配置文件。在本例中,它会从类路径下加载 “spring-mvc.xml” 文件。
  7. 根据 “spring-mvc.xml” 文件中的配置,Spring 框架会创建和初始化相应的组件、控制器等。这些组件和控制器将用于处理来自客户端的请求。
  8. 在 Web 应用程序运行时,DispatcherServlet 会作为前端控制器,接收并分发客户端发起的请求到对应的处理器(如 Controller)进行处理。

通过 <init-param> 设置 contextConfigLocation 参数值,你可以告诉 DispatcherServlet 在启动时加载指定的 Spring 配置文件。这样,在 Web 应用程序运行过程中,Spring 框架就能根据配置文件创建并管理相关的组件。

对应类的配置

在这里插入图片描述

/show表示根据web服务器返回的url地址字符串拼接/show之后的就是需要对应的service

快速入门总结

在这里插入图片描述

Controller中直接注入Spring中维护的Bean

在这里插入图片描述

程序可以运行,但是不知道为什么一直报无法解析的错误

MVC关键组件浅析

​ 上面已经完成的快速入门的操作,也在不知不觉中完成的Spring和SpringMVC的整合,我们只需要按照规则去定
义Controller7和业务方法就可以。但是在这个过程中,肯定是很多核心功能类参与到其中,这些核心功能类,
般称为组件。当请求到达服务器时,是哪个组件接收的请求,是哪个组件帮我们找到的Controller,是哪个组件
帮我们调用的方法,又是哪个组件最终解析的视图?

在这里插入图片描述

我们之前学过DispatcherServlet,他是一个前端控制器,他只是个总的控制器,就是收到客户端请求之后呢,会指派组件完成响应的功能,指派的组件有处理器映射器、处理器适配器、视图解析器等

三大组件的关系

在这里插入图片描述

springMVC加载组件的策略

​ 在Tomcat启动时,会先加载web.xml文件,并根据配置文件初始化Web容器中的 DispatcherServlet 组件。而DispatcherServlet组件本身是SpringMVC框架的核心控制器,它能够利用Spring IoC容器管理应用程序所需的所有Bean对象。

​ 当DispatcherServlet组件被初始化时,它会要求Spring容器提供它所需要的所有Bean对象,并将它们存储到相应的列表容器中,以便后续快速访问和使用。这个过程中不会涉及到Web容器的任何内容。

​ 因此,可以理解为Spring容器与Web容器在Tomcat启动时同时被初始化,各自处理各自相关的组件和类对象的装载、实例化、注册等操作。相比之下,Spring容器需要更早加载并初始化,以便提供给Web容器维护和调用所需的Bean对象。但两者在启动时均需要监听和处理各自相关的初始化事件,以确保应用程序在启动时处于正确的状态。
在这里插入图片描述

​ DispatcherServlet这个前端控制器若在初始化的时候没有在spring找到对应组件类,则会在初始化的是初始化组件类的对应List容器中,例如把上图org.springframework.web.servlet.HandlerMapping=后面的全限定名都放入List容器当中

在这里插入图片描述

在springmvc中在org.springframework.web.servlet.HandlerAdapter中指定了组件org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,那么其他组件是不是就不会被加载了

​ 并不是,指定了组件org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter只是告诉Spring MVC框架使用这个适配器来处理HttpRequestHandler类型的控制器。其他类型的控制器可能会使用其他的适配器来处理。

​ 在Spring MVC中,每个请求都会委托给一个或多个处理器(Handler)进行处理,并返回相应结果。而处理器可以是任意类型的对象,只需要实现特定的接口即可。但是,不同类型的处理器需要使用不同类型的适配器(HandlerAdapter)来进行处理。因此,Spring MVC框架提供了多种不同的适配器,以支持各种类型的处理器。

​ 如果在DispatcherServlet的配置中没有显式指定HandlerAdapter,那么SpringMVC会根据处理器类型自动选择相应的默认适配器。如果显式指定了HandlerAdapter,那么只有该适配器能够处理 对应类型 的处理器,其他类型的适配器仍然会继续起作用。

请求源资源路径的配置与请求参数键值对

​ Spring MVC框架的请求参数有两种形式:路径参数和键值对形式参数。下面是如何使用Spring MVC框架的spring-mvc 5.3.7版本的jar包来处理键值对形式参数的步骤:

  1. pom.xml文件中添加以下依赖:

    xml复制代码<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.7</version>
    </dependency>
    
  2. 创建一个Controller类,并在类上加上@Controller注解,例如:

    java复制代码@Controller
    public class MyController {
        // ...
    }
    
  3. 创建一个方法来处理请求,并在方法上加上@RequestMapping注解,例如:

    java复制代码@RequestMapping("/hello")
    public String hello(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello";
    }
    
  4. 在方法参数列表中添加@RequestParam注解,并指定参数名称,例如:

    java复制代码public String hello(@RequestParam("name") String name, Model model)
    
  5. 在模板文件(例如Thymeleaf、FreeMarker或JSP)中使用${...}语法引用模型中的属性,例如:

    html复制代码<p>Hello, ${name}!</p>
    

​ 在上述示例中,我们假设我们正在处理一个路径为/hello?name=World的GET请求,其中name是键值对形式参数。当请求到达该路径时,Spring MVC框架会自动绑定参数name到方法的name参数上,并将其值设置为"World"。然后,它将执行hello方法并返回hello字符串。最后,Spring MVC框架将在模板文件中查找名为hello的视图,并将模型中的属性传递给它。在此示例中,属性name具有值"World"

在最新版本的 spring-mvc jar 中,可以直接在请求参数中使用 List 容器。

Spring MVC 提供了多种方式来绑定请求参数。其中一种方式就是使用 @RequestParam 注解。而且,从 Spring 3.0 版本开始,@RequestParam 注解支持 List 类型的参数,甚至还支持数组类型的参数。

例如,下面的代码演示了如何在控制器方法中接收一个名称为 ids 的 List 类型的参数:

java复制代码@RequestMapping("/example")
public String example(@RequestParam List<Integer> ids) {
    // ...
}

​ 通过上述代码,Spring MVC 将会自动将 HTTP 请求中名为 ids 的参数转换成一个整数列表,并传递给该控制器方法。

​ 需要注意的是,如果您在接收 List 参数时没有指定任何请求参数名称,则 Spring MVC 将默认使用参数名称作为请求参数名称。例如,在上面的例子中,我们并没有指定请求参数的名称,因此 Spring MVC 将默认使用 “ids” 作为请求参数名称。如果您希望指定不同的名称,可以使用 @RequestParam 的 value 属性。

总之,最新版本的 spring-mvc jar 支持在控制器方法中直接接收 List 参数,并且使用起来非常方便。

补充

在这里插入图片描述
在这里插入图片描述

​ 注意:我们在发送http请求的时候,请求url地址中没有请求参数,但是方法中有参数要求,那么springmvc会给方法中的请求参数名赋值为null,所以当方法中的请求参数类型为int时候,那么这个int类型的请求参数名必须要通过url地址得到值,你可以与url参数的名字一样,也可以使用RequestParam给int类型命名重定义,因为int类型无法接受null的赋值

请求参数

post-发送text文本

在这里插入图片描述
在这里插入图片描述

Post-发送json请求

方式一

在这里插入图片描述

在这里插入图片描述

postman的内容
在这里插入图片描述

方式二-指定转换器

配置RequestMappingHandlerAdapter,指定消息转换器,就不用手动转换json格式字符串了

可以省去以下的代码
在这里插入图片描述

具体配置

(我是偷黑马教程的图)

bilibili学习的地址
在这里插入图片描述

在这里插入图片描述

​ 可以发现List里面的类型是个HttpMessageConvert类的引用类型变量,但是这个引用类型变量又是个接口类型,所以我们需要的是他对应的实现
在这里插入图片描述

在这里插入图片描述

执行过程

​ 完成以上的配置后,web服务器若收到json请求,接收请求体的参数是一个引用变量类型后,他会自动把json请求转换成对应的引用变量类型,并且根据键找到引用变量类型的setXXX方法实现类初始化,如发送的键是"password",值是"123456",他就会找到对应的setPssword方法,并且this.password=“123456”

接收restful风格的数据

什么是rest风格

在这里插入图片描述

注意,他只是一个设计风格和开发方式,不是一种什么新的技术

restful风格请求方式有三点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接收上传的文件

​ form标签的enctyp属性的默认属性值是 “application/x-www-form-urlencoded”。这是指浏览器将表单数据编码为URL编码格式,并在将数据发送到服务器之前对其进行URL编码。如果想要支持文件上传,则需要将enctype属性设置为"multipart/form-data"。

​ 若是想了解两种属性值的区别请观看如下!

请求体的设置

在这里插入图片描述

当提交的数据是纯文本时,使用x-www-form-urlencoded格式;当需要上传文件或其它二进制数据时,必须使用form-data编码格式。


类的配置

在这里插入图片描述

需要导入的包

在这里插入图片描述

需要配置的bean

在这里插入图片描述

注意bean的id一定要是multipartResolver,因为spring容器根据注解RequestBody把内容传入MutiPartFile对象的时候,由于mutipartFile内部需要一个CommonsMutipartResolve类对象,因此他会根据multipartResolver这个id寻找CommonsMutipartResolve类的bean对象

关于IOUtils类的选择

在这里插入图片描述

​ 这三个 IOUtils 分别来自不同的包,虽然名字相同,但是提供的功能不同:

  • IOUtils org.apache.commons.io: 这个 IOUtils 是 Apache Commons IO 库中的一个工具类。它提供了一系列静态方法,用于读写、拷贝、关闭输入输出流等操作,简化了 Java IO 操作的代码量
  • IOUtils jdk.jpackage.internal: 这个 IOUtils 是 JDK 内置模块 jdk.jpackage 的一部分。它提供了打包应用程序时需要使用到的一些工具函数,比如复制资源文件、打包 jar 文件等操作。
  • IOUtils sun.security.util: 这个 IOUtils 是 JDK 内置模块 sun.security.util 的一部分。它提供了一些与安全相关的工具函数,比如解析 PKCS12 证书、生成 AES 密钥等操作。

因此,尽管这三个类都有 IOUtils 这个类名,在不同的上下文中它们提供的功能是不同的。

org.apache.commons.io包的由来

​ Maven会通过传递依赖的方式来管理依赖关系,如果一个依赖包被其他依赖包所依赖,那么它也会自动被加入到你的项目中。可以使用mvn dependency:tree命令查看完整的依赖树结构,
在这里插入图片描述

由此可以看出我们导入的commons-fileupload这个jar包依赖commons-io,所以maven依赖包加载的时候就会自动导入commons-io这个jar包了

静态资源的请求

请求静态页面的原理:

在这里插入图片描述
在这里插入图片描述

在传统的web服务器中,有个默认的web.xml,这里default这个能够识别html,jpg等静态请求,并且原先的的路径是根路径,也就是说,其实是一个保底路径,当其他url请求找不到对应资源的时候,就会在根路径下查找,并且用对应的解析类解析请求文件

but!!!
在这里插入图片描述

​ 我们自己有一个web.xml,那么他就会以我们自己的web.xml配置文件为主,由于DispatcherServlet不具备处理静态资源的功能,但在我们自己的配置中只有一个DispatcherServelet这个类,且url-pattern路径是根路径下,也就说,覆盖了传统服务器default这个类的url-pattern,接收到的所有请求都会交给Dispatcherservet这个前端控制器来处理,但由于前端控制器没有解析静态资源的功能,所以在现在的情况下,我们是访问不到服务器的静态资源的

能够请求静态资源的方法

方法一

在这里插入图片描述

由于任何url-pattern的匹配路径都高于/,所以接收到的请求,会先通过defult类来匹配找对应的文件并且解析

方法二

在这里插入图片描述

方法三(重点!!!)

在这里插入图片描述

方法原理解析:

在这里插入图片描述

在这里插入图片描述

可以发现,解析mvc:default-servlet-handler/标签的时候,会交给default-servlet-handler这个处理器,而这个处理器他会注册一个DefaultServletHttpRequestHandler这个bean对象,这个bean对象

mvc:default-servlet-handler实现静态资源解析的原理(重点)

​ 在Spring MVC中,<mvc:default-servlet-handler/>配置向容器中注册了一个名为"DefaultServletHttpRequestHandler"的处理器。这个处理器用于将静态资源请求转发给Web应用服务器(如Tomcat)中的默认servlet(通常是"DefaultServlet"),从而实现对静态资源的解析和处理。

原理如下:

  1. 当用户发送一个请求到Spring MVC应用程序时,DispatcherServlet首先接收到该请求。
  2. DispatcherServlet会根据请求的URL和已注册的处理器映射来查找合适的处理器进行处理。如果找到匹配的控制器方法,那么请求将被分发到相应的控制器方法中。
  3. 如果没有找到与请求URL匹配的控制器方法,那么DispatcherServlet会将请求传递给DefaultServletHttpRequestHandler。
  4. DefaultServletHttpRequestHandler会将请求转发给Web应用服务器中的默认servlet(如Tomcat的DefaultServlet)。默认servlet负责处理静态资源,它会搜索指定目录(通常是webapp目录)下与请求URL匹配的静态资源文件。
  5. 一旦找到匹配的静态资源文件,服务器的默认servlet会将其返回给客户端。

因此,在Spring MVC应用程序中使用<mvc:default-servlet-handler/>配置可以确保:

  • **对于非静态资源请求,**它们将被分发到相应的控制器方法进行处理。
  • **对于静态资源请求,**它们将被转发给Web应用服务器的默认servlet进行处理,从而实现对静态资源文件的解析和访问。

补充:

​ 当你使用 <mvc:annotation-driven/> 标签启用 Spring MVC 的注解驱动配置时,Spring MVC 会自动注册一系列默认的 HttpMessageConverter,其中就包括 MappingJackson2HttpMessageConverter解析json数据用的)。所以,在启用了 <mvc:annotation-driven> 之后,你将能够在 Controller 类中使用 @RequestBody@ResponseBody 注解处理 JSON 数据。

使用了标签mvc:default-servlet-handler后,必须要自己配置一个解析器

​ 我们知道,我们若配置了对应的处理,如配置了组件中的请求映射器,handlerMapping,那么handlerMapping后面的类,也就是解析器,不会被加载到容器
在这里插入图片描述
​ 但在使用了使用了标签mvc:default-servlet-handler标签后,会自动注册一个HeadlerMapping,所以他后面的默认的解析器就不会被加载,我们注册的HeadlerMapping默认是空的,所以我们当要处理请求的话我们需要自己手动注入解析器
在这里插入图片描述

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

<mvc:annotation-driven>/与<mvc:default-servlet-handler/>

<mvc:annotation-driven/><mvc:default-servlet-handler/> 都是在 Spring MVC 中配置的重要标签,它们具有不同的作用和特点。下面我们来详细了解一下这两个标签的区别和各自的作用。

  1. mvc:annotation-driven/

<mvc:annotation-driven/> 标签用于启用 Spring MVC 的注解驱动配置。当你在 Spring MVC 配置文件中使用该标签时,框架会自动地为你完成以下操作:

  • 注册 RequestMappingHandlerMappingRequestMappingHandlerAdapter,用于处理带有 @RequestMapping 注解的 Controller 方法。
  • 自动注册一系列默认的 HttpMessageConverter,用于处理请求和响应中的 JSON、XML 等数据格式。
  • 为数据绑定和验证提供支持,例如自动注册 ConversionServiceValidator 实例。
  • 启用基于注解的异常处理,例如 @ExceptionHandler@ControllerAdvice 等。

使用 <mvc:annotation-driven/> 标签后,你可以利用各种注解来简化 Spring MVC 应用的开发。例如,可以使用 @RequestBody@ResponseBody 注解处理请求和响应中的 JSON 数据。

  1. mvc:default-servlet-handler/

<mvc:default-servlet-handler/> 标签用于配置一个默认的 Servlet 请求处理器。当接收到无法被映射到具体处理器方法的 HTTP 请求时(例如请求静态资源),该标签会将请求转发给默认的 Servlet(如 Tomcat 的 DefaultServlet)进行处理。

在实际应用中,<mvc:default-servlet-handler/> 主要适用于以下场景:

  • 静态资源处理:当请求静态资源(如 CSS、JS、图片等文件)时,如果没有任何 Spring MVC 的处理器方法与之匹配,此时 <mvc:default-servlet-handler/> 会将请求交给默认的 Servlet 来处理这些静态资源。
  • Spring MVC 无法处理的请求:如果收到某个请求,但是在 Controller 层找不到与之对应的处理方法,那么 <mvc:default-servlet-handler/> 会将请求交给默认的 Servlet 进行处理。

​ 总结一下:mvc:annotation-driven/ 标签主要用于启用 Spring MVC 的注解驱动配置,简化开发过程。而 mvc:default-servlet-handler/ 标签的作用是处理那些无法被映射到具体处理器方法的请求,并将它们交给默认的 Servlet 处理。

@EnableWebMvc

@EnableWebMvc注解在功能上,可以看作包含了 <mvc:annotation-driven /><mvc:default-servlet-handler />这两个XML配置标签的功能

springMVC数据处理

传统同步数据的响应

资源转发与重定向

在这里插入图片描述

ModelAndView的方式

在这里插入图片描述
在这里插入图片描述

直接写回字符串

在这里插入图片描述

@requestbody与@responsebody

  1. @RequestBody

@RequestBody 注解主要用于将 HTTP 请求中的 JSON 或 XML 数据转换为 Java 对象。当你在 Controller 层的方法参数上使用此注解时,Spring MVC 会自动将请求主体(request body)中的数据按照指定的数据格式(如 JSON、XML 等)解析成对应的 Java 对象

例如:

java复制代码@PostMapping("/user")
public User createUser(@RequestBody User user) {
    // 处理创建用户的逻辑
}

​ 在这个例子中,我们对 createUser 方法的参数 user 添加了 @RequestBody 注解。这样,当客户端发送 POST 请求时,请求体中的 JSON 数据会被自动转换为 User 对象。

  1. @ResponseBody

@ResponseBody 注解用于将 Java 对象转换为 HTTP 响应体中的 JSON 或 XML 数据。当你在 Controller 层的方法上使用此注解时,Spring MVC 会自动将方法返回的 Java 对象按照指定的数据格式(如 JSON、XML 等)序列化到响应体(response body)中。

例如:

java复制代码@GetMapping("/user/{id}")
@ResponseBody
public User getUser(@PathVariable("id") Long id) {
    // 查找并返回用户对象
}

​ 在这个例子中,我们对 getUser 方法添加了 @ResponseBody 注解。这样,当客户端发送 GET 请求时,方法返回的 User 对象会被自动转换为 JSON 数据并写入响应体。

​ 需要注意的是,在 Spring 4.0 之后,我们可以直接使用 @RestController 注解代替 @Controller@ResponseBody 的组合使用。当一个类被标注为 @RestController 时,其所有的方法都会默认添加 @ResponseBody 注解。

总结一下,@RequestBody 主要用于将请求体中的数据转换成 Java 对象,而 @ResponseBody 主要用于将 Java 对象转换成响应体中的数据。通过这两个注解,我们可以方便地处理和操作 HTTP 请求和响应中的 JSON 或 XML 数据。

拦截器

SpringMVC 处理请求的过程:

​ SpringMVC 中,拦截器(Interceptor)是基于 Java 的 Servlet 规范和 SpringMVC 框架实现的一种用于在请求到达 Controller 之前或之后,对请求进行预处理或后处理的机制。拦截器可以用于身份验证、权限检查、日志记录等多种场景

要了解拦截器的工作原理,我们首先需要了解 SpringMVC 处理请求的过程:

  1. 用户发起请求,请求被 SpringMVC 的 DispatcherServlet 接收。

  2. DispatcherServlet 根据用户请求,通过 HandlerMapping 找到对应的 Controller。

  3. 在调用 Controller 之前,DispatcherServlet 遍历配置的 Interceptor 列表,依次调用它们的 preHandle 方法。

  4. 如果所有 Interceptor 的 preHandle 方法都返回 true,则继续执行 Controller 方法。

  5. Controller 方法执行完成后,DispatcherServlet 再次遍历 Interceptor 列表,依次调用它们的 postHandle 方法。

  6. 最后,在视图渲染完成后,DispatcherServlet 遍历 Interceptor 列表,依次调用它们的 afterCompletion 方法。

详细解释一下 preHandle 方法的工作原理及过程:

​ preHandle 方法是在请求到达 Controller 之前被调用的。拦截器通过实现 HandlerInterceptor 接口(或继承 HandlerInterceptorAdapter 类)来定义自己的 preHandle 方法。这个方法有三个参数:HttpServletRequest、HttpServletResponse 和 Object(表示处理请求的 Controller 对象)。

拦截器可以在 preHandle 方法中对请求进行处理,例如检查用户是否已经登录、用户是否有权限访问该资源等。根据处理结果,preHandle 方法可以返回 true 或 false:

  • 如果返回 true,表示请求通过拦截器的检查,DispatcherServlet 会继续处理这个请求,调用下一个拦截器的 preHandle 方法(如果还有其他拦截器),最终到达 Controller。
  • 如果返回 false,表示请求被拦截器阻止,DispatcherServlet 不会继续处理这个请求,也不会调用后续的拦截器和 Controller。此时,拦截器需要向 HttpServletResponse 中写入适当的响应信息,告诉客户端请求被拒绝的原因。

要注意的是,interceptors 的执行顺序与它们在配置文件或 Java 配置类中的注册顺序相同。因此,在设计拦截器时,要考虑它们之间可能存在的依赖关系,并合理安排执行顺序。

拦截器的源码解析

​ 要通过源码分析 SpringMVC 中拦截器如何通过 preHandle 方法实现对请求的拦截原理,我们需要关注的主要类是 DispatcherServlet。在 DispatcherServlet 类中,有一个名为 doDispatch 的方法,它负责处理整个请求的生命周期,包括调用拦截器的 preHandle、postHandle 和 afterCompletion 方法。

以下是基于 SpringMVC 5.3.x 版本的部分 doDispatch 方法源码,其中展示了如何调用拦截器的 preHandle 方法:

java复制代码protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 省略其他代码...

    // 1. 根据 HandlerMapping 获取匹配的 HandlerExecutionChain(包含拦截器和控制器)
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
    }

    // 2. 获取 HandlerAdapter
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 3. 应用所有 HandlerInterceptor 的 preHandle 方法
    boolean success = applyPreHandle(processedRequest, response, mappedHandler);
    if (!success) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, null);
        return;
    }

    // 4. 调用 Controller 方法
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    // 省略其他代码...
}

接下来,让我们详细解析上述代码中与 preHandle 方法相关的部分。

  1. 首先,DispatcherServlet 通过 getHandler 方法找到一个 HandlerExecutionChain 对象,它包含了一个 Handler(通常是 Controller)以及与之关联的所有拦截器
  2. 然后,DispatcherServlet 通过 getHandlerAdapter 方法获取一个 HandlerAdapter 对象,用于处理具体的 Handler。
  3. 接下来,调用 applyPreHandle 方法,依次执行 HandlerExecutionChain 中的所有拦截器的 preHandle 方法。这个方法的实现如下:
java复制代码private boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler) throws Exception {

    HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, mappedHandler.getHandler())) {
                triggerAfterCompletion(request, response, mappedHandler, null);
                return false;
            }
            mappedHandler.applyInterceptorAndIncrementInterceptorIndex(interceptor);
        }
    }
    return true;
}

​ 在 applyPreHandle 方法中,首先从 HandlerExecutionChain 获取所有拦截器。然后遍历拦截器数组,依次调用每个拦截器的 preHandle 方法。

​ 如果某个拦截器的 preHandle 方法返回 false,表示请求被拦截。此时,会调用 triggerAfterCompletion 方法触发已经执行过的拦截器的 afterCompletion 方法,并返回 false。只有当所有拦截器的 preHandle 方法都返回 true 时,applyPreHandle 方法才会返回 true。

  1. 在 doDispatch 方法中,如果 applyPreHandle 返回 true,则表示请求通过了所有拦截器的检查,接下来会调用 HandlerAdapter 的 handle 方法来处理 Controller。如果返回 false,那么触发已执行过的拦截器的 afterCompletion 方法,并直接结束请求处理流程。

​ -从上述源码中,我们可以看到 SpringMVC 是如何使用拦截器 preHandle 方法实现对请求的拦截和预处理的。在整个请求处理过程中,拦截器起到了关键性作用,可以在不侵入 Controller 业务逻辑的前提下,对请求进行统一管理。

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());,这条语句如何判断所需对应的HandlerAdapter是什么

​ 在SpringMVC中,HandlerAdapter 负责处理请求的具体逻辑。通常情况下,有多个实现类,如 RequestMappingHandlerAdapter, SimpleServletHandlerAdapter 等。这些实现类可以处理不同类型的 Handler(例如:注解驱动的 Controller 或者基于Servlet API的 HttpServlet)。

​ 在 DispatcherServlet 中,通过 getHandlerAdapter(Object handler) 方法来获取与给定 Handler 对应的 HandlerAdapter。该方法的实现如下:

java复制代码protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

​ 从上述代码中,我们可以看到 getHandlerAdapter 方法会遍历在 SpringMVC 配置中注册的所有 HandlerAdapter 实例。对于每个 HandlerAdapter,调用其 supports(Object handler) 方法来判断是否支持处理给定的 Handler。一旦找到支持处理该 Handler 的 HandlerAdapter,就立即返回该实例。

​ 因此,在实际使用过程中,可以根据项目需求自定义 HandlerAdapter,并通过覆盖其 supports 方法来控制其适配范围。当处理请求时,DispatcherServlet 将根据 supports 方法的返回值来选择合适的 HandlerAdapter。这样的设计使得灵活性得到提高,可以应对不同类型的请求处理需求。

关于被过滤掉的请求

​ 在Spring Boot的过滤器中,如果请求不符合要求,可以根据需要进行不同的处理方式。通常情况下,过滤器可以选择继续处理该请求或者中断请求的继续传递。

​ 如果过滤器决定继续处理该请求,它可以对请求进行修改、添加响应头、记录日志等操作,并将请求传递给下一个过滤器或Servlet进行进一步处理。

​ 如果过滤器决定中断请求的继续传递,它可以选择不继续调用下一个过滤器或Servlet,直接返回响应给客户端。这可以通过不调用filterChain.doFilter(request, response)方法来实现。

​ 需要注意的是,如果过滤器中断了请求的继续传递,并且没有返回响应给客户端,那么该请求可能会被丢弃掉,客户端将无法收到响应。

​ 在实际应用中,可以根据业务需求选择适当的处理方式。有些情况下,如果请求不符合要求,可能需要返回特定的错误响应给客户端;而在其他情况下,可能需要记录日志或进行其他处理操作。重要的是根据实际需求来决定如何处理不符合要求的请求。

拦截器同理
HandlerAdapter,就立即返回该实例。**

​ 因此,在实际使用过程中,可以根据项目需求自定义 HandlerAdapter,并通过覆盖其 supports 方法来控制其适配范围。当处理请求时,DispatcherServlet 将根据 supports 方法的返回值来选择合适的 HandlerAdapter。这样的设计使得灵活性得到提高,可以应对不同类型的请求处理需求。

关于被过滤掉的请求

​ 在Spring Boot的过滤器中,如果请求不符合要求,可以根据需要进行不同的处理方式。通常情况下,过滤器可以选择继续处理该请求或者中断请求的继续传递。

​ 如果过滤器决定继续处理该请求,它可以对请求进行修改、添加响应头、记录日志等操作,并将请求传递给下一个过滤器或Servlet进行进一步处理。

​ 如果过滤器决定中断请求的继续传递,它可以选择不继续调用下一个过滤器或Servlet,直接返回响应给客户端。这可以通过不调用filterChain.doFilter(request, response)方法来实现。

​ 需要注意的是,如果过滤器中断了请求的继续传递,并且没有返回响应给客户端,那么该请求可能会被丢弃掉,客户端将无法收到响应。

​ 在实际应用中,可以根据业务需求选择适当的处理方式。有些情况下,如果请求不符合要求,可能需要返回特定的错误响应给客户端;而在其他情况下,可能需要记录日志或进行其他处理操作。重要的是根据实际需求来决定如何处理不符合要求的请求。

拦截器同理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值