SpringMVC

Spring MVC 作用

SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化

例如:

  • 请求映射
  • 数据输入
  • 视图界面
  • 请求分发
  • 表单回显
  • 会话控制
  • 过滤拦截
  • 异步交互
  • 文件上传
  • 文件下载
  • 数据校验
  • 类型转换
  • 等等等

总结:

  1. 简化前端参数接收( 形参列表 )
  2. 简化后端数据响应(返回值)

核心组件

Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 做整体请求处理调度!

除了DispatcherServletSpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。

SpringMVC涉及组件理解:

  1. DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  2. HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
  3. HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
  4. Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
  5. ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

简单SpringMVC搭建

导入依赖

packaging 改成 war

<packaging>war</packaging>
<properties>
  <spring.version>6.0.6</spring.version>
  <servlet.api>9.1.0</servlet.api>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
  <!-- springioc相关依赖  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <!-- web相关依赖  -->
  <!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
  <!--
        在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
         Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’
         is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的
         Servlet API,没有更新到 Jakarta EE 规范。
    -->
  <dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>${servlet.api}</version>
    <scope>provided</scope>
  </dependency>

  <!-- springwebmvc相关依赖  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
  </dependency>

</dependencies>

创建 Controller 组件

@Controller
public class HelloController {
  /**
     * handler就是controller内部的具体方法
     * @RequestMapping("/springmvc/hello") 就是用来向handlerMapping中注册的方法注解!
     * @ResponseBody 代表向浏览器直接返回数据!
     */
  @RequestMapping("/springmvc/hello")
  @ResponseBody
  public String hello(){
    System.out.println("HelloController.hello");
    return "hello springmvc!!";
  }
}

Spring MVC核心组件配置类

//导入handlerMapping和handlerAdapter的三种方式
//1.自动导入handlerMapping和handlerAdapter [推荐]
//2.可以不添加,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载
//3.使用@Bean方式配置handlerMapper和handlerAdapter
@EnableWebMvc     
@Configuration
@ComponentScan(basePackages = "com.example.controller") // 进行controller扫
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

  @Bean
  public HandlerMapping handlerMapping(){
    return new RequestMappingHandlerMapping();
  }

  @Bean
  public HandlerAdapter handlerAdapter(){
    return new RequestMappingHandlerAdapter();
  }

}

SpringMVC环境搭建

//SpringMVC提供的接口,是替代web.xml的方案,更方便实现完全注解方式ssm处理!
//Springmvc框架会自动检查当前类的实现类,会自动加载 getRootConfigClasses / getServletConfigClasses 提供的配置类
//getServletMappings 返回的地址 设置DispatherServlet对应处理的地址
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  /**
   * 指定service / mapper层的配置类
   */
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return null;
  }

  /**
   * 指定springmvc的配置类
   * @return
   */
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { SpringMvcConfig.class };
  }

  /**
   * 设置dispatcherServlet的处理路径!
   * 一般情况下为 / 代表处理所有请求!
   */
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }
}

使用 tomcat 10 启动web

浏览器打开 启动地址端口+/springmvc/hello 可看到 hello springmvc!!

路径和参数

访问路径设置

@RequestMapping 注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求

@RequestMapping 注解

精确匹配

@RequestMapping("/spring/mvc")

表示精确访问 “/spring/mvc” 地址

模糊匹配

@RequestMapping("/spring/*")
@RequestMapping("/spring/**")

/*:表示匹配下一层的地址,例如 spring/a, spring/b

/**:表示匹配地址中的多层,例如 spring/a, spring/aa/bb

这个知识点虽然对于@RequestMapping注解来说实用性不大,但是将来配置拦截器的时候也遵循这个规则

类和方法级别区别

  • 设置到类级别:@RequestMapping 注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。

    这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。

  • 设置到方法级别:@RequestMapping 注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 @RequestMapping 注解进行更精细的映射

例如

// RestController = Controller + ResponseBody
@RestController
@RequestMapping("/user")
public class UserController {
  @RequestMapping("/login") 
  public String userLogin(){
    // 实际地址 /user/login
    // 业务逻辑
    return "login";
  }
  @RequestMapping("/add")
  public String addUser(){
    // 实际地址 /user/add
    // 业务逻辑
    return "addUser";
  }
}

指定请求方式

springMVC 封装了 HTTP 请求方式枚举类:

public enum RequestMethod {
  GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

默认情况下,我们不指定请求方法 例如: @RequestMapping("/springmvc/hello"),所有请求方法都可访问

在 @RequestMapping 注解下添加 method 属性指定请求方式

例如

// get方式
@RequestMapping(value = "/springmvc/hello",method = RequestMethod.GET)
// post方式
@RequestMapping(value = "/springmvc/add",method = RequestMethod.POST)

注意:违背请求方式,会出现405异常!

我们也可以使用注解指明请求方式

@RequestMapping 的 HTTP 方法特定快捷方式变体:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

这些注解只能添加到handler方法上,无法添加到类上!

请求参数

param 参数和 json 参数区别

  1. 参数编码

    param 类型的参数会被编码为 ASCII 码。例如,假设 name=john doe,则会被编码为 name=john%20doe

    JSON 类型的参数会被编码为 UTF-8

  2. 参数顺序

    param 类型的参数没有顺序限制

    JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的

  3. 数据类型

    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型

    JSON 类型的参数则支持更复杂的数据类型,如数组、对象等

  4. 嵌套性

    param 类型的参数不支持嵌套

    JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构

  5. 可读性

    param 类型的参数格式比 JSON 类型的参数更加简单、易读

    JSON 格式在传递嵌套数据结构时更加清晰易懂。

在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递

param 参数接收

例如:

只要形参数名和类型与传递参数相同,即可自动接收!

@RestController
@RequestMapping("/user")
public class UserController {
  @RequestMapping("/login")
  public String userLogin(String name,String password){
    // 业务逻辑
    return "login~name: "+name+",password: "+password;
    //访问地址 http://localhost:8080/user/login?name=zs&password=123
    // 显示 login~name: zs,password: 123
  }
}

@RequestParam注解

@RequestParam使用场景:

  • 指定绑定的请求参数名
  • 要求请求参数必须传递
  • 为请求参数提供默认值

例如

@RestController
@RequestMapping("/user")
public class UserController {
  @RequestMapping("/add")
  public String addUser(
    @RequestParam("name") String userName,
    @RequestParam("age") int age){

    return "userName: "+ userName+",age: "+age;
    // 访问地址 http://localhost:8080/user/add?name=ls&age=10
    // 显示 userName: ls,age: 10
  }
}

默认情况下,使用此批注的方法参数是必需的,但您可以通过将 @RequestParam 批注的 required 标志设置为 false

如果没有没有设置非必须,也没有传递参数会出现 400 报错

@RequestMapping("/add")
public String addUser(
  @RequestParam("name") String userName,
  @RequestParam(value = "age",required = false,defaultValue = "22") int age){
	// required = false 表明参数不是必须
  // defaultValue = "22" 参数默认为22
  return "userName: "+ userName+",age: "+age;
  // http://localhost:8080/user/add?name=ls
  // userName: ls,age: 22
}

特殊值接收

例如一个 key 对应多个值:name=ls&name=zs

可以使用 list 来接收

@RequestMapping("/list")
public String userList(@RequestParam("name") List<String> nameList){
  return  nameList.toString();
  // 访问地址: http://localhost:8080/user/list?name=ls&name=zs&name=hh
  // 显示: [ls, zs, hh]
}

实体类接收

Spring MVC 是 Spring 框架提供的 Web 框架,它允许开发者使用实体对象来接收 HTTP 请求中的参数。通过这种方式,可以在方法内部直接使用对象的属性来访问请求参数,而不需要每个参数都写一遍。下面是一个使用实体对象接收参数的

例如

创建实体类 User

@Data
@ToString
public class User {
  private String name;
  private int age;

}

使用 post 方法接收实体类

@PostMapping("/adduser")
public String addUser(User user){
  return user.toString();
}

postman 测试

json 参数接收

前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上

例如

接收一个 User 实体类对于的 json 数据

使用 postman 测试

{
  "name": "fantasy",
  "age": 18
}

在控制器中,使用 @RequestBody 注解来接收 JSON 数据,并将其转换为 Java 对象

@PostMapping("/userinfo")
public String getUserInfo(@RequestBody User user){
  return user.toString();
}

在上述代码中,@RequestBody 注解将请求体中的 JSON 数据映射到 User 类型的 user 参数上,并将其作为一个对象来传递给 getUserInfo() 方法进行处理

添加 json 类型处理工具,不然报 415错误

添加 jackson,springmvc会自动配置

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.15.0</version>
</dependency>

postman 测试

路径 参数接收

路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 @PathVariable 注解来处理路径传递参数

@PathVariable 注解允许将 URL 中的占位符映射到控制器方法中的参数

例如,如果我们想将 /user/{id} 路径下的 {id} 映射到控制器方法的一个参数中,则可以使用 @PathVariable 注解来实现


@GetMapping("/get/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id,
                      @PathVariable("name") String uname) {
  return "user_detail: id = " +id + " ,name = "+uname;
  //地址 http://localhost:8080/user/get/2/ls
  //显示 user_detail: id = 2 ,name = ls
}
  • 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识
  • 形参列表取值: @PathVariable Long id 如果形参名 = {动态标识} 自动赋值
  •          @PathVariable("动态标识") Long id  如果形参名 != {动态标识} 可以通过指定动态标识赋值
    

获取 cookie

@RequestMapping("/request/cookie")
public String getCookie(

  // 使用 @CookieValue 注解获取指定名称的 Cookie 数据
  // name 或 value 属性:指定Cookie 名称
  // defaultValue 属性:设置默认值
  @CookieValue(value = "JSESSIONID", defaultValue = "missing") String cookieValue,

  // 形参位置声明 HttpSession 类型的参数即可获取 HttpSession 对象
  HttpSession session
) {

  return "cookie = " + cookieValue;
}
}

获取请求头信息

请求头

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300
@GetMapping("/demo")
public void handle(
    @RequestHeader("Accept-Encoding") String encoding, 
    @RequestHeader("Keep-Alive") long keepAlive) { 
  //...
}

原生Api对象操作

Controller method argument 控制器方法参数 Description
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse 请求/响应对象
jakarta.servlet.http.HttpSession 强制存在会话。因此,这样的参数永远不会为 null
java.io.InputStream, java.io.Reader 用于访问由 Servlet API 公开的原始请求正文。
java.io.OutputStream, java.io.Writer 用于访问由 Servlet API 公开的原始响应正文。
@PathVariable 接收路径参数注解
@RequestParam 用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。
@RequestHeader 用于访问请求标头。标头值将转换为声明的方法参数类型。
@CookieValue 用于访问Cookie。Cookie 值将转换为声明的方法参数类型。
@RequestBody 用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap 共享域对象,并在视图呈现过程中向模板公开。
Errors, BindingResult 验证和数据绑定中的错误信息获取对象!

例如

/**
 * 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
 * 注意: 接收原生对象,并不影响参数接收!
 */
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
                  HttpServletResponse response){
  String method = request.getMethod();
  System.out.println("method = " + method);
  return "api";
}

共享域对象操作

概念

常见的共享域有四种:ServletContextHttpSessionHttpServletRequestPageContext

  1. ServletContext 共享域:ServletContext 对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在 ServletContext 中保存的数据是线程安全的。
  2. HttpSession 共享域:HttpSession 对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在 HttpSession 中,让用户在多个页面间保持登录状态。
  3. HttpServletRequest 共享域:HttpServletRequest 对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在 HttpServletRequest 中,让处理器方法之间可以访问这些数据。
  4. PageContext 共享域:PageContext 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括pageScoperequestScopesessionScopeapplicationScope 等作用域。

Request级别属性(共享)域

使用 Model 类型的形参

@RequestMapping("/attr/request/model")
@ResponseBody
public String testAttrRequestModel(
  // 在形参位置声明Model类型变量,用于存储模型数据
  Model model) {
  // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
  // 存入请求域这个动作也被称为暴露到请求域
  model.addAttribute("requestModel", "[model]");
  return "target";
}

使用 ModelMap 类型的形参

@RequestMapping("/attr/request/model/map")
@ResponseBody
public String testAttrRequestModelMap(
  // 在形参位置声明ModelMap类型变量,用于存储模型数据
  ModelMap modelMap) {
  // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
  // 存入请求域这个动作也被称为暴露到请求域
  modelMap.addAttribute("requestModelMap", "[model map]");

  return "target";
}

使用 map 类型的形参

@RequestMapping("/attr/request/map")
@ResponseBody
public String testAttrRequestMap(

  // 在形参位置声明Map类型变量,用于存储模型数据
  Map<String, Object> map) {

  // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
  // 存入请求域这个动作也被称为暴露到请求域
  map.put("requestMap", "[map]");

  return "target";
}

使用原生对象


@RequestMapping("/attr/request/original")
@ResponseBody
public String testAttrOriginalRequest(

  // 拿到原生对象,就可以调用原生方法执行各种操作
  HttpServletRequest request) {

  request.setAttribute("requestOriginal", "[original]");

  return "target";
}

使用 ModelAndView 对象

@RequestMapping("/attr/request/mav")
public ModelAndView testAttrByModelAndView() {

  // 1.创建ModelAndView对象
  ModelAndView modelAndView = new ModelAndView();
  // 2.存入模型数据
  modelAndView.addObject("requestMAV", "[mav]");
  // 3.设置视图名称
  modelAndView.setViewName("target");

  return modelAndView;
}

Session级别属性(共享)域

@RequestMapping("/attr/session")
@ResponseBody
public String testAttrSession(HttpSession session) {
    //直接对session对象操作,即对会话范围操作!
    return "target";
}

Application级别属性(共享)域

解释:springmvc会在初始化容器的时候,讲servletContext对象存储到ioc容器中!

@Autowired
private ServletContext servletContext;

@RequestMapping("/attr/application")
@ResponseBody
public String attrApplication() {

  servletContext.setAttribute("appScopeMsg", "i am hungry...");

  return "target";
}

响应数据

页面跳转控制

在 Web 开发中,有两种主要的开发模式:前后端分离和混合开发

混合开发

导入 jsp 依赖

<dependency>
  <groupId>jakarta.servlet.jsp.jstl</groupId>
  <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
  <version>3.0.0</version>
</dependency>

创建 jsp 页面

位置: webapp/WEB-INF/views/home.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    <!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
    ${msg}
  </body>
</html>

jsp 视图解析器

springmvc 配置文件添加 jsp 视图解析器

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") 
public class SpringMvcConfig implements WebMvcConfigurer {
    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }
}

controller 组件

@GetMapping("jump")
public String jumpJsp(Model model){
  model.addAttribute("msg","request data!!");
  // 返回 jsp 页面 ,jsp文件的目录和后缀已经在上面配置文件写好
  return "home";
  // 浏览器访问 http://localhost:8080/jump
  // 显示 request data!!
}

转发和重定向

@RequestMapping("/redirect-demo")
public String redirectDemo() {
  // 重定向到 /demo 路径 
  return "redirect:/demo";
}

@RequestMapping("/forward-demo")
public String forwardDemo() {
  // 转发到 /demo 路径
  return "forward:/demo";
}

总结:

  • 将方法的返回值,设置 String 类型
  • 转发使用 forward 关键字,重定向使用 redirect 关键字
  • 关键字: /路径
  • 注意:如果是项目下的资源,转发和重定向都一样都是项目下路径!都不需要添加项目根路径

前后端分离

前后端分离,返回JSON数据给前端

添加 jackson 依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>

springMVC 配置文件添加了 @EnableWebMvc 注解会自动配置 jackson

@ResponseBody

在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端

@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
  // ...
  return obj;
}

@ResponseBody 注解可以用来标识方法或者方法返回值,表示方法的返回值是要直接返回给客户端的数据,而不是由视图解析器来解析并渲染生成响应体(viewResolver没用)

例如

@PostMapping("/user/detail")
@ResponseBody
public User getUser(@RequestBody User userParam) {
  System.out.println("userParam = " + userParam);
  User user = new User();
  user.setAge(18);
  user.setName("John");
  //返回的对象,会使用jackson的序列化工具,转成json返回给前端!
  return user;
}

postman测试

补充:如果类中每个方法上都标记了 @ResponseBody 注解,那么这些注解就可以提取到类上,代表默认类中的所有方法都生效!

@RestController

类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解

所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解

返回静态资源处理

springMVC 配置添加 处理静态资源的配置

//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  configurer.enable();
}

在 webapp/images/ 目录下放入图片

访问地址 http://localhost:8080/images/图片文件名


参考资料