概括
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。
原生Servlet API开发代码片段
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userName = request.getParameter("userName");
System.out.println("userName="+userName);
}
基于SpringMVC开发代码片段
@RequestMapping("/user/login")
public String login(@RequestParam("userName") String userName,Sting password){
log.debug("userName="+userName);
//调用业务即可
return "result";
}
主要作用
SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化!
SpringMVC的作用主要覆盖的是表述层,例如:
- 请求映射
- 数据输入
- 视图界面
- 请求分发
- 表单回显
- 会话控制
- 过滤拦截
- 异步交互
- 文件上传
- 文件下载
- 数据校验
- 类型转换
- 等等等**最终总结:
1. 简化前端参数接收( 形参列表 )
2. 简化后端数据响应(返回值)
3. 以及其他......
Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 `Servlet` `DispatcherServlet` 做整体请求处理调度!
除了`DispatcherServlet`SpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。
SpringMVC处理请求流程:
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数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]
简单使用
1.导入依赖
<!--springmvc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.6</version>
</dependency>
<!--web项目需要servlet-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
2.创建springmvc的配置类
//TODO: 导入handlerMapping和handlerAdapter的三种方式
//1.自动导入handlerMapping和handlerAdapter [推荐]
//2.可以不添加,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载
//3.使用@Bean方式配置handlerMapper和handlerAdapter
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "org.example.Controller")
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig {
@Bean
public RequestMappingHandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter heanderAdapter(){
return new RequestMappingHandlerAdapter();
}
}
3.SpringMVC环境搭建
//TODO: SpringMVC提供的接口,是替代web.xml的方案,更方便实现完全注解方式ssm处理!
//TODO: Springmvc框架会自动检查当前类的实现类,会自动加载 getRootConfigClasses / getServletConfigClasses 提供的配置类
//TODO: getServletMappings 返回的地址 设置DispatherServlet对应处理的地址
public class spring extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {//指定service / mapper层的配置类
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() { // 指定springmvc的配置类
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {//设置dispatcherServlet的处理路径!一般情况下为 / 代表处理所有请求!
return new String[]{"/"};
}
}
4.创建controller
@Controller
public class HelloController {
/**
* handler就是controller内部的具体方法
* @RequestMapping("hello/world") 就是用来向handlerMapping中注册的方法注解!
* @ResponseBody 代表向浏览器直接返回数据!
*/
@ResponseBody
@RequestMapping("/hello/world")
public String hello(){
System.out.println("666");
return "hello";
}
}
5.效果
@RequestMappering
@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。
精准路径
@RequestMapping("/hello/world")
模糊路径
@RequestMapping("/hello/*")
//游览器上 可以 hello/a或hello/b 都是可以的,任意输入
//如果 hello/a/b 报错
单层匹配和多层匹配:
/*:只能匹配URL地址中的一层,如果想准确匹配两层,那么就写“/*/*”以此类推。
/**:可以匹配URL地址中的多层。
其中所谓的一层或多层是指一个URL地址字符串被“/”划分出来的各个层次
这个知识点虽然对于@RequestMapping注解来说实用性不大,但是将来配置拦截器的时候也遵循这个规则。
类和方法级别区别
设置到类级别:@RequestMapping 注解可以设置在控制器类上,用于映射
1. 设置到类级别:`@RequestMapping` 注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。
2. 设置到方法级别:`@RequestMapping` 注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 `@RequestMapping` 注解进行更精细的映射。
//1.标记到handler方法
@RequestMapping("/user/login")
@RequestMapping("/user/register")
@RequestMapping("/user/logout")
//2.优化标记类+handler方法
//类上
@RequestMapping("/user")
//handler方法上
@RequestMapping("/login")
@RequestMapping("/register")
@RequestMapping("/logout")
设置请求方式
默认情况下:@RequestMapping("/logout") 任何请求方式都可以访问!
如果需要特定指定
@RequestMapping(value = {"/user/login"} , method = RequestMethod.POST) @RequestMapping(value = {"/user/register"},method = {RequestMethod.POST,RequestMethod.GET})
注意:违背请求方式,会出现405异常!!!
还有
`@RequestMapping` 的 HTTP 方法特定快捷方式变体:
- `@GetMapping`
- `@PostMapping`
- `@PutMapping`
- `@DeleteMapping`
- `@PatchMapping`@RequestMapping将请求方式设置为对应的注解,就于那个注解相同了,上面这几个注解无法加载到类上
接收参数(重点)
在 HTTP 请求中,我们可以选择不同的参数类型,如 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 格式在传递嵌套数据结构时更加清晰易懂。
总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递。
开发中常用的是json
param参数接收
1.直接接值
只要形参数名和类型与传递参数相同,即可自动接收!
@RequestMapping("/hello")
public String hello(String name,int age){
return name+age;
}
效果
2.@RequestParam注解
可以使用 `@RequestParam` 注释将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
`@RequestParam`使用场景:
- 指定绑定的请求参数名
- 要求请求参数必须传递
- 为请求参数提供默认值
如果形参名和@RequestParam中的value属性一样,那么value属性可以省略
网页路径上写的数据名称要和Requestparam中value的保持一致
@RequestMapping("hello")
public Object paramForm(@RequestParam("aaa") String name,
@RequestParam int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
默认情况下,使用此批注的方法参数是必需的,但您可以通过将 @RequestParam 批注的 required 标志设置为 false!
设置默认值defaultValue
@RequestMapping("hello")
public Object paramForm(@RequestParam(value = "name",required = false,defaultValue = "helloworld") String name,
@RequestParam int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
3.特殊场景接值
一名多值
多选框,提交的数据的时候一个key对应多个值,我们可以使用集合进行接收!
@RequestMapping("hello")
//此处必须加@RequestParam注解
public String paramForm(@RequestParam List<String> hbs){
System.out.println(hbs);
return "ok";
}
实体对象接收
1.创建一个实体类
@Data
public class Person {
private String name;
private int age;
}
2.Controller层
@RequestMapping("hello")
public String paramForm(Person person){
System.out.println(person);
return "ok";
}
输入属性名称,可以不把属性全部输上去(这里只输入了姓名,没有输年龄)
路径传参接收
路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 `@PathVariable` 注解来处理路径传递参数。
`@PathVariable` 注解允许将 URL 中的占位符映射到控制器方法中的参数。
例如,如果我们想将 `/user/{id}` 路径下的 `{id}` 映射到控制器方法的一个参数中,则可以使用 `@PathVariable` 注解来实现。
下面是一个使用 `@PathVariable` 注解处理路径传递参数的示例:
/**
* 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识!
* 形参列表取值: @PathVariable Long id 如果形参名 = {动态标识} 自动赋值!
* @PathVariable("动态标识") Long id 如果形参名 != {动态标识} 可以通过指定动态标识赋值!
*
* 访问测试: /param/user/1/root -> id = 1 uname = root
*/
@GetMapping("/user/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id,
@PathVariable("name") String uname) {
System.out.println("id = " + id + ", uname = " + uname);
return "user_detail";
JSON参数接收
前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。其使用方式和示例代码如下:
1前端发送 JSON 数据的示例:(使用postman测试)
{
"name": "张三",
"age": 18,
"gender": "男"
}
2.定义一个用于接收 JSON 数据的 Java 类,例如:
@Data
public class Person {
private String name;
private int age;
private String gender;
}
3.在控制器中,使用 @RequestBody 注解来接收 JSON 数据,并将其转换为 Java 对象,例如
//或者用@PostMapping
@RequestMapping(value = "hello", method = RequestMethod.POST)
@ResponseBody
public String addPerson(@RequestBody Person person) {
// 在这里可以使用 person 对象来操作 JSON 数据中包含的属性
return "success";
}
4.完善配置
415异常
原因:
- 不支持json数据类型处理
- 没有json类型处理的工具(jackson)
解决
springmvc handlerAdpater配置json转化器,配置类需要明确:
@EnableWebMvc //json数据处理
@Configuration
@ComponentScan(basePackages = "org.example.Controller")
并且添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
再次测试
@EnableWebMvc注解说明
handlerAdapter配置了json转化器
不用在手动创建
RequestMappingHandlerMapping,RequestMappingHandlerAdapter这两个对象了@EnableWebMvc //json数据处理,json转化器 @Configuration @ComponentScan(basePackages = "org.example.Controller") //WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现 public class SpringMvcConfig implements WebMvcConfigurer { // @Bean // public RequestMappingHandlerMapping handlerMapping(){ // return new RequestMappingHandlerMapping(); // } // // @Bean // public RequestMappingHandlerAdapter heanderAdapter(){ // // return new RequestMappingHandlerAdapter(); // } }
接收Cookie数据
@CookieValue
//存cookie
@RequestMapping("test")
public String test(@CookieValue("username")String name){
return "cookie"+name;
}
//取cookie并设置值
@RequestMapping("test2")
public String test2(HttpServletResponse response){
response.addCookie(new Cookie("username","zhangsan"));
return "ok";
}
先执行test路径,报400错误
执行test2路径,后在执行,获取到cookie
接收请求头数据
可以使用 @RequestHeader 批注将请求标头绑定到控制器中的方法参数
请考虑以下带有标头的请求
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
下面的示例获取 Accept-Encoding 的值:
@GetMapping("/demo")
public String handle(
@RequestHeader("Accept-Encoding") String encoding) {
//.
System.out.println(encoding);
return "demo"+encoding;
}
原生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
//应用域
@Autowired
private ServletContext servletContext;
//请求域,会话域
public String api(HttpSession session , HttpServletRequest request,
HttpServletResponse response){
String method = request.getMethod();
System.out.println("method = " + method);
return "api";
}
spring提供的
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
public ModelAndView testAttrByModelAndView() {
// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 2.存入模型数据
modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");
// 3.设置视图名称
modelAndView.setViewName("target");
return modelAndView;
}
响应数据
理解handler方法的作用和组成:
/**
* TODO: 一个controller的方法是控制层的一个处理器,我们称为handler
* TODO: handler需要使用@RequestMapping/@GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!
* TODO: handler作用总结:
* 1.接收请求参数(param,json,pathVariable,共享域等)
* 2.调用业务逻辑
* 3.响应前端数据(页面(不讲解模版页面跳转),json,转发和重定向等)
* TODO: handler如何处理呢
* 1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)
* 2.调用业务: { 方法体 可以向后调用业务方法 service.xx() }
* 3.响应数据: return 返回结果,可以快速响应前端数据
*/
@GetMapping
public Object handler(简化请求参数接收){
调用业务方法
返回的结果 (页面跳转,返回数据(json))
return 简化响应前端数据;
}
总结: 请求数据接收,我们都是通过handler的形参列表
前端数据响应,我们都是通过handler的return关键字快速处理!
springmvc简化了参数接收和响应!
页面跳转控制
在 Web 开发中,有两种主要的开发模式:前后端分离和混合开发。
前后端分离模式:[重点]
指将前端的界面和后端的业务逻辑通过接口分离开发的一种方式。开发人员使用不同的技术栈和框架,前端开发人员主要负责页面的呈现和用户交互,后端开发人员主要负责业务逻辑和数据存储。前后端通信通过 API 接口完成,数据格式一般使用 JSON 或 XML。前后端分离模式可以提高开发效率,同时也有助于代码重用和维护。
混合开发模式:
指将前端和后端的代码集成在同一个项目中,共享相同的技术栈和框架。这种模式在小型项目中比较常见,可以减少学习成本和部署难度。但是,在大型项目中,这种模式会导致代码耦合性很高,维护和升级难度较大。
对于混合开发,我们就需要使用动态页面技术,动态展示Java的共享域数据!!
jsp技术了解
JSP(JavaServer Pages)是一种动态网页开发技术,它是由 Sun 公司提出的一种基于 Java 技术的 Web 页面制作技术,可以在 HTML 文件中嵌入 Java 代码,使得生成动态内容的编写更加简单。
JSP 最主要的作用是生成动态页面。它允许将 Java 代码嵌入到 HTML 页面中,以便使用 Java 进行数据库查询、处理表单数据和生成 HTML 等动态内容。另外,JSP 还可以与 Servlet 结合使用,实现更加复杂的 Web 应用程序开发。
JSP 的主要特点包括:
1. 简单:JSP 通过将 Java 代码嵌入到 HTML 页面中,使得生成动态内容的编写更加简单。
2. 高效:JSP 首次运行时会被转换为 Servlet,然后编译为字节码,从而可以启用 Just-in-Time(JIT)编译器,实现更高效的运行。
3. 多样化:JSP 支持多种标准标签库,包括 JSTL(JavaServer Pages 标准标签库)、EL(表达式语言)等,可以帮助开发人员更加方便的处理常见的 Web 开发需求。总之,JSP 是一种简单高效、多样化的动态网页开发技术,它可以方便地生成动态页面和与 Servlet 结合使用,是 Java Web 开发中常用的技术之一。
准备jsp页面和依赖
<!-- jsp需要依赖! jstl-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
jsp页面创建
位置:/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>
controller层创建
@RequestMapping("jsp/index")
public String jsp(HttpServletRequest request){
request.setAttribute("msg","hello jsp");
return "home";
}
视图解析器(重要)
//视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//前面路径,后面文件类型
registry.jsp("/WEB-INF/views/",".jsp");
}
执行
请求转发与响应重定向
在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 `redirect` 或者 `forward` 关键字来实现重定向。
转发
只能转发项目下的资源,项目外的不行
1 方法返回值写成字符串
2不能添加responseBody注解
3返回的字符串前 forward:/转发地址
@RequestMapping("jsp/index")
public String jsp(HttpServletRequest request){
request.setAttribute("msg","hello jsp");
return "home";
}
@RequestMapping("jsp/test")
public String test(){
System.out.println("转发成功");
return "forward:/jsp/index";
}
游览器上输入的是 jsp/test
但显示效果确实 jsp/home
这就是转发
重定向
可以重定向到项目外其他资源 ,也可以是项目下的
1.方法返回值写字符串
2.不能添加不能添加responseBody注解
3.返回字符串前面redirect:/重定向的地址
@RequestMapping("reject")
public String reject(){
System.out.println("重定向成功");
return "redirect:http://www.baidu.com";
}
在游览器上输入http://localhost:8080/reject
结果却重定向到百度一下,你就知道
返回JSON数据
1.还是导入导入jackson依赖,添加添加json数据转化器@EnableWebMvc
@ResponseBody向json响应,返回json,可以放到类上@RequestBody 接收json数据实体类Person之前就创建了
@ResponseBody
@RequestMapping(value = "json",method = RequestMethod.POST)
public Person json(@RequestBody Person person){
System.out.println(person); //打印接收的数据
Person person1 = new Person();
person1.setName("李四");
person1.setAge(10);
person1.setGender("女");
return person1; //发送数据
}
效果
RESTful
RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格,用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序!
学习RESTful设计原则可以帮助我们更好去设计HTTP协议的API接口!!
1. 每一个URI代表1种资源(URI 是名词);
2. 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3. 资源的表现形式是XML或者**JSON**;
4. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
HTTP协议请求方式要求
REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。
操作 请求方式 查询操作 GET 保存操作 POST 删除操作 DELETE 更新操作 PUT
URL路径风格要求
REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的!
使用URL+请求方式确定具体的动作,他也是一种标准的HTTP协议请求!
操作 传统风格 REST 风格 保存 /CRUD/saveEmp URL 地址:/CRUD/emp 请求方式:POST 删除 /CRUD/removeEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:DELETE 更新 /CRUD/updateEmp URL 地址:/CRUD/emp 请求方式:PUT 查询 /CRUD/editEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:GET
根据接口的具体动作,选择具体的HTTP协议请求方式
路径设计从原来携带动标识,改成名词,对应资源的唯一标识即可!
好处
1. 含蓄,安全
使用问号键值对的方式给服务器传递数据太明显,容易被人利用来对系统进行破坏。使用 REST 风格携带数据不再需要明显的暴露数据的名称。
2. 风格统一URL 地址整体格式统一,从前到后始终都使用斜杠划分各个单词,用简单一致的格式表达语义。
3. 无状态在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了系统设计的复杂度。
4. 严谨,规范严格按照 HTTP1.1 协议中定义的请求方式本身的语义进行操作。
1. 简洁,优雅
过去做增删改查操作需要设计4个不同的URL,现在一个就够了。
操作 传统风格 REST 风格 保存 /CRUD/saveEmp URL 地址:/CRUD/emp 请求方式:POST 删除 /CRUD/removeEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:DELETE 更新 /CRUD/updateEmp URL 地址:/CRUD/emp 请求方式:PUT 查询 /CRUD/editEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:GET
6 丰富的语义
通过 URL 地址就可以知道资源之间的关系。它能够把一句话中的很多单词用斜杠连起来,反过来说就是可以在 URL 地址中用一句话来充分表达语义。
代码演示
需求分析
- 数据结构: User {id 唯一标识,name 用户名,age 用户年龄}
- 功能分析
- 用户数据分页展示功能(条件:page 页数 默认1,size 每页数量 默认 10)
- 保存用户功能
- 根据用户id查询用户详情功能
- 根据用户id更新用户数据功能
- 根据用户id删除用户数据功能
- 多条件模糊查询用户功能(条件:keyword 模糊关键字,page 页数 默认1,size 每页数量 默认 10)
接口设计
功能 接口和请求方式 请求参数 返回值 分页查询 GET /user page=1&size=10 { 响应数据 } 用户添加 POST /user { user 数据 } {响应数据} 用户详情 GET /user/1 路径参数 {响应数据} 用户更新 PUT /user { user 更新数据} {响应数据} 用户删除 DELETE /user/1 路径参数 {响应数据} 条件模糊 GET /user/search page=1&size=10&keywork=关键字 {响应数据} 问题
为什么查询用户详情,就使用路径传递参数,多条件模糊查询,就使用请求参数传递?
误区:restful风格下,不是所有请求参数都是路径传递!可以使用其他方式传递!
在 RESTful API 的设计中,路径和请求参数和请求体都是用来向服务器传递信息的方式。
- 对于查询用户详情,使用路径传递参数是因为这是一个单一资源的查询,即查询一条用户记录。使用路径参数可以明确指定所请求的资源,便于服务器定位并返回对应的资源,也符合 RESTful 风格的要求。
- 而对于多条件模糊查询,使用请求参数传递参数是因为这是一个资源集合的查询,即查询多条用户记录。使用请求参数可以通过组合不同参数来限制查询结果,路径参数的组合和排列可能会很多,不如使用请求参数更加灵活和简洁。此外,还有一些通用的原则可以遵循:
- 路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。
- 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
- 对于敏感信息,最好使用 POST 和请求体来传递参数。
总结
代码
新建实体类(略)
controller层代码(相当于之前的一些整合)
package org.example.Controller;
import org.example.pojo.User;
import org.springframework.web.bind.annotation.*;
@RequestMapping("user")
@RestController
public class UserController {
//param传参
@GetMapping
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size){
System.out.println("page = " + page + ", size = " + size);
System.out.println("分页查询业务!");
return "{'status':'ok'}";
}
//json传参
@PostMapping
public Object saveUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户保存业务!");
return "{'status':'ok'}";
}
//路径传参
@PostMapping("/{id}")
public Object detailUser(@PathVariable Integer id){
System.out.println("id = " + id);
System.out.println("用户详情业务!");
return "{'status':'ok'}";
}
//json传参
@PutMapping
public Object updateUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户更新业务!");
return "{'status':'ok'}";
}
//param传参
@GetMapping("search")
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size,
@RequestParam(name = "keyword",required= false)String keyword){
System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
System.out.println("条件分页查询业务!");
return "{'status':'ok'}";
}
}
其他扩展
全局异常处理
对于异常的处理,一般分为两种方式:
- 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
- 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 `@Throws` 或 `@ExceptionHandler`),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。站在宏观角度来看待声明式事务处理:
整个项目从架构这个层面设计的异常处理的统一机制和规范。
一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。
使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!
代码练习
1.声明全局异常类,捕捉全部异常
//@ControllerAdvice //可以返回逻辑视图,转发,重定向
@RestControllerAdvice //@ControllerAdvice+@ResponseBody=@RestControllerAdvice 直接返回json字符串
public class ExceptionAdvice {
/*只要发生异常,就会走进这个全局异常类
在全局异常类中找相对赢得异常处理方法
*
* */
//NullPointerException异常专属异常
@ExceptionHandler(NullPointerException.class)
public Object nullPointerExceptionHandler(NullPointerException e){
String message = e.getMessage();
System.out.println("message"+message);
return message;
}
//保底异常处理
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
String message = e.getMessage();
System.out.println("message"+message);
return message;
}
2.编写controller类
@Controller
public class ExceptionController {
@RequestMapping("exception")
public String exception() {
int i = 1 / 0; //算出异常
return "success";
}
}
结果
拦截器
拦截器和过滤器解决问题
- 生活中
为了提高乘车效率,在乘客进入站台前统一检票
程序中
在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测
拦截器 Springmvc VS 过滤器 javaWeb:
- 相似点
- 拦截:必须先把请求拦住,才能执行后续操作
- 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
- 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
- 不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
代码练习
1.创建拦截器
public class Myinterceptor implements HandlerInterceptor {
/*
request请求对象
response响应对象
handler我们要调用的方法对象
true放行 false拦截
ModelAndView 返回的视图和共享域对象
Exception 如果handler报错的异常
*
* */
//执行handler之前调用的拦截方法
//编码格式,登入保护,权限处理等
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
System.out.println("拦截器执行了");
return true;
}
// 在目标 handler 方法之后,handler报错不执行!
//handler执行后触发,不拦截的
//对结果处理,敏感词汇检查等
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
System.out.println("handler执行后在执行的");
}
//整体处理完毕后执行
//
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
System.out.println("最后执行的");
}
}
在配置类中设置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Myinterceptor()); //配置方案一:拦截全部请求 //里面是自己设置的拦截器
}
随便找一个controller运行一下
没异常的
有异常的
拦截器拦截
默认拦截全部
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Myinterceptor()); //配置方案一:拦截全部请求 //里面是自己设置的拦截器
}
精准拦截配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//addPathPatterns("/common/request/one") 添加拦截路径
//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
}
排除配置
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//addPathPatterns("/common/request/one") 添加拦截路径
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
//排除匹配,排除应该在匹配的范围内排除
//addPathPatterns("/common/request/one") 添加拦截路径
//excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内
registry.addInterceptor(new Process01Interceptor())
.addPathPatterns("/common/request/one","/common/request/tow")
.excludePathPatterns("/common/request/tow");
}
1. 多个拦截器执行顺序
1. preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
2. postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
3. afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
参数效验
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
校验概述
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
注解 规则 @Null 标注值必须为 null @NotNull 标注值不可为 null @AssertTrue 标注值必须为 true @AssertFalse 标注值必须为 false @Min(value) 标注值必须大于或等于 value @Max(value) 标注值必须小于或等于 value @DecimalMin(value) 标注值必须大于或等于 value @DecimalMax(value) 标注值必须小于或等于 value @Size(max,min) 标注值大小必须在 max 和 min 限定的范围内 @Digits(integer,fratction) 标注值值必须是一个数字,且必须在可接受的范围内 @Past 标注值只能用于日期型,且必须是过去的日期 @Future 标注值只能用于日期型,且必须是将来的日期 @Pattern(value) 标注值必须符合指定的正则表达式
标注值必须是格式正确的 Email 地址 @Length 标注值字符串大小必须在指定的范围内 @NotEmpty 标注值字符串不能是空字符串 @Range 标注值必须在指定的范围内
Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 @EnableWebMvc 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。
配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。
代码练习
1.导入依赖
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
编写实体类
@Data
public class User {
//age 10 <= age
@Min(10)
private int age;
//name 3 <= name.length <= 10
@Length(min = 3,max = 10)
private String name;
//email 邮箱格式
@Email
private String email;
//生日 2002-01-01
// @Past
// private String birthday;
}
Controller层代码
关键注解
@Validated
param传参方式
@ResponseBody
@RequestMapping("xy")
public User xy(@Validated User user){
System.out.println("user = " + user);
return user;
}
效果
不符合条件时
用json方式时(日期时间加入了)
@ResponseBody
@RequestMapping("xy")
public User xy( @RequestBody @Validated User user){
System.out.println("user = " + user);
return user;
}
问题:如果不符合效验,它会直接向前端抛出异常!
解决方式:自定义返回结果!接收错误绑定信息!约定:参数错误{code 400} -》前端
BindingResult result 必须紧紧挨着效验对象
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
@PostMapping("save")
@ResponseBody
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result){
//判断是否有信息绑定错误! 有可以自行处理!
if (result.hasErrors()){
Map map = new HashMap();
map.put("code",400);
map.put("msg","参数效验出现异常");
return map;
}
//没有,正常处理业务即可
System.out.println("正常");
return user;
}
效果(这里只展示效验失败的)
综合
核心点 | 掌握目标 |
springmvc框架 | 主要作用、核心组件、调用流程 |
简化参数接收 | 路径设计、参数接收、请求头接收、cookie接收 |
简化数据响应 | 模板页面、转发和重定向、JSON数据、静态资源 |
restful风格设计 | 主要作用、具体规范、请求方式和请求参数选择 |
功能扩展 | 全局异常处理、拦截器、参数校验注解 |