JavaEE进阶(3)Spring Web MVC入门(MVC 定义、什么是Spring MVC?学习Spring MVC:项目准备、建立连接【注解介绍、指定请求、请求、响应】)

接上次博客: Spring Boot 改版如何解决?使用阿里云创建项目、使用IDEA进行创建-CSDN博客

目录

什么是 Spring Web MVC?

MVC 定义

 什么是Spring MVC?

学习Spring MVC

项目准备 

建立连接

类注解

方法注解

 @RequestMapping注解介绍

@RequestMapping 使用

注意事项

@RequestMapping 是 GET 还是 POST 请求?

指定请求

请求

传递单个参数 

传递多个参数

如何接收多个参数呢?

那么怎么传递一个对象?

如何对参数进行重命名?

传递数组

如何传递集合

传递JSON数据

获取URL中参数@PathVariable

上传文件@RequestPart 

获取Cookie/Session

获取Header

响应

返回静态页面

返回数据

返回HTML片段

返回JSON

设置状态码/设置响应头/编码

设置Header

设置Content-Type

自定义Header


什么是 Spring Web MVC?

官方对于 Spring MVC 的描述是这样的:

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonly known as "Spring MVC".

翻译成中文大概是:

"Spring Web MVC"是一个基于Servlet API构建的原始Web框架,最初就包含在Spring框架中。它的正式名称来源于其源模块的名称,即"Spring-webmvc",但通常简称为"Spring MVC"。

这个框架提供了一种设计模式和一组工具,使开发者能够构建灵活且可扩展的Web应用程序。Spring MVC采用了MVC(Model-View-Controller)架构模式,这有助于将应用程序的不同方面分离开,使其更易于维护和测试。通过使用Spring MVC,开发者能够更轻松地管理Web应用程序的请求-响应循环,实现业务逻辑和用户界面的分离。

回顾——什么是Servlet?

Servlet是一种用于实现动态网页的Java技术。更准确地说,Servlet是一套Java Web开发的规范,定义了一系列接口和规则,而具体的实现则需要开发者编写代码来满足这些规范。Servlet规范的实现包括类、方法、属性等,开发者通过编写这些代码来处理Web应用程序的请求和生成响应。

Servlet规范是开放的,这意味着不仅Sun公司可以实现这些规范,其他公司也可以。目前常见的实现了Servlet规范的产品包括Tomcat、Weblogic、Jetty、Jboss、WebSphere等。这些产品被称为Servlet容器,它们负责在运行时加载、实例化和管理Servlet,并提供了Servlet所需的运行环境。

总体而言,Servlet是Java平台上用于处理Web请求和生成动态内容的一种技术,通过遵循Servlet规范,开发者可以编写与特定Servlet容器兼容的代码,实现可移植的Web应用程序。

根据上述定义,我们可以得知Spring Web MVC是一个Web框架,通常简称为Spring MVC。为了真正理解Spring MVC,让我们首先来搞清楚什么是MVC。

MVC 定义

MVC 是 Model View Controller 的缩写,它是软件工程中的⼀种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分:

  1. Model(模型):

    • 模型是应用程序的主要组成部分,负责管理应用程序的数据和业务逻辑。它独立于用户界面,负责处理数据的存储、检索、更新和计算。
    • 模型通常包括数据库操作、业务规则和应用程序的数据结构。它的设计目标是提供一种可重用、可维护和可扩展的数据处理逻辑。
  2. View(视图):

    • 视图是用户界面的一部分,负责显示数据给用户并接收用户的输入。视图通常展示模型的数据,并将用户的操作反馈给控制器。
    • 视图是用户与应用程序交互的地方,可以是图形用户界面(GUI)、命令行界面或其他形式的用户界面。
  3. Controller(控制器):

    • 控制器是MVC的核心,充当模型和视图之间的协调者。它接收用户的输入(通常来自视图),然后决定使用哪个模型来处理请求,并指导视图显示更新后的数据。
    • 控制器负责处理用户的请求、调用适当的业务逻辑,并更新模型和视图。它帮助实现模型和视图之间的解耦,使系统更加灵活和易于维护。

MVC模式的优势在于将应用程序的不同方面分离开,使得每个组件都可以独立变化而不会影响其他组件。这种分离提高了代码的可维护性、可扩展性和重用性。在Web开发中,Spring MVC等框架广泛使用MVC模式,使开发者能够更轻松地构建和管理Web应用程序。

我们可以举两个例子来理解一下:

购物商城系统

  • View(视图): 顾客在购物商城网站上浏览商品、选择商品、加入购物车,并最终确认购买。网站的用户界面就是视图,负责展示商品信息和接收用户的操作。

  • Controller(控制器): 购物商城的后台系统,根据顾客的购物车内容生成订单、计算总价,并处理支付流程。它负责协调顾客与商品、订单之间的交互,确保正确处理购物流程。

  • Model(模型): 数据库中存储的商品信息、订单信息等数据。当顾客下单时,模型层负责将订单信息存储到数据库中。

学生选课系统

  • View(视图): 学生在选课系统中浏览课程、选择感兴趣的课程,并提交选课申请。系统提供的学生选课页面就是视图,负责显示课程信息和接收学生的选课操作。

  • Controller(控制器): 选课系统的管理后台,根据学生的选课申请分配课程、检查选课资格,并生成学生的选课结果。它负责协调学生与课程、选课结果之间的关系,确保选课流程的正确执行。

  • Model(模型): 数据库中存储的课程信息、学生信息、选课结果等数据。当学生选课成功时,模型层负责更新数据库中的选课结果。

在这两个例子中,视图负责用户界面,控制器负责协调用户的操作和处理业务逻辑,模型负责数据的存储和处理(真正做事的人~~~)。这种分层架构使系统更容易维护和扩展,同时提高了代码的可重用性。

 什么是Spring MVC?

MVC 是⼀种架构设计模式,也⼀种思想,而Spring MVC 是对 MVC 思想的具体实现。

除此之外, Spring MVC还是一个Web框架。

咱们总结一下,Spring MVC是Spring框架中的一个模块,它实现了MVC设计模式并提供了一个Web框架。

具体而言,Spring MVC关注以下两个主要方面:

  1. MVC设计模式: Spring MVC采用了Model-View-Controller(MVC)设计模式,将应用程序划分为模型、视图和控制器三个主要组件。这种分层架构有助于将应用程序的不同方面分离开,提高了代码的可维护性、可扩展性和重用性。

  2. Web框架: Spring MVC是一个基于Servlet API构建的Web框架,它提供了一组类和工具,帮助开发者构建灵活且可扩展的Web应用程序。它处理HTTP请求和响应,通过控制器将请求路由到相应的处理程序,然后将处理结果呈现给用户。

因此,Spring MVC不仅是MVC设计模式的实现,而且是一个完整的Web框架,为开发者提供了一种有效的方式来组织和管理他们的Web应用程序。

Spring框架、Spring Boot和Spring web MVC的区别 

Spring框架、Spring Boot和Spring Web MVC是Spring生态系统中的不同部分,每个部分有不同的职责和用途。

  1. Spring框架:

    • Spring框架是一个全面的Java企业应用程序开发框架,提供了许多模块和功能,包括依赖注入、AOP(面向切面编程)、事务管理、数据访问、消息传递等。
    • Spring框架的核心是IoC(控制反转)容器,它简化了Java开发并提供了更好的可测试性。Spring框架的模块化设计使开发者能够选择使用框架的哪些部分。
  2. Spring Boot:

    • Spring Boot是Spring团队为了简化Spring应用程序的开发和部署而推出的项目。它基于Spring框架,并提供了一组预配置的默认值,使得开发者能够更轻松地创建独立的、自包含的Spring应用程序。
    • Spring Boot自动配置了许多常见的开发任务,如数据库连接、安全性等,同时提供了一种约定大于配置的方式,减少了开发者的工作量。Spring Boot还内置了嵌入式Web服务器,使得构建和运行Web应用程序更加简单。
  3. Spring Web MVC:

    • Spring Web MVC是Spring框架中的一个模块,提供了一种基于MVC设计模式的方式来构建Web应用程序。它包括模型(Model)、视图(View)、控制器(Controller)等组件,用于处理和响应Web请求。
    • Spring Web MVC可以作为Spring框架的一部分使用,也可以与Spring Boot一起使用。它允许开发者构建灵活且可扩展的Web应用程序,同时提供了与多种视图技术(如JSP、Thymeleaf等)和数据传输格式(如JSON、XML等)集成的能力。

总的来说:

  • Spring框架是整个Java企业应用程序开发框架,提供了广泛的功能和模块。
  • Spring Boot是在Spring框架基础上构建的,旨在简化和加速Spring应用程序的开发和部署。
  • Spring Web MVC是Spring框架的一部分,用于构建Web应用程序,提供了基于MVC设计模式的Web开发支持。

其实, Spring MVC 我们在前⾯已经用过了, 在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架 其实就是 Spring MVC 框架:

可能这时候你还是有点懵, 前面创建的不是SpringBoot项目吗? 怎么又变成了Spring MVC项目?

我们之前虽然提过他们之间的关系,但是这里还是再从新的角度强调一次: 

  1. Spring框架:

    Spring框架是一个全面的Java企业应用程序开发框架,最早发布于2004年。它提供了多个模块,包括核心容器、数据访问、AOP、事务管理、Web等。Spring框架并非专门为构建Web应用而设计,而是为了提供一种更轻松、可测试、松散耦合的方式来构建任何类型的Java应用。
  2. Spring Boot:

    Spring Boot是Spring团队于2014年推出的项目,旨在简化和加速Spring应用程序的开发和部署。Spring Boot通过提供默认配置、自动配置、嵌入式Web服务器等功能,大幅度减少了开发者的配置工作,使得构建独立的、自包含的Spring应用程序变得更加容易。Spring Boot并不是Spring框架的替代品,而是建立在Spring框架之上的一个增强和扩展。
  3. Spring MVC:

    Spring MVC是Spring框架中的一个模块,提供了一种基于MVC设计模式的方式来构建Web应用程序。Spring MVC早在Spring Boot之前就存在,并且可以与Spring Boot一起使用。Spring MVC负责处理Web请求和响应,将请求路由到相应的处理程序,并呈现视图。

关系澄清:

  • Spring Boot与Spring框架: Spring Boot是建立在Spring框架之上的,它使用了Spring框架的核心功能。Spring Boot提供了一种更便捷的方式来配置和启动Spring应用程序,使得开发者无需进行繁琐的配置。

  • Spring Boot与Spring MVC: Spring Boot可以用于构建任何类型的Spring应用程序,包括Web应用程序。当你在Spring Boot项目中添加Spring Web MVC的依赖时,它会自动配置Web应用程序的相关设置,使得你能够更容易地构建和运行Web应用。

所以,Spring框架早在Spring Boot之前就支持MVC架构,而Spring Boot为构建Web应用程序提供了更加方便和便捷的方式。在Spring Boot项目中,我们可以选择性地添加Spring Web MVC等模块,根据需要配置和扩展我们的应用程序。


不过,在实际应用中,Spring框架的MVC实现可能会根据项目的需求和特点进行一些自定义或调整,以更好地适应具体的业务场景。在MVC的核心概念上仍然保持不变,但在具体实践中,某些项目可能会对传统MVC模型进行一些变化,以满足特定需求。

比如,一些项目可能会采用更灵活的请求处理方式,直接由具体的业务组件处理请求,而无需中间的控制器。这种灵活性是Spring框架的优势之一,使开发者能够根据项目需求进行定制。

总体而言,虽然具体实现可能会有一些变化,但Spring框架在MVC方面的核心概念和优势仍然是保持的。这种灵活性和可定制性是Spring框架在不同场景下广泛使用的原因之一。

学习Spring MVC

然是 Web 框架, 那么当用户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求, 并给予响应。 

我们学习Spring MVC, 重点也就是学习如何通过浏览器和用户程序进行交互。

主要分以下三个方面:

  1. 建立连接:

    在Web应用中,建立连接通常是通过浏览器发起HTTP请求来实现的。用户在浏览器中输入URL,这个URL对应着Spring MVC中的某个Controller的处理方法。Spring MVC通过处理这个请求,建立了用户和后端Java程序之间的连接。
  2. 请求:

    请求阶段涉及到从用户的HTTP请求中提取数据,这些数据可能包含在URL中、请求头中、请求参数中等。Spring MVC的Controller负责处理这些请求,通过方法参数获取用户传递的数据,例如路径变量、请求参数等。
  3. 响应:

    处理完用户的请求之后,Spring MVC中的Controller执行相应的业务逻辑,然后生成一个响应。响应可以是HTML页面、JSON数据、重定向等。Spring MVC会将生成的响应发送回浏览器,从而用户能够看到处理结果。

银行存款可以非常生动地说明这个过程:用户去银行存款,与柜台建立连接,带着银行卡和身份证进行存款操作,银行处理完业务后返回一张存折。类比到Spring MVC中,用户在浏览器中输入URL发起请求,Controller处理请求获取参数,执行业务逻辑,最后返回给用户相应的结果。

掌握了建立连接、请求和响应这三个关键方面,就相当于掌握了Spring MVC中Web应用程序的主要交互流程。这对于理解和构建Spring MVC应用程序是至关重要的。

项目准备 

Spring MVC 项目创建和 Spring Boot 创建项目相同,在创建的时候选择 Spring Web 就相当于创建了,我们之前已经详细讲解过,这里就略去。

建立连接

在 Spring MVC 中我们使用 @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作用。

我们先来看看代码怎么写:

创建⼀个 UserController 类,实现用户通过浏览器和程序的交互,具体实现代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    //"/sayHi"前面的/可加可不加,建议加
    @RequestMapping("/sayHi")
    public String sayHi() {
        return "hello, Spring MVC";
    }
}

这个简单的控制器类中,@RestController 注解标识这是一个控制器,@RequestMapping("/sayHi") 指定了处理请求的路径。当用户访问 "/sayHi" 路径时,sayHi 方法将被调用,并返回字符串 "hello, Spring MVC",这个字符串将作为响应的内容返回给用户。 

我们的代码片段使用了 @RequestMapping 注解来定义一个路由规则,这是Spring MVC中的基本用法。我们同时使用了 @RestController 注解,它实际上等同于 @Controller 和 @ResponseBody 的组合。

具体来解释一下我们的代码:

  1. @Controller 注解:

    表示这个类是一个控制器,它处理用户请求并返回相应的结果。
  2. @RequestMapping("/sayHi") 注解:

    指定了处理请求的URL路径。在这个例子中,当用户访问 "/sayHi" 路径时,将由 sayHi 方法来处理。
  3. public String sayHi() 方法:

    这是一个处理请求的方法。它返回一个字符串,这个字符串将作为响应的内容返回给用户。
  4. @ResponseBody 注解:

    当 @RestController 注解存在时,@ResponseBody 不是必需的,因为 @RestController 注解包含了 @ResponseBody 的语义。这表示返回的字符串直接作为响应的主体内容。
  5. 返回值 "hello, Spring MVC":

    当用户访问 "/sayHi" 路径时,sayHi 方法将返回这个字符串,它将成为用户浏览器接收到的响应内容。

综合来说,我们的代码是一个简单的Spring MVC控制器,它定义了一个路由规则,处理 "/sayHi" 路径的请求,并返回 "hello, Spring MVC" 这个字符串。这个例子演示了建立连接的过程,即用户访问特定的URL,Spring MVC通过路由规则将请求映射到相应的方法,并返回结果。

类注解

@Controller

@Controller 注解用于标注一个类,表示这个类是 Spring MVC 中的控制器。它告诉 Spring 这个类负责处理 HTTP 请求。通常搭配方法级别的注解一起使用。

@Controller
public class MyController {
    // 控制器方法
}

@RestController

@RestController 是 @Controller 的一个特例,它表示这个类的每个方法的返回值都直接写入 HTTP 响应体中,而不是视图渲染。适用于 RESTful 风格的控制器。

@RestController
public class MyRestController {
    // RESTful 风格的控制器方法
}

方法注解


@RequestMapping

@RequestMapping 注解用于标注控制器方法,表示这个方法处理特定的请求路径。可以标注在类级别,表示类中的所有方法都处理该路径。

@Controller
@RequestMapping("/example")
public class MyController {

    @RequestMapping("/hello")
    public String hello() {
        // 处理 /example/hello 请求
        return "hello";
    }
}

@GetMapping, @PostMapping, 等

这些注解是 @RequestMapping 的缩写,用于限定处理特定 HTTP 方法的请求。

@Controller
@RequestMapping("/example")
public class MyController {

    @GetMapping("/hello")
    public String hello() {
        // 处理 GET 请求 /example/hello
        return "hello";
    }

    @PostMapping("/submit")
    public String submit() {
        // 处理 POST 请求 /example/submit
        return "submit";
    }
}

类注解和方法注解的组合可以构建出复杂的路由映射规则,使得控制器可以灵活地处理各种请求。 

 @RequestMapping注解介绍

@RequestMapping 是 Spring Web MVC 应用程序中最常用的注解之一,它用于定义接口的路由映射。

在 Spring Web MVC 中,@RequestMapping 起到两个主要作用:

  1. 注册接口的路由映射: 通过在方法或类上使用 @RequestMapping 注解,我们告诉Spring框架当接收到特定路径的请求时,应该调用相应的方法来处理该请求。
  2. 路由映射: 路由映射是指当用户访问一个特定的URL时,将用户的请求映射到程序中某个类的某个方法的过程。通过合理使用 @RequestMapping 注解,我们能够建立清晰的URI结构,确保请求能够被正确路由到相应的处理方法。

综合起来,@RequestMapping 在 Spring Web MVC 中充当了连接接口和请求处理方法之间关系的桥梁。通过这一注解,我们能够定义清晰的API结构,使得请求能够被正确地映射到后端处理逻辑,实现了前端和后端的有效交互。

既然 @RequestMapping 已经可以达到我们的目的了, 我们为什么还要加 @RestController 呢?

我们把 @RestController 去掉, 再来访问一次:

可以看到, 程序报了404, 找不到页面。

这就是 @RestController 起到的作用。⼀个项目中,会有很多类, 每个类可能有很多的方法, Spring程序怎么知道要执行哪个方法呢?

Spring会对所有的类进行扫描,如果类加了注解@RestController, Spring才会去看这个类里面的方法有没有加 @RequestMapping 这个注解,当然他的作用不止这⼀点, 咱们先用, 后面再详细讲。

  1. @RestController 注解:

    • @RestController 是一个特殊的控制器注解,结合了 @Controller 和 @ResponseBody。
    • @Controller 告诉Spring,这是一个控制器类,而 @ResponseBody 告诉Spring,这个控制器类的每个方法的返回值都应该直接作为HTTP响应的主体(Body)部分,而不是通过视图解析器解析为视图。
    • 因此,@RestController 的作用是将控制器类的每个方法的返回值直接转换为JSON或其他格式,并将其发送到客户端,而不是通过模板引擎渲染为HTML。
  2. Spring对类的扫描和@RequestMapping 注解的作用:

    • 在Spring应用程序中,会对项目中的所有类进行扫描,以发现带有特殊注解的类,其中包括 @RestController。
    • 当Spring发现一个类上标有  @RestController 注解时,它会解析该类的每个方法,查看是否有 @RequestMapping 或其他映射注解。
    • 如果方法上标有 @RequestMapping 注解,Spring就知道这个方法可以处理特定路径的HTTP请求。这个注解告诉Spring将该方法映射到特定的URI路径,以便在接收到匹配的请求时调用相应的方法。

总体来说,@RestController 的作用是声明一个类是 REST 风格的控制器,它的每个方法都会返回数据而不是视图。Spring通过扫描这些带有@RestController  注解的类,查找并识别标有 @RequestMapping注解的方法,从而建立了 URI 到方法的映射,实现了请求的路由和处理。

@RequestMapping 使用

@RequestMapping 即可修饰类,也可以修饰方法 ,当修饰类和方法时,访问的地址是类路径 + 方法路径。

  • @RequestMapping标识一个类:设置映射请求的请求路径的初始信息
  • @RequestMapping标识一个方法:设置映射请求请求路径的具体信息

在Spring MVC中,@RequestMapping 注解可以同时作用在类级别和方法级别。

  1. 类级别:

    当 @RequestMapping 应用在类级别时,它为整个控制器建立了一个基本URI。这个基本URI然后与类内方法上的单独 @RequestMapping 注解中指定的URI组合在一起。
     
    @RestController
    @RequestMapping("/users")
    public class UserController {
    
        @RequestMapping("/sayHi")
        public String sayHi() {
            return "hello, Spring MVC";
        }
    
        // 其他方法...
    }
    

    在这个例子中,所有在 UserController 中的方法的基本URI为 "/users"。因此,sayHi 方法的完整URI变为 "/users/sayHi"。

  2. 方法级别:(也就是我们刚刚使用的那种)

    当 @RequestMapping 应用在方法级别而没有类级别的注解时,它指定了该特定方法的URI。
     
    @RestController
    public class UserController {
    
        @RequestMapping("/sayHi")
        public String sayHi() {
            return "hello, Spring MVC";
        }
    
        // 其他方法...
    }
    

    在这种情况下,sayHi 方法被映射到 "/sayHi" URI,没有来自类的额外基本URI。

这两种方法都是有效的,选择取决于应用程序对URI结构的期望。在类级别使用 @RequestMapping 可以方便地将相关的端点分组在一个公共的基本URI下,而方法级别的注解则适用于对单个端点进行更精细控制。

当一个类有多个注解时,这些注解没有先后顺序:

在Spring中,注解的顺序通常是没有特定要求的,因为每个注解都有自己的特定语义。但是,对于某些注解,特别是在类级别和方法级别都可以使用的注解(比如 @RestController),它们之间的顺序可能会影响其行为。

在 @RestController 的情况下,它是一个组合注解,包含了 @Controller 和 @ResponseBody 的功能。通常,@Controller 应该放在 @RestController 的前面,因为 @RestController 包含 @Controller 的语义,但是这并不是绝对的要求。

在实践中,通常会先放置类级别的注解,然后是方法级别的注解,如下所示:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/Hello")
public class UserController {

    @RequestMapping("/sayHi")
    public String sayHi() {
        return "hello, Spring MVC";
    }
}

在这个例子中,我添加了 @RequestMapping("/") 注解到类上,它用于指定类中所有处理方法的基础路径。这样,在访问 "/sayHi" 时,实际路径将为类路径+方法路径,即 "/Hello" + "/sayHi"。

总的来说,虽然注解的顺序在大多数情况下不是关键问题,但在一些特定情况下,特别是组合注解的情况下,确保正确的注解顺序是一个好的实践。

注意事项

在使用 @RequestMapping 注解时,URL路径的斜杠(/)的使用是相对灵活的,而且Spring会根据情况进行处理。

总体而言,有以下几点:

  1. 斜杠的前缀:

    • 如果在 @RequestMapping 注解的路径前没有加斜杠(/),Spring会在运行时自动添加一个斜杠。
    • 例如,@RequestMapping("users") 和 @RequestMapping("/users") 效果是一样的,最终都会映射到类路径后面加上 "/users"。
  2. 多层路径:

    • @RequestMapping 的路径也可以是多层次的,通过使用斜杠分隔不同的路径部分。
    • 例如,@RequestMapping("/users/admin") 表示该方法映射到 "/users/admin" 路径。

综合这些,Spring提供了一定的灵活性,以适应不同的URI路径风格和项目需求。不过,根据约定,我们通常建议还是在路径前加上斜杠,以提高路径的可读性和清晰度。

@RequestMapping 是 GET 还是 POST 请求?

通过浏览器访问的就是GET,这一点我们可以直接抓包看看:

至于怎么验证POST?我们直接使用Postman看看:

先试试GET:

POST:

依然正确响应。

所以我们的@RequestMapping 既支持 GET 也支持 POST 。

指定请求

注解里,双引号里的值会赋值给“value”这个属性,多个对多个属性赋值,需要写上属性名。只有一个属性时,且属性名为value,可以省略。

它有哪些属性呢?

此时重启,POST请求就会报错了:

请求

当我们访问不同的路径时,实际上就是发送不同的 HTTP 请求,这些请求可以包含各种类型的参数。学习 Spring 请求主要包括如何传递参数到后端以及后端如何接收和处理这些参数。

传递参数我们们主要是使用浏览器和Postman来模拟。

传递单个参数 

接收单个参数, 在 Spring MVC 中直接用方法中的参数就可以,比如:

先使用Postman来看看。我们传了一个参数: 

使用浏览器接收:

 

再换一个参数:

只选择age:

那如果我们age也不传会怎么样?

报错了。

出现报错,就去看错误码:

1. 404 错误:路径问题

当收到 HTTP 404 错误时,通常表示请求的资源未找到。在 Spring MVC 中,可能的原因包括:

  • 请求的路径映射错误,去检查 @RequestMapping 注解是否正确。
  • 控制器方法的路径和请求路径不匹配。
  • 查看项目的上下文路径(Context Path),有时会影响路径的匹配。

2. 500 错误:服务器内部错误

HTTP 500 错误表示服务器遇到了意外情况,无法处理请求。在 Spring MVC 中,可能的原因包括:

  • 控制器方法中的异常未被捕获,导致了异常栈的展示。在生产环境中,应该适当处理异常,不要将详细的异常信息暴露给用户。
  • 依赖的服务或数据库出现问题,导致了控制器方法无法正常执行。
  • 检查是否有不合法的输入或请求导致了异常。

3. 调试和查看错误日志

调试是解决问题的重要手段之一。可以通过以下方式进行调试:

  • 在控制台或日志中输出关键信息,例如方法的执行流程、输入参数等。
  • 使用断点在代码中设置调试点,观察程序的执行情况。

查看错误日志是一种重要的调试方式。在 Spring 项目中,一般会有日志文件(如 spring.log 或 catalina.out)。可以查看其中的异常堆栈信息,从底部向上逐段阅读,找出导致异常的代码行。

这个异常信息表明,在处理请求时,Spring MVC 发现了一个问题:

java.lang.IllegalStateException: Optional int parameter 'age' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as an object wrapper for the corresponding primitive type.

这个异常的主要内容是:

  1. Optional int parameter 'age' 表示在控制器方法的参数中有一个类型为 int 的参数名为 'age'。
  2. 'age' is present but cannot be translated into a null value due to being declared as a primitive type. 表示参数 'age' 存在,但由于它被声明为原始类型(primitive type),不能被转换为 null 值。

这是因为在 Java 中,原始类型(如 int)是不允许为 null 的,但在方法参数中使用 Optional 类型时,有可能传入 null。因此,建议将原始类型参数改为对应的包装类型,如将 int 改为 Integer。

对于包装类型, 如果不传对应参数,Spring 接收到的数据则为null。

所以企业开发中,对于参数可能为空的数据,建议使用包装类型

在 Java 中,基本数据类型(如 int、double、boolean 等)有默认值,而包装类型(如 Integer、Double、Boolean 等)可以表示空(null)。

当使用基本数据类型时,如果没有传递对应的参数,Spring MVC 会尝试将参数设置为默认值,这可能导致业务逻辑上的混淆。例如,对于一个 int 类型的参数,默认值是 0,这样就无法区分是客户端故意传递了 0,还是根本没有传递该参数。

使用包装类型可以更好地表示参数的状态,因为它们可以为 null,这样我们就可以明确知道参数是否被传递,而且可以在业务逻辑中更好地处理这种情况。

在 Spring MVC 的控制器方法中,如果我们希望参数可以为 null,可以将参数声明为包装类型,并在方法体内进行相应的判空处理。例如:

@RequestMapping("/example")
public String example(@RequestParam Integer age) {
    if (age == null) {
        // 处理未传递参数的情况
    } else {
        // 处理传递了参数的情况
    }
    // 其他业务逻辑
    return "success";
}

这样,我们就可以在方法内部明确处理参数为空的情况,而不会产生歧义。 

还有客户端传递的参数类型不匹配

如果客户端传递给参数的值不正确:

这个警告信息表明在处理请求时,Spring MVC 遇到了一个参数类型转换的问题:

org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "ahshfkas"

这个异常的主要内容是:

  • Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer' 表示在控制器方法的参数类型为 Integer,但尝试将一个类型为 java.lang.String 的值转换为 Integer 时失败。
  • nested exception is java.lang.NumberFormatException: For input string: "ahshfkas" 表示具体的转换失败原因,即试图将字符串 "ahshfkas" 转换为整数时发生了数字格式异常。

但是这里只是一个警告,而不是报错。

警告信息表明 Spring MVC 认为这个问题是由客户端输入的数据导致的,而不是后端代码的错误。这是框架的一种防御性机制,它告诉你的应用程序有一些请求参数的类型不匹配,但不会中断应用程序的正常运行。

在生产环境中,这样的警告信息通常是期望的行为,因为你不能总是控制客户端的输入。然而,开发过程中,这样的警告可以帮助你发现潜在的问题,例如客户端代码或前端表单验证可能存在的 bug。

传递多个参数
如何接收多个参数呢?

在 Spring MVC 中,接收多个参数的方式和接收单个参数类似,我们可以在控制器方法的参数列表中直接声明多个参数。这些参数可以是请求参数、路径变量等,具体取决于我们的业务需求。

先把后端代码写完:

    @RequestMapping("/T3")
    public Object method2(String name, Integer age) {
        return "接收到参数name:" + name + ", age:" + age;
    }

前端传递参数:

 参数的请求不分先后:

用浏览器看看:

好,如果这个时候我们的需求变更了,想再多增加一个参数,这个时候我们首先想到的两种方法:

变更我们刚刚的接口:

    @RequestMapping("/T3")
    public Object method2(Integer id,String name, Integer age) {
        return "接收到参数name:" + name + ", age:" + age;
    }

然后需要通知调用方,以后调用这个接口。

或者我们再添加一个新的接口,传递三个参数。但是后面也还是要通知调用方。

这样挺麻烦的,我们不如直接把它们封装为一个对象:

package com.example.UserControl;

public class UserInfo {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    private Integer age;

    @Override
    public String toString() {
        return "UserInfo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}
    @RequestMapping("/T3")
    public Object method2(UserInfo user) {
        return user.toString();
    }
那么怎么传递一个对象?

直接传递就好了:

如果这个时候我们不传递id:

再把后端代码改改:


当传递的是一个对象的时候,可以使用基本类型了。 

  1. 单参数传递时(基本数据类型,如int)

    • 当你在处理请求时,如果方法的参数是基本数据类型(例如int),而请求中没有传递对应的参数值,Spring MVC 会尝试将请求中的参数值映射到这个基本数据类型。但是,由于基本数据类型不能为null,如果请求中没有提供这个参数,就会导致类型不匹配的错误(500错误)。

    • 如果你将参数类型改为包装类型(如Integer),Spring MVC 就可以处理这个问题,因为包装类型可以为null。

  2. 多参数传递时(使用对象接收参数)

    • 当你使用对象来接收参数时,Spring MVC 会尝试创建这个对象的实例,并将请求中的参数值映射到对象的属性。对于对象的int属性,如果请求中没有提供对应的参数值,Spring MVC 会使用基本数据类型的默认值,比如0。

    • 在这种情况下,即使请求中没有提供对应的参数值,Spring MVC 不会报错,因为它是在对象级别上进行处理,而不是在基本数据类型的参数上。

综上所述,基本数据类型在单参数传递时可能会导致类型不匹配的错误,而在多参数传递时,Spring MVC 会使用基本数据类型的默认值。使用包装类型可以规避单参数传递时的错误,因为包装类型可以为null。

有时候我们想要创建带参数的构造方法时会报错:

你应该还记得,我们学构造方法的时候提过,在 Java 中,如果一个类没有显式地定义构造函数,Java 编译器会默认生成一个无参构造函数。这个默认的无参构造函数在很多场景下是非常有用的,比如通过反射创建对象、使用一些框架进行对象实例化(如 JSON 转换工具)等。

当我们在一个类中显式地定义了任何构造函数(无论是无参构造函数还是带参数的构造函数),Java 编译器就不再提供默认的无参构造函数。这是因为在 Java 规范中规定,如果一个类有显式的构造函数,编译器就不会再自动生成无参构造函数。

在使用 JSON 转换工具时,比如 Jackson 库,它在进行对象的反序列化(将 JSON 转为对象)时,默认使用无参构造函数来创建对象实例。如果此时我们显式地在类中定义了带参数的构造函数,而没有提供一个无参构造函数,JSON 转换工具在实例化对象时就无法找到默认的无参构造函数,从而导致报错。

为了解决这个问题,我们可以在类中显式地添加一个无参构造函数,即使我们已经定义了带参数的构造函数。这样就能够保留默认的无参构造函数,使 JSON 转换工具能够正常使用

如何对参数进行重命名?

比如我现在想把name改成username,但是前端识别不了。

这个时候在改动过的地方加上@RequestParam("name"):

    @RequestMapping("/T4")
    public Object method(@RequestParam("name") String username,Integer age) {
        return "接收到参数 username:"+username+" age:"+age;
    }

通过这个注解,我们就可以进行重命名。

@RequestParam从请求中获得name的值,并赋值给username。 

如果不传递name:

必须传递 "name" 这个参数,它却不存在: 

2023-11-26 17:03:54.554  WARN 19528 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'name' for method parameter type String is not present]

在我们的方法签名中,@RequestParam("name") String username 显式地指定了请求参数 name 的名称,并且将其映射到了方法的 username 参数。由于在请求中我们没有传递 name 参数,Spring MVC 认为这是一个缺失的必需参数,因此会导致 400 错误。

如果希望 name 参数是可选的,可以修改方法签名,将 @RequestParam("name") 改为 @RequestParam(name = "name", required = false)。这样设置了 required = false 后,name 参数就变成可选的,如果请求中没有提供 name 参数,Spring MVC 会将其值设置为 null。

    @RequestMapping("/T4")
    public Object method(@RequestParam(name = "name", required = false) String username, Integer age) {
        return "接收到参数 username:" + username + " age:" + age;
    }

传递数组
    @RequestMapping("/T5")
    public Object method1(String[] arr) {
        return Arrays.toString(arr);
    }

由于发送请求的时候,传递的是一个字符串,不能确认返回的是不是真的是一个数组,所以我们来判断一下: 

    @RequestMapping("/T5")
    public Object method1(String[] arr) {
        return Arrays.toString(arr)+",length:"+arr.length;
    }

如果返回长度是1,那就是字符串。 

 

可以确定,返回的是数组。 

还有一种传递数组的方式:多个同名的参数会被拼接成一个数组:

如何传递集合
    @RequestMapping("/T6")
    public String T6(List<String> list) {
        return list.toString();
    }

看错误日志:

2023-11-26 17:27:03.981 ERROR 4840 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List] with root cause

这个错误表明在处理请求时出现了异常,根本原因是找不到List接口的主要构造函数。这通常是因为 Spring 在尝试实例化某个类时,找不到满足要求的构造函数。

集合参数和数组类似, 同⼀个请求参数名有为多个, 且需要使用@RequestParam 绑定参数关系。

这是因为默认情况下,请求中参数名相同的多个值,是封装到数组。

 如果要封装到集合,就要使用 @RequestParam 绑定参数关系。

    @RequestMapping("/T6")
    public String T6(@RequestParam("list") List<String> list) {
        return list.toString()+list.size();
    }

长度是3,进行了分割,不是字符串。

如果不勾选:

所以: 

    @RequestMapping("/T6")
    public String T6(@RequestParam(value = "list",required = false) List<String> list) {
        return list.toString()+list.size();
    }

还在报错,只不过错误码换了:

通过错误日志可知:

2023-11-26 17:36:41.469 ERROR 19500 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

 list空指针了。

所以我们要进行一些一些判断:

    @RequestMapping("/T6")
    public String T6(@RequestParam(value = "list",required = false) List<String> list) {
       if(list!=null){
           return list.toString()+list.size();
       }
        return "list为空";
    }

传递JSON数据

JSON概念

JSON(JavaScript Object Notation)是一种轻量级的数据交互格式。它基于 ECMAScript  (欧洲计算机协会制定的js规范) 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。

简单来说,JSON是一种数据格式,具有自己的格式和语法。它使用文本来表示对象或数组的信息,因此本质上是字符串。JSON主要用于在不同的编程语言之间进行数据传递和交换,因为它是一种通用的数据格式,易于解析和生成。JSON的结构清晰,易于阅读和编写,成为前后端数据交互的常用格式。

JSON与Javascript的关系

JSON(JavaScript Object Notation)与JavaScript之间有一定的关系,但它们并不是完全相同的概念。

JSON是一种数据交换格式,独立于任何编程语言,它使用文本格式表示数据。JSON的语法受到JavaScript对象字面量的影响,因此在JavaScript中使用JSON的语法会更加自然。JSON的数据结构包括对象(由花括号{}表示)和数组(由方括号[]表示),这与JavaScript中的对象和数组相似。

JavaScript是一种编程语言,而JSON仅仅是一种数据格式。在JavaScript中,可以使用内置的JSON对象来解析JSON字符串和序列化JavaScript对象为JSON字符串。这种相似的语法使得在JavaScript中处理JSON数据变得非常方便。

总体而言,JSON与JavaScript之间的关系在于JSON的语法受到JavaScript对象字面量的影响,因此JavaScript中可以轻松地处理JSON格式的数据。

JSON语法

JSON(JavaScript Object Notation)是一个字符串,其格式类似于JavaScript对象字面量。

我们先来看⼀段JSON数据:

{
  "animalKingdom": {
    "king": {
      "name": "Leo the Lion",
      "age": 8,
      "title": "King of the Jungle",
      "subjects": [
        {
          "name": "Ellie the Elephant",
          "age": 15,
          "specialty": "Trunk Acrobatics"
        },
        {
          "name": "Gerry the Giraffe",
          "age": 10,
          "specialty": "Neck Stretching"
        },
        {
          "name": "Zara the Zebra",
          "age": 5,
          "specialty": "Fast Stripes"
        }
      ]
    },
    "queen": {
      "name": "Luna the Leopard",
      "age": 7,
      "title": "Queen of Spots",
      "subjects": [
        {
          "name": "Milo the Monkey",
          "age": 6,
          "specialty": "Tree Swinging"
        },
        {
          "name": "Oscar the Owl",
          "age": 3,
          "specialty": "Nighttime Wisdom"
        }
      ]
    }
  }
}

SON数据可以进行压缩以减小文件大小,提高传输效率。压缩JSON通常涉及删除不必要的空格、换行和缩进。以下是相同JSON数据的压缩表示:

{"animalKingdom":{"king":{"name":"Leo the Lion","age":8,"title":"King of the Jungle","subjects":[{"name":"Ellie the Elephant","age":15,"specialty":"Trunk Acrobatics"},{"name":"Gerry the Giraffe","age":10,"specialty":"Neck Stretching"},{"name":"Zara the Zebra","age":5,"specialty":"Fast Stripes"}]},"queen":{"name":"Luna the Leopard","age":7,"title":"Queen of Spots","subjects":[{"name":"Milo the Monkey","age":6,"specialty":"Tree Swinging"},{"name":"Oscar the Owl","age":3,"specialty":"Nighttime Wisdom"}]}}}

这个压缩后的表示消除了所有的空格和缩进,使其更加紧凑。在实际传输和存储中,压缩的JSON通常更受欢迎,因为它减小了数据的大小。

JSON的语法

JSON的语法规则简单而直观,这使得它成为一种理想的数据交换格式。

  1. 键值对 (Key/Value):数据以键值对的形式存在,键和值之间用冒号分隔。
  2. 逗号分隔:不同的键值对之间用逗号分隔。
  3. 对象表示:对象由花括号 {} 表示,其中包含零个或多个键值对。
  4. 数组表示:数组由方括号 [] 表示,其中包含零个或多个值,值可以是对象、数组、字符串、数字、布尔值或null。
  5. 嵌套结构:JSON支持嵌套结构,可以在对象内部包含其他对象,或在数组内部包含其他数组或对象。

JSON的两种结构 

1、对象结构:用大括号 {} 包裹的无序键值对集合。每个键值对由键和值组成,键和值之间使用冒号 : 分隔,键值对之间使用逗号 , 分隔。对象提供了一种描述实体的方式,其中实体的特征由键值对表示。

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

2、数组结构:用中括号 [] 包裹的有序值的集合。值之间使用逗号 , 分隔。数组提供了一种表示列表或有序集合的方式,其中值可以是任意类型的数据,包括对象、数组、字符串、数字、布尔值或null。

[
  "apple",
  "orange",
  "banana"
]

这两种结构的组合和嵌套使得JSON成为一种灵活且强大的数据表示格式。

这些都是合法的JSON数据:

{"name":"admin","age":18}
["hello", 3.1415, "json"]
[{"name":"admin","age":18},{"name":"root","age":16},{"name":"张三","age":20}]

我们刚刚的动物王国JSON数据也可以分解成:

  • 整体结构:该JSON数据以对象的形式表示整个动物王国。对象包括 kingdom、description 和 categories 三个键值对。
  • kingdom 和 description:这两个键分别存储了动物王国的名称和描述信息。
  • categories:这是一个包含不同动物类别的数组。每个类别是一个对象,包含 name(类别名称)和 examples(该类别下的动物列表)两个键值对。
  • examples:每个类别下有一个包含动物信息的数组。每个动物的信息也是一个对象,包括 name(动物名称)、habitat(栖息地)和 population(种群数量)三个键值对。

 关于压缩数据,不方便判断的时候可以使用在线JSON格式化⼯具来进行校验和书写: JSON 在线解析 | 菜鸟工具 (runoob.com)

JSON字符串和Java对象互转

  • JSON(JavaScript Object Notation)是一种数据交互格式,本质上是一个字符串,通过文本来存储和描述数据。
  • Spring MVC框架集成了JSON的转换工具,可用于实现JSON字符串和Java对象的相互转换。

jackson-databind是JSON转换的工具包之一,提供了功能强大的对象映射。

jackson-databind是JSON转换的工具包之一,而且有很多其他的 JSON 转换工具包。不同的工具包可能有不同的特性和适用场景。以下是一些常见的 JSON 转换工具包:

  1. Gson: 由Google开发的JSON库,用于在Java对象和JSON之间进行转换。它简单易用,广泛应用于Android开发。

  2. Fastjson: 阿里巴巴开发的JSON库,性能较高。它支持更多的特性,例如自定义序列化和反序列化,以及对JSONPath的支持。

  3. JSON.simple: 一个轻量级的Java库,用于处理JSON数据。它的设计目标是简单性,适用于较小的项目或对功能要求不高的应用。

  4. Moshi: 由Square公司开发的JSON库,专注于Android平台。它提供了一种简洁的方式来序列化和反序列化JSON数据。

  5. Boon: 一个快速、轻量级的JSON库,具有简单的API。它的设计目标是提供高性能和低内存消耗。

  6. LoganSquare: 一个高性能的JSON库,专注于Android平台。它使用了预编译的技术,提供了快速的序列化和反序列化能力。

在Java和Android开发中,jackson-databind、Gson和Fastjson是比较常见和广泛使用的JSON库。

在Spring MVC框架中,jackson-databind已经被引入,无需额外配置。

1、若脱离Spring MVC使用,需要引入相关依赖:

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

2、Spring MVC框架中Spring Boot已经默认把Jackson集成进来了,直接新建一个类,直接使用:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JSONUtils {

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        UserInfo userInfo = new UserInfo();
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        userInfo.setId(12);

        //对象转json
        String s = objectMapper.writeValueAsString(userInfo);

        System.out.println(s);

        //json字符串转成Java对象
        UserInfo userInfo1 = objectMapper.readValue(s, UserInfo.class);
        System.out.println(userInfo1);

    }
}

在 main 方法中,我们首先创建了一个 ObjectMapper 实例,这是 Jackson 库的核心类,专门用于实现对象和 JSON 字符串之间的相互转换。接着,我们创建了一个 UserInfo 对象,并为其设置了一些属性值。

对象转 JSON 部分:
通过调用 objectMapper.writeValueAsString(userInfo) 方法,将 userInfo 对象转换为 JSON 字符串,并将结果存储在字符串变量 s 中。这一步模拟了将 Java 对象序列化为 JSON 格式的过程。

JSON 转对象部分:
使用 objectMapper.readValue(s, UserInfo.class) 方法,将 JSON 字符串 s 转换为 UserInfo 类型的 Java 对象,并将结果存储在 userInfo1 中。这一步模拟了将 JSON 字符串反序列化为 Java 对象的过程。

 JSON优点详解:

  1. 简单易用: JSON的语法设计简洁清晰,易于理解和编写。它采用键值对的形式表示数据,使得数据结构直观,有助于快速上手和使用。

  2. 跨平台支持: JSON是一种独立于编程语言的数据格式,可以被多种编程语言解析和生成。这意味着在不同的平台和使用不同编程语言的系统之间,可以轻松地进行数据交换和传输。

  3. 轻量级: 相比XML等其他数据交换格式,JSON数据格式更加轻量。它不需要繁重的标签,而是采用简洁的键值对和数组表示,减少了数据冗余,有助于提高数据传输效率,尤其在网络传输中更为高效。

  4. 易于扩展: JSON的数据结构支持嵌套对象和数组,这使得数据模型可以灵活扩展。开发人员可以轻松地添加新的字段或嵌套新的数据结构,而不破坏原有的数据格式,提高了数据模型的灵活性和可维护性。

  5. 安全性: JSON数据格式是一种纯文本格式,不包含可执行代码。与某些其他数据格式相比,JSON不涉及执行程序或脚本的能力,因此不容易受到恶意代码注入的攻击。这种特性增强了数据的安全性,使其更适用于从不受信任的来源接收数据。

总的来说,JSON的设计使其成为一种高效、灵活、易于使用和安全的数据交换格式,适用于各种应用场景,在Web应⽤程序中被广泛使用, 如前后端数据交互、API接口数据传输等。

传递JSON对象 

接收JSON对象, 需要使用 @RequestBody 注解。

@RequestBody 注解作用在请求正文(Request Body)的数据绑定上,它告诉 Spring MVC 框架将请求体的内容解析为方法参数的类型。在传统的 HTTP 请求中,请求参数通常是通过 URL 参数(QueryString)或者表单数据的形式传递的。然而,在某些场景下,特别是在前后端分离的应用中,我们希望能够通过请求的正文直接传递结构化的数据,比如 JSON 或 XML 数据。

  • 请求正文(Request Body): HTTP 请求中的正文部分,通常包含了客户端发送到服务器的数据。对于 POST 请求,请求正文就是 POST 请求的主体部分,其中包含了实际的数据。@RequestBody 注解就是用来处理请求正文的数据。

  • 数据绑定: 意味着将请求体中的数据与方法参数进行绑定。在 Spring MVC 中,这通常涉及到将 JSON 或其他格式的数据转换为 Java 对象,或者将其绑定到方法参数上。

  • 请求参数必须在请求正文中: 这部分内容强调了使用 @RequestBody 注解时,请求参数是通过请求的正文中传递的,而不是通过 URL 参数或表单数据传递的。因此,要使用这个注解,客户端在请求时需要将数据放在请求的正文中,而不是通过 URL 参数传递。

总体而言,@RequestBody 注解提供了一种方便的方式,让开发者能够轻松地接收和处理通过请求正文传递的数据,特别是在处理结构化数据(如 JSON)的情境下。

    @RequestMapping("/r7")
    public String r7(@RequestBody UserInfo userInfo){
        return userInfo.toString();
    }

023-11-26 19:47:47.233  WARN 12008 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.String com.example.UserControl.UserController.r7(com.example.UserControl.UserInfo)]

 这个警告表明在处理请求时发生了 HttpMessageNotReadableException 异常,具体的错误信息是 "Required request body is missing"。这个异常通常出现在使用 @RequestBody 注解的方法上,表示请求中缺少必要的请求体(Request Body)。

抓包分析后知道,我们现在要把请求正文赋值给userInfo,但是请求正文却为空,没有办法转换。

我们请求发错了!

应该这样:

获取URL中参数@PathVariable

学过网络爬虫的人可能比较熟悉,我们打开几篇网络静态网页,看看新闻头条、文章:

@PathVariable 是 Spring MVC 中用于从 URI 中提取变量值的注解。它允许我们将 URI 模板中的变量部分映射到方法的参数上。在 RESTful Web 服务中,通常需要从 URL 中提取参数,@PathVariable 就是用来完成这个任务的。
下面是一个简单的例子,演示了如何在 Spring MVC 中使用 @PathVariable:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{userId}")
    public ResponseEntity<String> getUserById(@PathVariable Long userId) {
        // 处理逻辑,根据 userId 获取用户信息
        return ResponseEntity.ok("User ID: " + userId);
    }
}

在上面的例子中,@GetMapping("/{userId}") 中的 {userId} 是一个占位符,它表示这个位置可以接受一个变量值。然后,通过 @PathVariable 注解将这个变量绑定到方法的参数上,这里是 Long userId。当请求类似于 /api/users/123 时,123 就会被传递给 userId。

总的来说,@PathVariable 提供了一种方便的方式,让我们可以从 URI 中获取动态的参数值,使得 RESTful API 中的参数传递更加灵活。

 现在我们回到正题:

    @RequestMapping("/r8/{articleId}")
    public String r8(@PathVariable Integer articleId){
        return "articleId:"+articleId;
    }

报错说这个参数也是一个必传的参数,但是它现在没有找到……

所以我们如果不想用它,需要手动改为false。 

但是使用URL是有限制条件的:

现在是正常的:

把19去掉:

报错是因为它找不到第二个参数,我们只给了两个参数,它只找得到一个。

你也可以进行重命名:

    @RequestMapping("/r9/{name}/{age}")
    public String r9(@PathVariable("name") String userName, @PathVariable Integer age){
        return "name:"+userName+",age:"+age;
    }

上传文件@RequestPart 

@RequestPart 是 Spring MVC 中用于处理 multipart 请求中的单个部分的注解。Multipart 请求通常用于文件上传,当客户端通过表单上传文件时,请求的 Content-Type 会被设置为 multipart/form-data。

@RequestMapping("/r11")
public String r11(@RequestPart("file") MultipartFile file) throws IOException {
    return "获取上传文件: " + file.getOriginalFilename();
}

 

但是我们平常操作文件肯定不是只获取一下文件的名字,还得对它进行一些操作:

    @RequestMapping("/r11")
    public String r11(@RequestPart("file") MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        file.transferTo(new File("D:/temp/"+fileName));
        return "获取上传文件: " + file.getOriginalFilename();
    }

不加这个@RequestPart注解行不行?其实也可以……可以自已验证一下。

获取Cookie/Session

回顾 Cookie

HTTP 协议自身是属于 "⽆状态" 协议。

HTTP 协议被描述为 "无状态" 是指每个请求都是独立的,服务器不会保留任何关于客户端请求的信息。每个请求都包含了客户端的所有信息,服务器不会在两个请求之间保留任何状态。这种设计简化了协议,提高了可伸缩性,并使得服务端更容易实现。

"无状态" 的设计原则有一些优点,如:

  1. 简化设计: 无状态设计简化了协议和服务端的实现。每个请求都是独立的,不依赖于之前的请求,这使得系统更容易理解和维护。

  2. 可伸缩性: 由于每个请求都是独立的,服务器可以更容易地扩展,而不用担心维护每个客户端的状态信息。

  3. 灵活性: 无状态设计使得客户端和服务器之间的交互更为灵活,因为它们不需要维护复杂的状态关系。

然而,在某些场景下,需要保留一些状态信息以便在请求之间共享数据。这时,一些机制,比如 Cookie、Session 等就被引入,以在一定程度上维护状态信息。

生活中通常会使用类似 Session 或 Token 的机制来维护用户的登录状态。这些机制通过在请求中携带特定的信息来创建一种间接的状态维护机制,以实现用户登录信息的跨请求保持。

"令牌"通常是在身份验证过程中生成的一段字符串,它的目的是为了标识用户的身份,而不必在每个请求中都携带用户名和密码。这个令牌通常存储在客户端,比如浏览器的Cookie中。

在使用"令牌"进行身份验证时,服务器需要验证客户端请求中的令牌,并确定该令牌对应的用户。这就引入了会话(Session)的概念。会话是指客户端与服务器之间的一系列交互,而 Session 机制就是为了在这些交互中保持用户的状态和信息。

下面是一般的流程:

  1. 用户登录: 用户提供用户名和密码,服务器验证通过后生成一个令牌,并将这个令牌返回给客户端。

  2. 令牌存储: 客户端通常会将令牌存储在 Cookie 中,以便在后续的请求中自动发送给服务器。

  3. 请求时的令牌验证: 每次请求时,客户端会自动在请求中携带令牌。服务器通过验证这个令牌来确定请求的合法性和对应的用户身份。

  4. Session 创建: 为了更好地管理用户的状态,服务器会创建一个 Session 对象,其中包含了与该用户相关的信息,比如用户的身份、权限等。

  5. Session ID: 服务器将这个 Session 对象的标识(通常是一个 Session ID)返回给客户端(通过set-Cookie的方式)。

  6. Session 的存储: 为了在多个请求之间保持状态,服务器会将 Session 对象存储在服务器端,通常在内存或持久化存储中。后续的请求中,客户端带着Session ID去请求的,也就是带着Cookie的信息去请求。

  7. 会话终止: 当会话结束时,可以是用户注销、过期时间到达或其他条件触发,服务器会清除相应的 Session 信息。

Session 机制的核心思想是在服务端维护一个与客户端相关的状态信息,通过 Session ID 将客户端与服务器上的 Session 对象关联起来。这样,服务器可以根据 Session ID 找到对应的 Session 对象,从而获取用户的状态信息。

Session的本质是一个 "哈希表",存储了一些键值对结构。Key 就是 SessionID,Value 就是用户信息。SessionId 是由服务器生成的一个 "唯一性字符串",在用户登录时,服务器在 Session 中新增一个新记录,并把 sessionId 返回给客户端(通过 HTTP 响应中的 Set-Cookie 字段返回)。

后续客户端的请求会携带这个 sessionId(通过 HTTP 请求中的 Cookie 字段),服务器根据 sessionId 在 Session 信息中获取到对应的用户信息,再进行后续操作。如果找不到对应的 Session 信息,服务器会重新创建 Session,并将新的 SessionID 返回给客户端。

默认情况下,Session是保存在服务器的内存中的。如果服务器重启,Session 数据就会丢失。要解决这个问题,可以使用一些持久化的方案,比如将 Session 数据存储在数据库或其他持久化存储中。这样,即使服务器重启,用户的 Session 数据也能被恢复。

总的来说,"令牌"用于在客户端和服务器之间传递身份验证信息,而 Session 机制用于在服务器端保持用户的状态信息。这样,服务器既可以验证用户的身份,又可以维护用户的状态,实现更复杂的应用逻辑。

Cookie 和 Session 的区别:

  1. 机制:

    • Cookie: 是客户端保存用户信息的一种机制,将数据存储在客户端的浏览器中。
    • Session: 是服务器端保存用户信息的一种机制,将数据存储在服务器上。
  2. 关联:Cookie 和 Session 主要通过 SessionId 关联起来,SessionId 是 Cookie 和 Session 之间的桥梁。

  3. 配合使用:

    • Cookie 和 Session经常会在一起配合使用,但不是必须的。
    • 完全可以使用 Cookie 单独保存一些数据在客户端,这些数据不一定是用户身份信息,也不一定是 SessionId。
    • Session 中的 SessionId 也不需要非得通过 Cookie/Set-Cookie 传递,可以通过其他方式,比如通过URL传递。

总体而言,Cookie和Session在Web开发中通常一起使用,Cookie用于在客户端存储少量数据,而Session则在服务器端保持和管理用户的状态信息。SessionId作为一个标识符,连接了这两者,使得服务器能够识别和管理用户的状态。

获取Cookie

传统获取Cookie

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;


@RestController
@RequestMapping("/request")
public class RequestController {
    @RequestMapping("/getCookie")
    public String getCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        Arrays.stream(cookies).forEach(c -> {
            System.out.println(c.getName() + ":" + c.getValue());
        });
        return "获取Cookie成功";
    }

Spring MVC是基于Servlet API构建的Web框架,并且充分利用了 Servlet API 的特性来处理 Web 请求和响应。它直接继承自Servlet,所以 Servlet 有的它也有。

在这个框架中,HttpServletRequest和HttpServletResponse是内置对象,用于处理HTTP请求和响应。

HttpServletRequest代表客户端的请求,包含了客户端通过HTTP协议访问服务器时请求头中的所有信息。通过这个对象提供的方法,我们可以轻松获取客户端请求的各种信息。

HttpServletResponse代表服务器的响应,包含了HTTP响应的信息,如向客户端发送的数据、响应头、状态码等。通过这个对象提供的方法,我们可以得到服务器响应的所有内容。

Spring MVC在Servlet的基础上对这两个对象进行了封装,为开发者提供了更加简便的使用方式。这些对象的直接声明就可在控制器方法中使用,使得处理Web请求和生成响应变得更加方便。

错误信息表明在请求处理过程中发生了空指针异常 (java.lang.NullPointerException)。这种异常通常是因为在代码中对一个空对象执行了操作,导致无法访问对象的属性或调用对象的方法。

在我们的代码中,可能的引发空指针异常的地方是 request.getCookies() 方法,如果请求中没有 Cookie,该方法将返回 null。在代码中没有足够的空指针检查,因此可能导致异常。

为了解决这个问题,我们可以在调用 request.getCookies() 之前添加一个非空检查,以确保 cookies 不为 null。

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@RestController
@RequestMapping("/request")
public class RequestController {
    @RequestMapping("/getCookie")
    public String getCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            Arrays.stream(cookies).forEach(c -> {
                System.out.println(c.getName() + ":" + c.getValue());
            });
        }
        return "获取Cookie成功";
    }

Postman也可以:

简洁获取Cookie

也有更简洁的方式获取Cookie:

    @RequestMapping("/getCookie2")
    public String getCookie2(@CookieValue("satoru") String satoru){
        return "Gojo:"+satoru;
    }

先使用"/getCookie"去访问,可以获得所有Cookie: 

再使用"/getCookie2"去访问,只能得到当前的Cookie2:

获取Session

Session 存储和获取 :

Session是服务器端的机制,我们需要先存储,才能再获取 Session。

我们在存储和获取 Session 数据时,需要使用 HttpServletRequest 对象。这样,服务器可以根据请求中的 Session ID 关联到相应的 Session 对象,实现数据的持久化和跨请求的数据传递。

Session存储

    @RequestMapping("/setSession")
    public String setSession(HttpServletRequest request){
        HttpSession session = request.getSession();
        session.setAttribute("userName","zhangsan");
        return "设置Session成功";
    }

获取Session有三种方式:

1、传统方法:

    @RequestMapping("/getSession")
    public String getSession(HttpServletRequest request){
        HttpSession session = request.getSession();
        String userName = (String)session.getAttribute("userName");
        return "登录用户:"+userName;
    }

 

  1. HttpSession getSession(boolean create):

    • 该方法用于获取与请求关联的 HttpSession 对象。
    • 如果参数 create 为 true,则当不存在会话时会创建一个新的会话。
    • 如果参数 create 为 false,则当不存在会话时返回 null,而不会创建新的会话。
  2. HttpSession getSession():

    • 与 getSession(true) 的含义相同,即默认值为 true。
    • 如果会话存在,则返回关联的 HttpSession 对象;如果不存在,则创建一个新的会话并返回。
  3. void setAttribute(String name, Object value):

    • 该方法用于将指定名称的属性绑定到 HttpSession 对象上。
    • 这样可以在会话之间存储和传递对象,通过指定的名称可以在后续的请求中获取相应的属性值。

这些方法是 HttpSession 在 Java Web 开发中常用于存储和管理用户状态信息的核心操作。通过这些方法,可以实现在会话期间持久化存储和获取数据,方便在多个请求之间共享和传递信息。

2、HttpSession 是 Java Servlet 规范中定义的一个接口,用于在服务器端存储和管理用户的状态信息。在 Java Web 开发中,HttpSession 可以看作是一个内置对象,因为它是在 Servlet 容器中为每个用户请求自动创建和管理的。

HttpSession 提供了一种在多个请求之间存储和共享数据的机制。

// 存储数据到会话中
session.setAttribute("username", "John");

// 从会话中获取数据
String username = (String) session.getAttribute("username");

这样,HttpSession 对象可以用于在整个用户会话期间保持状态信息。 

所以:

    @RequestMapping("/getSession2")
    public String getSession(HttpSession session){
        String userName = (String)session.getAttribute("userName");
        return "登录用户:"+userName;
    }

 

3、简洁获取Session:

    @RequestMapping("/getSession3")
    public String getSession(@SessionAttribute(value = "userName") String userName){
        return "登录用户:"+userName;
    }

 

但是这里还是有一个问题,我们如果不先setSession,直接getSession3,就会报错。相当于这里的Session不允许为空: 

    @RequestMapping("/getSession3")
    public String getSession(@SessionAttribute(value = "userName", required = false) String userName){
        return "登录用户:"+userName;
    }
获取Header

传统获取 Header (一次性获取多个)

获取Header也是从 HttpServletRequest 中获取:

    @RequestMapping("/getHeader")
    public String getHeader(HttpServletRequest request){
    String userAgent = request.getHeader("User-Agent");
    return userAgent + ":"+userAgent;
 }

简洁获取(获取少量): 

    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("User-Agent") String userAgent){
        return userAgent + ":"+userAgent;
    }

 

响应

在我们前面的代码例子中,都已经设置了响应数据。Http响应结果可以是数据,也可以是静态页面,还可以针对响应设置状态码、Header信息等。这灵活的设置允许开发者更精细地控制网络请求的各个方面,以满足不同场景下的需求。

返回静态页面

首先需要创建前端页面 index.html (注意路径):

访问URL,网页地址直接输入:127.0.0.1:8080/index.html 即可,不需要加上 @RequestMapping("/Response") 和 @RequestMapping("/return") 里面的内容。

如果又在static里面加了一层目录,我们访问URL的时候就也需要加上这层目录:

如果不加:

把URL补充完整: 

注意事项:

1、相对于当前请求路径 和 相对于应用的根路径:

在@RequestMapping注解中,路径是否以斜杠(/)开头主要取决于路径的解析规则。Spring MVC 提供了一种相对路径的解析方式,具体规则如下:

  1. 相对路径(不以斜杠开头):如果路径不以斜杠开头,表示相对于当前请求路径。例如,假设当前请求的路径是 /example/current,而@RequestMapping("return")表示请求路径为 /example/current/return。

  2. 绝对路径(以斜杠开头):如果路径以斜杠开头,表示相对于应用的根路径。例如,假设应用部署在 http://localhost:8080/myapp,而@RequestMapping("/return")表示请求路径为 http://localhost:8080/myapp/return。

在我们的代码中,@RequestMapping("Response")指定了相对路径,因为路径不以斜杠开头。这将映射到当前请求路径的下级路径,例如 /example/current/Response。如果想要指定绝对路径,可以使用斜杠,如@RequestMapping("/Response")。

总的来说,相对路径更适合在处理一组相关请求时,而绝对路径更适合在整个应用中使用。选择使用哪种路径取决于我们的需求和应用的结构,但是我们一般还是建议使用斜杠开头。

2、启动多个Tomcat

servlet 路径有项目名,是因为一个tomcat下面可以部署多个项目。我们需要通过路径来进行区分:

Spring路径不需要有项目名,是因为Spring Boot内置了tomcat,一个 tomcat下面就部署当前这一个项目。

  1. Servlet 路径有项目名:

    • 应用部署方式: 在传统的Java Web应用中,WAR(Web Application Archive)文件通常包含一个项目,该项目会被部署到Servlet容器(比如Tomcat)中。每个项目都有一个唯一的上下文路径,这个路径通常被称为项目名。
    • 多项目管理: 由于一个Servlet容器可能运行多个应用,通过在URL中包含项目名,可以清晰地区分不同的应用。例如,http://localhost:8080/myapp/servletPath中的/myapp是项目名。
  2. Spring Boot 路径不需要有项目名:

    • 嵌入式Servlet容器: Spring Boot应用内嵌了Servlet容器(如Tomcat、Jetty或Undertow)。这样,整个应用以及其依赖都打包在一个可执行的JAR文件中,使得部署变得简单。
    • 独立运行: 由于Spring Boot应用是自包含的,它们可以在同一服务器上独立运行,不受传统项目名的限制。Spring Boot应用的URL路径相对简洁,不需要包含项目名。例如,http://localhost:8080/springpath中没有项目名。

总体而言,这些差异反映了传统Java Web应用和现代Spring Boot应用之间的演进。Spring Boot采用了约定大于配置的理念,通过内置Servlet容器、自包含的JAR文件等特性,使得开发者在构建和部署应用时更加便捷,同时也提供了更大的灵活性。

如果你需要同时在Spring Web中部署多个项目,就要启动多个tomcat。

(1)、一个项目部署多个服务

在工具栏找到services:专业版是竖着的,社区版应该在底部横着:

社区版选择: 

 专业版得选择Spring Boot。接下来操作基本相同,我就以专业版演示:

 

 

 

 

 

(2)、部署多个项目:给不同的项目设置不同的端口号

在Spring Boot应用的配置文件(application.properties或application.yml)中,可以设置不同的端口号。

# application.properties for Project 1
server.port=8080

# application.properties for Project 2
server.port=8081

这样,我们就可以通过不同的端口访问不同的Spring Boot应用。

或者使用application.yml:

# application.yml for Project 1
server:
  port: 8080

# application.yml for Project 2
server:
  port: 8081

这样启动不同的Spring Boot应用时,它们会分别监听不同的端口。确保端口之间没有冲突,这样就能够在同一个Tomcat实例中部署多个项目。

返回数据

关于刚刚的静态页面的例子,我们继续:

@RestController 换成 @Controller 会怎样?

我们先来看一组对比:

使用 @RestController,传递静态页面:

返回的是字符串: 

返回静态页面: 

 

使用 @RestController,传递字符串:

成功返回:

 使用 @Controller,传递静态页面:

并不会像@RestController一样返回字符串:

倒是可以成功返回静态页面:

使用@Controller,传递字符串:

返回字符串失败:

此时加上 @ResponseBody 注释:

成功返回字符串: 

所以这就不禁引发我们思考:难道 @RestController = @Controller + @ResponseBody

没错,就是这样。为了了解得更加清楚,我们先来看看这三个注释的定义具体是什么: 

1、@Controller

当我们在Spring框架中使用@Controller注解标记一个类时,这个类就成为了Spring MVC中的一个控制器。控制器的主要作用是处理用户发起的HTTP请求,执行相应的业务逻辑,并返回一个HTTP响应。

具体而言,@Controller的作用包括:

  • 标识为控制器: @Controller告诉Spring框架,被注解的类是一个处理HTTP请求的控制器。这样,Spring就能够识别和管理这个类。
  • 处理请求: 在@Controller标记下的类通常包含处理HTTP请求的方法。这些方法使用@RequestMapping等注解标识了可以处理的请求路径和方法。例如,一个处理GET请求的方法可能用@GetMapping("/example")标识。
  • 业务逻辑: 控制器中的方法包含了业务逻辑,即对请求的处理过程。这可能涉及调用服务层的方法、访问数据库等。
  • 返回响应: 控制器的方法执行完成后,通常会返回一个表示响应的对象。这可以是一个视图名、一个JSON对象、一个HTML页面等,具体取决于应用的需求。
  • 整合前端和后端: 控制器在前端(用户通过浏览器发起的请求)和后端(业务逻辑的执行)之间充当了桥梁。它帮助实现前后端的交互,将用户请求映射到相应的处理方法上,并将处理结果返回给前端。

总体来说,@Controller的功能是定义和组织处理HTTP请求的逻辑单元,使得我们能够通过代码来响应用户的操作,并完成与前端的交互。

 2、@ResponseBody


@ResponseBody是Spring框架中的注解,用于指示方法的返回值应该直接作为HTTP响应体返回,而不是被解析为视图。这意味着返回的数据将以原始形式直接写入HTTP响应。

具体而言,@ResponseBody的功能包括:

  • 指示返回类型: 当方法被@ResponseBody注解标记时,Spring框架知道方法的返回值应该直接写入HTTP响应,而不是交给视图解析器处理。
  • 处理非视图数据: 通常,控制器方法的返回值被解析为视图,然后由视图解析器渲染成HTML等。但是,使用@ResponseBody可以直接返回非视图数据,如JSON、XML、字符串等。
  • 修饰类: 如果 @ResponseBody 注解修饰在类上,表示该类的所有方法都默认返回数据,而不是视图。这在编写 RESTful 风格的控制器时非常有用。
  • 修饰方法: 如果 @ResponseBody 注解修饰在方法上,表示该方法的返回值直接作为 HTTP 响应的内容,而不是通过视图解析器解析为视图。
  • 适用于RESTful服务: 在构建RESTful服务时,@ResponseBody常用于标记处理方法,直接返回数据给客户端,而不是渲染HTML视图。例如,在 RESTful 风格的控制器中,@ResponseBody 常用于返回 JSON 或 XML 格式的数据。如果不使用 @ResponseBody,Spring 将默认通过视图解析器解析返回的字符串为视图路径。

 3、@RestController 

@RestController是Spring框架中的注解,它是@Controller和@ResponseBody注解的组合。这个注解常用于构建RESTful风格的Web服务。

具体而言,@RestController的功能包括:

  • 合并注解: @RestController相当于给所有的方法都添加了@ResponseBody注解,因此它的作用是将方法的返回值直接作为HTTP响应的内容,而不是解析为视图。
  • 方便构建RESTful服务: 由于@RestController注解的存在,开发者无需为每个方法都添加@ResponseBody注解,使得编写RESTful服务时更为简洁。
  • 支持内容协商: @RestController常用于处理RESTful API,支持根据请求头中的Accept属性选择不同的数据格式(如JSON、XML等)进行响应。


所以,提炼总结一下就是: 

@RestController=@Controller+@ResponseBody:

  • @Controller 注解:

    • 用于定义一个类是 Spring MVC 控制器,表明该类将处理 HTTP 请求。
    • 配合方法上的注解(如 @RequestMapping),将特定的请求映射到相应的方法进行处理。
  • @ResponseBody 注解:

    • 告诉 Spring 框架,被标注的方法的返回值直接作为 HTTP 响应的内容。
    • 表示不会返回视图名称,而是直接返回数据,通常是 JSON 或 XML 等。
  • @RestController 注解:

    • 是一个组合注解,相当于同时加上了 @Controller 和 @ResponseBody 的功能
    • 用于标识 RESTful 风格的控制器,其所有方法的返回值都将直接作为 HTTP 响应的内容。

这种组合注解的使用特别适合构建 RESTful API,因为它简化了代码并提供了清晰的方式来处理 HTTP 请求,并将结果直接转换为所需的格式,而无需通过视图解析器。

@RestController 和@Controller的区别

@Controller 和 @RestController 是 Spring Framework 中用于定义控制器的两个注解。它们的关联和区别主要在于返回结果的处理方式。

关联:

  • @RestController 是 @Controller 的特殊形式。实际上,@RestController 是一个组合注解,包含了 @Controller 和 @ResponseBody。
  • @Controller 用于定义经典的 Spring MVC 控制器,而 @ResponseBody 用于指示方法返回的结果直接作为响应体,而不是通过视图解析器进行解析。

区别:

  • @Controller 用于构建传统的 Web 应用,方法通常返回视图名,由视图解析器解析为具体的视图。
  • @RestController 用于构建 RESTful 服务,方法返回的对象会直接转换为 JSON 或 XML 格式,并作为响应的内容返回给客户端。

其实,在很久以前的 Web 开发中,后端的职责不仅仅是处理业务逻辑和数据,还包括生成整个网页的 HTML 结构。这意味着后端除了负责处理请求、与数据库交互等任务之外,还需要负责渲染整个页面,并将生成的 HTML 页面发送给浏览器。

但是,随着前后端分离的兴起,这种传统的后端生成整个页面的方式逐渐被改变。

前后端分离的核心思想是将前端和后端的开发过程解耦,使它们能够独立进行工作。在这个模型下,后端不再负责生成整个页面的 HTML,而是专注于提供数据和业务逻辑。前端则负责根据这些数据以及自身的业务逻辑,通过现代的前端框架动态地生成页面。

具体来说,后端现在只需返回前端所需的数据,通常以 JSON 格式的形式提供。前端收到这些数据后,可以使用 JavaScript 框架(如React、Angular、Vue等)来动态地渲染页面。这样的做法带来了一系列优势,包括更好的开发效率、更好的可维护性、更灵活的前端技术选择以及更好的性能。

总体而言,前后端分离的思想使得整个开发流程更加灵活、高效,并且有助于应对不同平台和设备的需求。

所以现在我们想要返回数据的时候,加上@ResponseBody ,如果想要返回静态视图页面,就不加,使用@Controller。 

刚刚我们点开过这些注释的源码看过,里面有许多元注释和其它注释,我们顺道分析分析:

什么是元注释?

元注释(Meta-Annotation)指的是用于注解其他注解的注解。在Java中,注解也可以被注解,这些注解就是元注解。元注解提供了关于注解本身的信息,可以用来规范和配置注解的行为。

Java中有一些内置的元注解,常见的有四种:

  1. @Retention: 用于指定被注解的注解的生命周期。有三个取值:

    • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译时会被丢弃。
    • RetentionPolicy.CLASS:注解被保留到类加载阶段,但在运行时不会被使用。
    • RetentionPolicy.RUNTIME:注解在运行时保留,因此可以通过反射机制读取。
  2. @Target: 用于指定被注解的注解可以应用的程序元素类型,如类、方法、字段等。

  3. @Documented: 表示被该元注解标注的注解将被javadoc工具提取成文档。

  4. @Inherited: 表示被该元注解标注的注解将具有继承性。如果一个类使用了带有@Inherited注解的注解,那么其子类将自动具有这个注解。

这些元注解为自定义注解提供了一些基本的元信息和行为规范。在定义自己的注解时,可以使用这些元注解来配置注解的行为。

 看看@RestController中涉及到的几个注释:


@Target(ElementType.TYPE) 是元注解 @Target 的一种使用方式。这个元注解表明被它修饰的注解可以用在什么地方。在这里,ElementType.TYPE 表示这个注解可以用在类、接口(包括注解类型)、枚举上。所以,@RestController 这个注解可以用来标记类、接口或枚举,用于指示它们是 Spring MVC 的控制器。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

 
@Retention(RetentionPolicy.RUNTIME) 是元注解 @Retention 的一种使用方式。这个元注解指定了被它修饰的注解的生命周期。在这里,RetentionPolicy.RUNTIME 表示被标注的注解会在运行时保留,这样我们就能通过反射机制在程序运行时获取这些注解的信息。对于 @RestController,这是必要的,因为 Spring MVC 在运行时需要识别和处理这个注解来配置相应的行为。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Documented 是一个元注解,用于表示被它修饰的注解应该被 javadoc 工具记录。在这里,@Documented 用于说明 @RestController 注解的信息应该包含在生成的文档中,以便开发者能够在文档中了解有关这个注解的信息。这有助于文档的完整性和清晰度。

@interface RestController:

  • @interface 是 Java 中定义注解的关键字。这里定义了 RestController 注解,它实际上是一个组合注解,结合了 @Controller 和 @ResponseBody 的功能。
  • @AliasFor(annotation = Controller.class) 表示 value 元素是对 @Controller 注解中 value 元素的别名。这个元素用于指定建议的逻辑组件名称,可以在自动检测组件时转换为 Spring bean。默认值为空字符串。


好,我们继续…… 

返回HTML片段

抓个包确认一下: 

返回JSON
    @ResponseBody
    @RequestMapping("/r4")
    public UserInfo r4(){
        UserInfo userinfo= new UserInfo();
        userinfo.setAge(15);
        userinfo.setId(2);
        userinfo.setName("miaomiao");
        return userinfo;
    }

我们刚刚传递字符串和静态页面的时候它返回的是text.html,现在我们传递一个对象,它返回的就是json。

所以这就意味着Spring会根据我们的结果设置动态的设置响应的类型?

Spring MVC 的工作原理是通过处理器映射(HandlerMapping)和处理器适配器(HandlerAdapter)来找到并调用相应的处理器方法。而消息转换器(MessageConverter)负责将处理器方法的返回值转换成合适的响应体。

在处理器方法中,我们的业务逻辑会返回一个具体的值,例如字符串、视图名称或对象。Spring MVC 会根据返回值的类型,选择合适的消息转换器来处理,转换成最终的响应体。

  1. 字符串和页面: 如果我们的处理器方法返回的是字符串或者视图名称,Spring MVC 会使用视图解析器(ViewResolver)解析出对应的视图,然后渲染成 HTML 页面。同时,Spring 会设置 Content-Type 为 text/html;charset=UTF-8,以确保浏览器正确解析。

  2. 对象(自动生成 JSON): 如果我们的处理器方法返回的是对象,Spring MVC 会使用消息转换器(通常是 Jackson 的 MappingJackson2HttpMessageConverter)将对象转换成 JSON 格式。此时,Content-Type 会被设置为 application/json;charset=UTF-8。这适用于 RESTful 风格的接口,前端可以通过 AJAX 请求获取 JSON 数据,然后在前端进行展示或其他操作。

这种自动根据返回值类型设置响应体的机制简化了开发流程,使得开发者无需关心太多底层细节。当然,如果需要更加精细的控制,我们也可以通过特定的注解或配置进行定制。

当我们在Spring MVC中编写控制器方法时,返回的数据类型会被自动转换为适当的响应格式。这个过程是通过Spring MVC中的消息转换器来完成的,它根据返回值的类型以及请求头中的Accept信息来确定如何转换数据。

让我们来看一些例子:

  1. 返回字符串: 如果我们的控制器方法返回一个普通的字符串,Spring MVC将自动将其转换为文本格式(text/plain;charset=UTF-8)。

  2. 返回字节数组: 如果我们返回一个字节数组,Spring MVC会根据文件类型设置相应的Content-Type,比如图片或其他二进制数据。

  3. 返回资源文件(如CSS或JS): 如果我们的方法返回Resource类型,Spring MVC会根据资源的类型设置适当的Content-Type,适用于静态资源文件。

  4. 返回视图: 如果我们返回一个视图(比如ModelAndView或View对象),Spring MVC将使用相应的视图解析器来渲染并返回HTML。

  5. 返回XML数据: 如果返回的对象带有@XmlRootElement注解,Spring MVC将使用MappingJackson2XmlHttpMessageConverter将其转换为XML格式。

这些只是一些常见的情况,实际上Spring MVC支持多种数据类型和格式的自动转换。这种机制让我们不必手动处理响应的格式,Spring会根据我们的返回值类型和具体情况来选择正确的方式。

 我们可以多做几组测试:

    @ResponseBody
    @RequestMapping("/r5")
    public Map<String,String> r5(){
        HashMap map = new HashMap();
        map.put("k1","v1");
        map.put("k2","v2");
        return map;
    }

    @RequestMapping("/r6")
    public String r6(){
        return "/a.js";
    }

    @RequestMapping("/r7")
    public String r7(){
        return "/b.css";
    }

我们创建一个js文件和一个css文件: 

 

设置状态码/设置响应头/编码

状态码来源于servlet里面的HttpServletResponse :

 

你可能会奇怪为啥都是401了还可以设置成功?——状态码的设置不影响页面的显示。 

状态码的设置主要用于表示请求的处理状态,而不影响页面的显示。HTTP状态码是一种标准的方式来传达服务器对请求的处理状态。当状态码为401时,表示未经授权,要求进行身份验证。在实际应用中,浏览器或客户端会根据不同的状态码采取不同的行为,但这通常不会直接影响到页面的显示。

在Spring MVC中,通过设置HTTP状态码,我们就可以向客户端传达请求的处理状态,但这不会影响返回的内容,特别是当使用@ResponseBody注解时。@ResponseBody注解告诉Spring将方法的返回值直接作为响应的主体内容(数据),而不是将其解释为视图名称。

因此,尽管状态码被设置为401,但实际上返回的是字符串"设置状态码成功",这个字符串会成为HTTP响应的主体内容。这不会妨碍页面的显示,因为状态码和页面内容是两个独立的方面。

设置Header

HTTP响应报头(HTTP response headers)是包含在HTTP响应中的元数据,用于提供关于响应的额外信息,这些信息通常包括描述主体内容的类型(Content-Type)、响应的日期和时间、服务器信息以及其他相关信息。也会向客户端传递一些附加信息,比如服务程序的名称,请求的资源已移动到新地址等 。在Spring MVC中,我们可以通过@RequestMapping注解的属性来设置一些HTTP响应的报头信息。

我们先来看 @RequestMapping 的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}

@RequestMapping 注解是 Spring MVC 中用于映射请求的核心注解,它提供了多个属性,用于指定请求的不同条件。

一些常用属性的解释:

1、value/path:

  • 作用: 指定映射的URL。
  • 示例: @RequestMapping("/example"),该方法处理路径为 "/example" 的请求。

2、method:

  • 作用: 指定请求的 HTTP 方法类型,如 GET、POST 等。
  • 示例: @RequestMapping(value = "/example", method = RequestMethod.GET),该方法处理 HTTP GET 请求。

3、consumes:

  • 作用: 指定处理请求的提交内容类型(Content-Type)。
  • 示例: @RequestMapping(value = "/example", consumes = "application/json"),表示处理请求时需要提交内容类型为 JSON。

4、produces:

  • 作用: 指定返回的内容类型,仅当请求头中的 Accept 类型中包含该指定类型时才返回。
  • 所以Content-type是根据这里设置的。
  • 示例: @RequestMapping(value = "/example", produces = "application/json"),表示仅在请求头中包含 JSON 类型时才返回。

5、params:

  • 作用: 指定请求中必须包含某些参数值时才让该方法处理。
  • 示例: @RequestMapping(value = "/example", params = "param=value"),表示只有请求中包含名为 "param",值为 "value" 的参数时才处理。

6、headers:

  • 作用: 指定请求中必须包含某些指定的 header 值才能让该方法处理请求。
  • 示例: @RequestMapping(value = "/example", headers = "X-Requested-With=XMLHttpRequest"),表示只有请求头中包含指定的 "X-Requested-With" 值为 "XMLHttpRequest" 时才处理。

这些属性可以组合使用,以更精确地映射和处理请求,其实说白了就是一些对请求的限制。通过灵活使用这些属性,我们可以定义处理特定请求条件的方法。

如果你想了解更多,可以参考:Mapping Requests :: Spring Framework

设置Content-Type

我们通过设置 produces属性的值, 设置响应的报头Content-Type :

 

 

我们指定它为JSON:

 ​​​​​​​

 

 

还可以同步设置编码:

    @ResponseBody
//    @RequestMapping("/r9")
    @RequestMapping(value = "/r9", produces = "application/json; charset=utf8")
    public String r9(){
        return "{\"OK\":1}";
    }
自定义Header

设置其他Header的话, 需要使⽤Spring MVC的内置对象HttpServletResponse 提供的方法来进行设置:

    @ResponseBody
    @RequestMapping(value = "/r10")
    public String r10(HttpServletResponse response){
        response.setHeader("myHeader","myHeaderValue");
        return "设置header成功";
    }

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要搭建Spring MVC环境,你需要按照以下步骤进行操作: 1. 首先,确保你已经安装了Java开发工具包(JDK)和一个Java集成开发环境(IDE),比如Eclipse或IntelliJ IDEA。 2. 创建一个新的Maven项目。在IDE中选择创建一个新的Maven项目,并选择适当的参数和配置。 3. 在项目的pom.xml文件中添加Spring MVC的依赖。可以通过以下方式添加Spring MVC的依赖: ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.9</version> </dependency> ``` 4. 创建一个Spring MVC的配置文件。在src/main/resources目录下创建一个名为`springmvc-servlet.xml`的文件,并添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 启用Spring MVC注解驱动 --> <mvc:annotation-driven/> <!-- 定义控制器扫描包 --> <context:component-scan base-package="com.example.controllers"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans> ``` 5. 创建一个控制器类。在com.example.controllers包下创建一个新的类用于处理请求响应,例如: ```java @Controller public class HomeController { @RequestMapping("/") public String home() { return "home"; } } ``` 6. 创建视图文件。在src/main/webapp/WEB-INF/views目录下创建一个名为`home.jsp`的文件,并添加相关内容。 7. 配置Web部署描述符。在src/main/webapp/WEB-INF目录下创建一个名为`web.xml`的文件,并添加以下内容: ```xml <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> ``` 8. 运行应用程序。启动你的应用程序,并在浏览器中访问http://localhost:8080/。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值