当前位置: 首页 > news >正文

SpringMVC架构解析:从入门到精通(1)

1. MVC架构

MVC是一种软件架构模式,它将应用分为三块:

  • M:Model(模型)

  • V:View(视图)

  • C:Controller(控制器)

应用为什么要被分为三块,优点是什么?

① 低耦合,扩展能力增强;  ②代码复用性增强;  ③代码可维护性增强;

④高内聚,让程序员更加专注业务的开发

MVC将应用分为三块,每一块各司其职,都有自己专注的事情要做,它们属于分工协作,互相配合:

  • Model:负责业务处理及数据的收集。

  • View:负责数据的展示

  • Controller:负责调度。它是一个调度中心,它来决定什么时候调用Model来处理业务,什么时候调用View视图来展示数据。

MVC架构模式的描述:前端浏览器发送请求给web服务器,web服务器中的Controller接收到用户的请求,Controller负责将前端提交的数据进行封装,然后Controller调用Model来处理业务,当Model处理完业务后会返回处理之后的数据给Controller,Controller再调用View来完成数据的展示,最终将结果响应给浏览器,浏览器进行渲染展示页面。

2. SpringMVC

SpringMVC是一个实现了MVC架构模式的Web框架,底层基于Servlet实现。SpringMVC已经将MVC架构模式实现了,因此只要基于SpringMVC框架写代码,编写的程序就是符合MVC架构模式的。

SpringMVC框架做了什么,与纯粹的Servlet开发有什么区别?

  1. 入口控制:SpringMVC框架通过DispatcherServlet作为入口控制器,负责接收请求和分发请求。而在Servlet开发中,需要自己编写Servlet程序,并在web.xml中进行配置,才能接受和处理请求。

  2. 在SpringMVC中,表单提交时可以自动将表单数据绑定到相应的JavaBean对象中,只需要在控制器方法的参数列表中声明该JavaBean对象即可,无需手动获取和赋值表单数据。而在纯粹的Servlet开发中,这些都是需要自己手动完成的。

  3. IoC容器:SpringMVC框架通过IoC容器管理对象,只需要在配置文件中进行相应的配置即可获取实例对象,而在Servlet开发中需要手动创建对象实例。

  4. 统一处理请求:SpringMVC框架提供了拦截器、异常处理器等统一处理请求的机制,并且可以灵活地配置这些处理器。而在Servlet开发中,需要自行编写过滤器、异常处理器等,增加了代码的复杂度和开发难度。

  5. 视图解析:SpringMVC框架提供了多种视图模板,如JSP、Freemarker、Velocity等,并且支持国际化、主题等特性。而在Servlet开发中需要手动处理视图层,增加了代码的复杂度。

3. SpringMVC程序开发

3.1 配置web.xml文件

<!--前端控制器-->
<servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--通过Servlet的初始化参数来指定Spring MVC配置文件的名字和位置--><init-param><param-name>contextConfigLocation</param-name><!--指定了Spring MVC配置文件的名字是: springmvc.xml--><!--指定了Spring MVC配置文件存放的路径是:类的根路径--><param-value>classpath:springmvc.xml</param-value></init-param><!--在web服务器启动的时候,就初始化DispatcherServlet--><!--这是优化方式,可以提高用户第一次发送请求的体验。第一次请求的效率较高。--><load-on-startup>0</load-on-startup>
</servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!--所有前端发送的请求都会先经过 DispatcherServlet 处理,除了jsp--><url-pattern>/</url-pattern>
</servlet-mapping>

3.1.1 DispatchServlet类

DispatcherServlet是Web应用程序的主要入口点之一,它的职责包括:

  1. 接收客户端的HTTP请求:DispatcherServlet监听来自Web浏览器的HTTP请求,然后根据请求的URL将请求数据解析为Request对象。

  2. 处理请求的URL:DispatcherServlet将请求的URL(Uniform Resource Locator)与处理程序进行匹配,确定要调用哪个控制器(Controller)来处理此请求。

  3. 调用相应的控制器:DispatcherServlet将请求发送给找到的控制器处理,控制器将执行业务逻辑,然后返回一个模型对象(Model)。

  4. 渲染视图:DispatcherServlet将调用视图引擎,将模型对象呈现为用户可以查看的HTML页面。

  5. 返回响应给客户端:DispatcherServlet将为用户生成的响应发送回浏览器,响应可以包括表单、JSON、XML、HTML以及其它类型的数据。

3.2 配置springmvc-servlet.xml文件

SpringMVC框架有它自己的配置文件,该配置文件的名字默认为:<servlet-name>-servlet.xml,默认存放的位置是WEB-INF 目录下:

<!--组件扫描-->
<context:component-scan base-package="com.powernode.springmvc.controller"/><!--配置视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver"><!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集--><property name="characterEncoding" value="UTF-8"/><!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高--><property name="order" value="1"/><!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板--><property name="templateEngine"><bean class="org.thymeleaf.spring6.SpringTemplateEngine"><!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析--><property name="templateResolver"><bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver"><!--设置模板文件的位置(前缀)--><property name="prefix" value="/WEB-INF/templates/"/><!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html--><!--将来要在 xxxx.thymeleaf 文件中编写符合 Thymeleaf 语法格式的字符串:Thymeleaf 模板字符串。--><property name="suffix" value=".thymeleaf"/><!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等--><property name="templateMode" value="HTML"/><!--用于模板文件在读取和解析过程中采用的编码字符集--><property name="characterEncoding" value="UTF-8"/></bean></property></bean></property>
</bean>

视图解析器(View Resolver)的作用主要是将Controller方法返回的逻辑视图名称解析成实际的视图对象。视图解析器将解析出的视图对象返回给DispatcherServlet,并最终由DispatcherServlet将该视图对象转化为响应结果,呈现给用户。

如果采用了其它视图,请配置对应的视图解析器,例如:

  • JSP的视图解析器:InternalResourceViewResolver

  • FreeMarker视图解析器:FreeMarkerViewResolver

  • Velocity视图解析器:VelocityViewResolver

3.3 视图

在WEB-INF目录下新建templates目录,在templates目录中新建html文件。

<!DOCTYPE html>
<!--指定 th 命名空间,让 Thymeleaf 标准表达式可以被解析和执行-->
<!--th不是固定的,可以指定其它的命名空间,只不过大部分情况下用th-->
<!--表示程序中出现的 th 开头的后面代码都是 Thymeleaf语法,需要被 Thymeleaf识别-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>first springmvc</title>
</head>
<body>
<h1>我的第一个Spring MVC程序</h1>
<!-- th: 表示后面的代码可以编写Thymeleaf语法,可以被Thymeleaf语法解析 -->
<!-- Thymeleaf检测到以 / 开始,表示绝对路径,自动会将视图解析器中webapp的上下文路径加上去 -->
<!-- 最终的效果是:href="/other" -->
<a th:href="@{/other}">other请求</a>
</body>
</html>

3.4 Controller类

@RequestMapping("/")
public String toIndex(){// 返回逻辑视图名称return "index";
}// 请求映射
// 这个方法是一个实例方法
// 这个方法目前返回一个String字符串
// 返回值代表的是一个逻辑视图名称
//@RequestMapping(value="/test")
@RequestMapping("/test")
public String hehe(){// 返回一个逻辑视图名称return "first";
}

3.5 执行流程总结

  1. 浏览器发送请求:http://localhost:8080/haha

  2. SpringMVC的前端控制器DispatcherServlet接收到请求

  3. DispatcherServlet根据请求路径 /haha 映射到 FirstController#名字随意(),调用该方法

  4. FirstController#名字随意() 处理请求

  5. FirstController#名字随意() 返回逻辑视图名称 first 给视图解析器

  6. 视图解析器找到 /WEB-INF/templates/first.html 文件,并进行解析,生成视图解析对象返回给前端控制器DispatcherServlet

  7. 前端控制器DispatcherServlet响应结果到浏览器。

3.6 IndexController

@Controller
public class IndexController {@RequestMapping("/")public String toIndex(){return "index";}
}

表示请求路径如果是:http://localhost:8080/ ,则进入 /WEB-INF/templates/index.html 页面。

4. RequestMapping

@RequestMapping 注解是 Spring MVC 框架中的一个控制器映射注解,用于将请求映射到相应的处理方法上。具体来说,它可以将指定 URL 的请求绑定到一个特定的方法或类上,从而实现对请求的处理和响应。

在同一个webapp中,RequestMapping必须具有唯一性。在类上和方法上都使用RequestMapping注解来进行路径的映射。假设在类上映射的路径是"/a",在方法上映射的路径是"/b",那么整体表示映射的路径就是:"/a/b"。

@Controller
@RequestMapping("/product")
public class ProductController {// @RequestMapping("/product/detail")@RequestMapping("/detail")public String toDetail(){return "product_detail";}
}

首页中添加两个超链接:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>index page</title>
</head>
<body>
<h1>index page</h1>
<a th:href="@{/user/detail}">用户详情</a><br>
<a th:href="@{/product/detail}">商品详情</a><br>
</body>
</html>

4.1 Value属性

value属性是该注解最核心的属性,value属性填写的是请求路径,也就是说通过该请求路径与对应的控制器的方法绑定在一起。value属性是一个字符串数组:

@Controller
public class IndexController {@RequestMapping("/")public String toDetail(){return "index";}// 对于注解来说:如果是一个数组,数组中只有一个元素,大括号是可以省略的//@RequestMapping(value = "/testVal1")// 对于注解来说,如果只使用了一个value属性,那么value也是可以省略的。// @RequestMapping("/testVal2")
//	@RequestMapping(value = {"/testValue1", "/testValue2"})@RequestMapping(path = {"/testValue1", "/testValue2"})public String testValue(){return "testValue";}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>index page</title>
</head>
<body>
<h1>index page</h1><!--测试RequestMapping的value属性-->
<a th:href="@{/testValue1}">testValue1</a><br>
<a th:href="@{/testValue2}">testValue2</a><br></body>
</html>

4.1.1 Ant风格的value

value是可以用来匹配路径的,路径支持模糊匹配,把这种模糊匹配称之为Ant风格。

关于路径中的通配符包括:

  • ?,代表任意一个字符

  • *,代表0到N个任意字符

  • **,代表0到N个任意字符,并且路径中可以出现路径分隔符 /

注意:** 通配符在使用时,左右不能出现字符,只能是 /;

在Spring6当中,** 通配符只能作为路径的末尾出现。

//@RequestMapping(value = "/x?z/testAntValue")	 // x和z中间任意一个字符,除了/和?和空着
//@RequestMapping(value = "/x*z/testAntValue") 	 // x和z中间任意一个字符,除了/和?
//@RequestMapping(value = "/x**z/testAntValue")  // x和z中间任意一个字符,除了?,和上面这个语句没有区别
//@RequestMapping(value = "/**/testAntValue") // 报错了,Spring6报错。Spring5不会报错。
@RequestMapping(value = "/testAntValue/**")
public String testRequestMappingAntValue(){return "ok";
}

4.1.2 value中的占位符

普通的请求路径:http://localhost:8080/springmvc/login?username=admin&password=123&age=20

RESTful风格的请求路径:http://localhost:8080/springmvc/login/admin/123/20

如果使用RESTful风格的请求路径,在控制器中应该如何获取请求中的数据呢?可以在value属性中使用占位符,例如:/login/{id}/{username}/{password}

@RequestMapping(value = "/login/{a}/{b}")
public String testRESTFulURL(@PathVariable(value = "a")String username,@PathVariable("b")String password){System.out.println("用户名:" + username + ",密码:" + password);return "ok";
}
<!--测试RequestMapping注解的value属性支持占位符-->
<a th:href="@{/login/zhangsan/123456}">测试value属性使用占位符</a>

4.2 method属性

在Servlet当中,如果后端要求前端必须发送一个post请求,后端可以通过重写doPost方法来实现。后端要求前端必须发送一个get请求,后端可以通过重写doGet方法来实现。当重写的方法是doPost时,前端就必须发送post请求,当重写doGet方法时,前端就必须发送get请求。如果前端发送请求的方式和后端的处理方式不一致时,会出现405错误。

HTTP状态码405,这种机制的作用是:限制客户端的请求方式,以保证服务器中数据的安全。

假设后端程序要处理的请求是一个登录请求,为了保证登录时的用户名和密码不被显示到浏览器的地址栏上,后端程序有义务要求前端必须发送一个post请求,如果前端发送get请求,则应该拒绝。

// 当前端发送的请求路径是 /user/login ,并且发送的请求方式是以POST方式请求的。则可以正常映射。
// 当前端发送的请求路径不是 /user/login,请求方式是POST,不会映射到这个方法上。
// 当前端发送的请求路径是 /user/login,请求方式不是POST,也不会映射到这个方法上。
//@RequestMapping(value = "/user/login", method = RequestMethod.POST)
@PostMapping("/user/login")
public String userLogin(){System.out.println("处理登录的业务......");return "ok";
}

在SpringMVC中不仅提供了 PostMaping注解,像这样的注解还有四个,包括:

  • GetMapping:要求前端必须发送get请求

  • PutMapping:要求前端必须发送put请求

  • DeleteMapping:要求前端必须发送delete请求

  • PatchMapping:要求前端必须发送patch请求

<!-- 前端  发送POST请求-->
<form th:action="@{/user/login}" method="post">用户名:<input type="text" name="username"/><br>密码:<input type="password" name="password"/><br><input type="submit" value="登录">
</form>

对于RequestMapping注解来说,多一个属性,就相当于多了一个映射的条件,如果value和method属性都有,则表示只有前端发送的请求路径 + 请求方式都满足时才能与控制器上的方法建立映射关系,只要有一个不满足,则无法建立映射关系。例如:@RequestMapping(value="/login", method = RequestMethod.POST) 表示当前端发送的请求路径是 /login,并且发送请求的方式是POST的时候才会建立映射关系。如果前端发送的是get请求,或者前端发送的请求路径不是 /login,则都是无法建立映射的。

4.3 params属性

对于RequestMapping注解来说:

  • value属性是一个数组,只要满足数组中的任意一个路径,就能映射成功

  • method属性也是一个数组,只要满足数组中任意一个请求方式,就能映射成功。

  • params属性也是一个数组,不过要求请求参数必须和params数组中要求的所有参数完全一致后,才能映射成功。

4.3.1 params属性的四种用法

@RequestMapping(value="/login", params={"username", "password"}) 表示:请求参数中必须包含 username 和 password,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", params={"!username", "password"}) 表示:请求参数中不能包含username参数,但必须包含password参数,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", params={"username=admin", "password"}) 表示:请求参数中必须包含username参数,并且参数的值必须是admin,另外也必须包含password参数,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", params={"username!=admin", "password"}) 表示:请求参数中必须包含username参数,但参数的值不能是admin,另外也必须包含password参数,才能与当前标注的方法进行映射。

注意:如果前端提交的参数,和后端要求的请求参数不一致,则出现400错误!!!HTTP状态码400的原因:请求参数格式不正确而导致的。

<!--测试RequestMapping注解的params属性-->
<!--发送请求 /testParams,并且携带参数 username password-->
<a th:href="@{/testParams?username=admin&password=1234}">测试RequestMapping注解的params属性</a><br>
<a th:href="@{/testParams(username='admin', password='1234')}">测试RequestMapping注解的params属性</a><br>
// 当请求路径是 /testParams,并且提交的参数包括 username 和 password时,才能映射成功。
@RequestMapping(value = "/testParams", params = {"username", "password"})
//@RequestMapping(value = "/testParams", params = {"username=zhangsan", "password"})
//@RequestMapping(value = "/testParams", params = {"username!=zhangsan", "password"})
//@RequestMapping(value = "/testParams", params = {"!username", "password"})
public String testParams(){return "ok";
}

4.4 headers属性

@RequestMapping(value="/login", headers={"Referer", "Host"}) 表示:请求头信息中必须包含Referer和Host,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", headers={"Referer", "!Host"}) 表示:请求头信息中必须包含Referer,但不包含Host,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", headers={"Referer=http://localhost:8080/springmvc/", "Host"}) 表示:请求头信息中必须包含Referer和Host,并且Referer的值必须是http://localhost:8080/springmvc/,才能与当前标注的方法进行映射。

@RequestMapping(value="/login", headers={"Referer!=http://localhost:8080/springmvc/", "Host"}) 表示:请求头信息中必须包含Referer和Host,并且Referer的值不是http://localhost:8080/springmvc/,才能与当前标注的方法进行映射。

注意:如果前端提交的请求头信息,和后端要求的请求头信息不一致,则出现404错误!!!

<!--测试RequestMapping注解的headers属性-->
<a th:href="@{/testHeaders}">测试RequestMapping注解的headers属性</a>
// 只有当请求路径是 /testHeaders,并且请求头当中有 Referer 和 Host,这样才能映射成功。
//@RequestMapping(value = "/testHeaders", headers = {"Referer", "Host"})
//@RequestMapping(value = "/testHeaders", headers = {"!Referer", "Host"})
@RequestMapping(value = "/testHeaders", headers = {"Referer=http://localhost:8888/", "Host"})
//@RequestMapping(value = "/testHeaders", headers = {"Referer!=http://localhost:8080/", "Host"})
public String testHeaders(){return "ok";
}

4.5 跳转的ok页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>ok</title>
</head>
<body>
<h1>Test OK!</h1>
</body>
</html>

5. 获取请求数据

5.1 注册表单

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>用户注册</title>
</head><body>
<!--注册页面-->
<form th:action="@{/user/reg}" method="post">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br>性别:男<input type="radio" name="sex" value="1">女<input type="radio" name="sex" value="0"><br>兴趣:抽烟<input type="checkbox" name="interest" value="smoke">喝酒<input type="checkbox" name="interest" value="drink">烫头<input type="checkbox" name="interest" value="tt"><br>简介:<textarea cols="60" rows="10" name="intro"></textarea><br>年龄:<input type="text" name="age"><br><input type="submit" value="注册">
</form></body>
</html>

5.1.1 成功页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ok</title>
</head>
<body>
<h1>Test OK!</h1>
</body>
</html>

5.2 原生API获取数据

原生的Servlet API指的是:HttpServletRequest在SpringMVC当中,一个Controller类中的方法参数上如果有HttpServletRequest,SpringMVC会自动将  当前请求对象 传递给这个参数,因此可以通过这个参数来获取请求提交的数据。

//@RequestMapping(value = "/user/reg", method = RequestMethod.POST)
@PostMapping("/user/reg")
// SpringMVC检测到request对象会自动将Tomcat服务器创建的request对象传进来
public String register(HttpServletRequest request, HttpServletResponse response, HttpSession session){// HttpServletRequest、HttpServletResponse、HttpSession都属于原生Servlet API。// HttpServletRequest(请求对象)代表 客户端(如浏览器)向服务器发送的一次 HTTP 请求。// HttpServletResponse(响应对象)代表 服务器对客户端请求的响应。// HttpSession(会话对象)代表一次用户会话(Session),即用户从打开浏览器到关闭浏览器期间与服务器的交互过程。System.out.println(request);System.out.println(response);System.out.println(session);// 获取请求提交的数据String username = request.getParameter("username");String password = request.getParameter("password");String sex = request.getParameter("sex");String[] interest = request.getParameterValues("interest"); // interest=smoke&interest=drink&interest=ttString intro = request.getParameter("intro");System.out.println(username);System.out.println(password);System.out.println(sex);System.out.println(Arrays.toString(interest));System.out.println(intro);return "ok";
}

5.3 使用RequestParam注解标注

对于@RequestParam注解来说,属性有value和name,这两个属性的作用相同,都是用来指定提交数据的name。

value都是注册表单中各个标签的id.

发送请求时提交的数据是:name1=value1&name2=value2,则这个注解应该这样写:@RequestParam(value="name1")、@RequestParam(value="name2")

@PostMapping("/user/reg")
public String register(/* @RequestParam(value = "username") // value可以,name也可以*//* @RequestParam(name = "username") // username 不能随便写,最好是复制过来的。*/@RequestParam(name = "username") /* 如果前端没有提供 uname 参数,则报错:400*/String a, /* 变量名随意*/@RequestParam(value = "password")String password,@RequestParam("sex")Integer sex, /* SpringMVC也可以自动帮助我们做类型转换,从前端提交的是'0'/'1'字符串,可以自动转换成Integer类型。*/@RequestParam("interest")String[] interest,@RequestParam("intro")String intro,@RequestParam(value = "age", required = false, defaultValue = "20")Integer age){System.out.println(a);System.out.println(password);System.out.println(sex);System.out.println(Arrays.toString(interest));System.out.println(intro);System.out.println("年龄:" + age);return "ok";
}

5.3.1 RequestParam注解的required属性

required属性用来设置该方法参数是否为必须的。默认情况下,这个参数为 true,表示方法参数是必需的。如果请求中缺少对应的参数,则会抛出异常。可以将其设置为false,false表示不是必须的,如果请求中缺少对应的参数,则方法的参数为null。

1. required属性可以设置为false,这样这个参数就不是必须的了。如果前端没有提供,则不会报400错误。但是由于前端没有提供这个数据,因此程序中的变量值为null.

2. defaultValue属性:通过defaultValue属性可以给参数赋默认值。如果前端没有提供这样的参数,参数的默认值就起作用了。

5.4 依靠控制器方法上的形参名接收

如果 请求参数名(表单中的标签名)控制器方法上的形参名 保持一致,那么 @RequestParam注解可以省略。

// 依靠控制器方法上的形参名来接收
@PostMapping("/user/reg")
public String register(String uname, String password, Integer sex, String[] interest, String intro, Integer age){System.out.println(uname); // null(因为前端没有提交这个数据),前端的是usernameSystem.out.println(password);System.out.println(sex);System.out.println(Arrays.toString(interest));System.out.println(intro);System.out.println(age);return "ok";
}

但是,如果采用的是Spring6+版本,需要在pom.xml文件中指定编译参数'-parameter'

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.12.1</version><configuration><source>21</source><target>21</target><compilerArgs><arg>-parameters</arg></compilerArgs></configuration></plugin></plugins>
</build>

5.5 使用POJO类接收请求参数

// 通过POJO类接收请求参数
@PostMapping("/user/reg")
public String register(User user){System.out.println(user);return "ok";
}

底层实现原理:反射机制。

使用这种方式的前提是:POJO类的属性名必须和请求参数的参数名保持一致。

实现原理是什么?
    假设提交了一个请求,参数名是 username,那么要求POJO类当中必须有一个属性名也叫做:username。

Class clazz = Class.forName("com.powernode.springmvc.pojo.User");
User user = (User)clazz.newInstance();
String fieldName = "username";
String setMethodName = "setUsername";
Method setMethod = clazz.getDeclaredMethod(setMethodName, ....);
setMethod.invoke(user, "zhaoliu");

重点:底层通过反射机制调用set方法给属性赋值的。所以set方法的方法名非常重要。
如果前端提交了参数是: username=zhangsan
那么必须保证POJO类当中有一个方法名叫做:setUsername

如果前端提交了参数是: email=zhangsan@powernode.com
那么必须保证POJO类当中有一个方法名叫做:setEmail

如果没有对应的set方法,将无法给对应的属性赋值。所以set方法正确,属性名其实可以随意定义

5.6 RequestHeader注解

该注解的作用是:将请求头信息映射到方法的形参上。和RequestParam注解功能相似,RequestParam注解的作用:将请求参数映射到方法的形参上。当然,对于RequestHeader注解来说,也有三个属性:value、required、defaultValue,和RequestParam一样。

// 获取请求头信息
// 将`请求头信息`映射到`方法的形参上`。
//@PostMapping("/user/reg")
@RequestMapping("/user/reg")
public String register(User user,@RequestHeader(value = "Referer", required = false, defaultValue = "")String referer,@RequestHeader(value = "Host", required = false, defaultValue = "")String host,@CookieValue(value = "id", required = false, defaultValue = "")String id){System.out.println(user);System.out.println(referer);System.out.println(host);System.out.println("客户端提交过来的cookie,它的值是:" + id);return "ok";
}

5.7 CookieValue注解

该注解的作用:将请求提交的Cookie数据映射到方法形参上同样是有三个属性:value、required、defaultValue。

<!--发送Cookie给服务器-->
<script type="text/javascript">function sendCookie(){/*id=123456789:创建名为 id的 Cookie,值为 123456789expires=...:设置过期时间为 2025 年 12 月 18 日 UTC 时间 12:00path=/:使 Cookie 在整个网站根路径下有效*/document.cookie = "id=123456789; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";/** 将浏览器重定向到路径 /springmvc/user/reg* 用户访问新页面,常用于身份验证后跳转*/document.location = "/user/reg";}
</script>
<button onclick="sendCookie()">向服务器端发送Cookie</button>

5.8 请求乱码问题

5.8.1 get请求

在SpringMVC中如何解决请求体的中文乱码问题呢?当然,还是使用

request.setCharacterEncoding("UTF-8")使用它有一个前提条件,要想解决请求体乱码问题,以上代码必须在 request.getParameter("username")执行之前执行才有效。

@RequestMapping("/user/reg")
public String register(User user, HttpServletRequest request) throws UnsupportedEncodingException {// 设置请求体的字符编码方式,解决POST请求乱码问题//request.setCharacterEncoding("UTF-8");System.out.println(user);return "ok";
}

也就是说以上代码如果放在Controller的相关方法中执行是无效的,因为Controller的方法在执行之前 DispatcherServlet已经调用了 request.getParameter("username")方法。因此在Controller方法中使用request.setCharacterEncoding("UTF-8");无效

5.8.2 post请求

如果遇到Tomcat9- 版本,那么POST请求乱码应该怎么解决呢?

在 request.getParameter() 方法执行之前,执行: request.setCharacterEncoding("UTF-8"); 


第一种方案:自己编写一个过滤器!!!!过滤器Filter在Servlet执行之前执行。

<!--配置字符编码过滤器,在上面DispatcherServlet执行之前先执行-->
<filter><filter-name>characterEncodingFilter</filter-name><filter-class>com.hnlg.springmvc.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

第二种方案:使用SpringMVC框架内置的字符编码过滤器即可:CharacterEncodingFilter。

<!--使用SpringMVC框架内置的字符编码过滤器-->
<filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><!--设置字符集--><param-value>UTF-8</param-value></init-param><!--让请求体的编码方式强行使用以上的字符集。--><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><!--让响应体的编码方式强行使用以上的字符集。--><param-value>true</param-value></init-param>
</filter>
<filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

6. 三个域对象

请求域:request会话域:session应用域:application三个域都有以下三个方法:

// 向域中存储数据
void setAttribute(String name, Object obj);
​
// 从域中读取数据
Object getAttribute(String name);
​
// 删除域中的数据
void removeAttribute(String name);

主要是通过:setAttribute + getAttribute方法来完成在域中数据的传递和共享。

对比项转发(Forward)重定向(Redirect)
请求次数1次(服务器内部完成)2次(客户端再次发起)
浏览器地址栏不变(仍显示原地址 /login改变(变为新地址 /welcome.jsp
能否共享 request 域数据✅ 可以(request.setAttribute() 有效)❌ 不行(request 已结束)
效率高(服务器内部跳转)稍低(多一次 HTTP 请求)
应用场景表单验证失败、内部处理跳转登录成功跳转、防止重复提交
API 调用request.getRequestDispatcher().forward()response.sendRedirect()
场景推荐方式原因
表单提交后跳转到结果页✅ 重定向防止用户刷新页面重复提交(F5 不会重复登录)
表单验证失败返回原页✅ 转发可以保留错误信息和用户已填数据
传递敏感数据✅ 转发不暴露在 URL 中
跳转到外部网站✅ 重定向只能用 sendRedirect("https://baidu.com")

6.1 request域

接口名:HttpServletRequest

request对象代表了一次请求。一次请求一个request。

使用请求域的业务场景:在A资源中通过转发的方式跳转到B资源,因为是转发,因此从A到B是一次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到request域中。

6.1.1 共享数据方式

原生Servlet API方式
<a th:href="@{/testServletAPI}">测试在SpringMVC当中使用原生Servlet API完成request域数据共享</a>
@Controller
public class RequestScopeTestController {@RequestMapping("/testServletAPI")public String testServletAPI(HttpServletRequest request){// 将共享的数据存储到request域当中request.setAttribute("testRequestScope", "在SpringMVC当中使用原生Servlet API完成request域数据共享");// 跳转视图,在视图页面将request域中的数据取出来,这样就完成了:Controller和View在同一个请求当中两个组件之间数据的共享。// 这个跳转,默认情况下是:转发的方式。(转发forward是一次请求)// 这个返回的是一个逻辑视图名称,经过视图解析器解析,变成物理视图名称。/WEB-INF/thymeleaf/ok.htmlreturn "ok";}
}

无论是Model、Map还是ModelMap,底层实例化的对象都是:BindingAwareModelMap。

BindingAwareModelMap继承了ModelMap,而ModelMap又实现了Map接口。 因此表面上是采用了不同方式,底层本质上是相同的。

Model接口
<a th:href="@{/testModel}">测试在SpringMVC当中使用Model接口完成request域数据共享</a>
@RequestMapping("/testModel")
public String testModel(Model model){// 向request域当中绑定数据model.addAttribute("testRequestScope", "在SpringMVC当中使用Model接口完成request域数据共享");System.out.println(model);System.out.println(model.getClass().getName());// 转发return "ok";
}
Map接口
<a th:href="@{/testMap}">测试在SpringMVC当中使用Map接口完成request域数据共享</a>
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){// 向request域当中存储数据map.put("testRequestScope", "在SpringMVC当中使用Map接口完成request域数据共享");System.out.println(map);System.out.println(map.getClass().getName());// 转发return "ok";
}
Model Map类
<a th:href="@{/testModelMap}">测试在SpringMVC当中使用ModelMap类完成request域数据共享</a>
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){// 向request域当中存储数据modelMap.addAttribute("testRequestScope", "在SpringMVC当中使用ModelMap类完成request域数据共享");System.out.println(modelMap);System.out.println(modelMap.getClass().getName());// 转发return "ok";
}

ModelAndView类

在SpringMVC框架中为了更好的体现MVC架构模式,提供了一个类:ModelAndView。这个类的实例封装了Model和View。也就是说这个类既封装业务处理之后的数据,也体现了跳转到哪个视图。使用它也可以完成request域数据共享。

<a th:href="@{/testModelAndView}">测试在SpringMVC当中使用ModelAndView类完成request域数据共享</a>
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){// 创建 模型视图 对象ModelAndView mav = new ModelAndView();// 给 模型视图对象 绑定数据mav.addObject("testRequestScope", "在SpringMVC当中使用ModelAndView类完成request域数据共享");// 给 模型视图对象 绑定视图(绑定逻辑视图名称)mav.setViewName("ok");// 返回 模型视图对象return mav;
}
  1. 方法的返回值类型不是String,而是ModelAndView对象。

  2. ModelAndView不是出现在方法的参数位置,而是在方法体中new的。

  3. 需要调用addObject向域中存储数据。

  4. 需要调用setViewName设置视图的名字。

当请求路径不是JSP的时候,都会走前端控制器DispatcherServlet。
DispatcherServlet中有一个核心方法 doDispatch(),这个方法用来通过请求路径找到对应的 处理器方法;

然后调用 处理器方法,处理器方法返回一个逻辑视图名称(可能也会直接返回一个ModelAndView对象),底层会将逻辑视图名称转换为View对象,然后将View对象结合Model对象,封装一个ModelAndView对象,然后将该对象返回给DispatcherServlet类了。

6.1.2 显示结果

<div th:text="${testRequestScope}"></div>

6.2 session域

接口名:HttpSession

session对象代表了一次会话。从打开浏览器开始访问,到最终浏览器关闭,这是一次完整的会话。每个会话session对象都对应一个JSESSIONID,而JSESSIONID生成后以cookie的方式存储在浏览器客户端。浏览器关闭,JSESSIONID失效,会话结束。

使用会话域的业务场景:

  1. 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。

  2. 登录成功后保存用户的登录状态。

6.2.1 原生Servlet API

<a th:href="@{/testSessionServletAPI}">测试在SpringMVC当中使用原生Servlet API完成session域数据共享</a>
@Controller
public class SessionScopeTestController {@RequestMapping("/testSessionServletAPI")public String testServletAPI(HttpSession session){// 处理核心业务....// 将数据存储到session中session.setAttribute("testSessionScope", "在SpringMVC当中使用原生Servlet API完成session域数据共享");// 返回逻辑视图名称(这是一个转发的行为)return "ok";}
}
<div th:text="${session.testSessionScope}"></div>

6.2.2 SessionAttributes注解

<a th:href="@{/testSessionAttributes}">测试在SpringMVC当中使用@SessionAttributes注解完成session域数据共享</a>
@Controller
//@SessionAttributes(value = {"x", "y"})
@SessionAttributes({"x", "y"}) // 标注x和y都是存放到session域中,而不是request域。
public class SessionScopeTestController {@RequestMapping("/testSessionAttributes")/*ModelMap指的是request域,但是在类上加了注解就存储到session域,如果没有注解就默认存储到request域*/public String testSessionAttributes(ModelMap modelMap){// 处理业务// 将数据存储到session域当中modelMap.addAttribute("x", "我是埃克斯");modelMap.addAttribute("y", "我是歪");// 返回逻辑视图名称return "ok";}
}
<div th:text="${session.x}"></div>
<div th:text="${session.y}"></div>

6.3 application

接口名:ServletContext

application对象代表了整个web应用,服务器启动时创建,服务器关闭时销毁。对于一个web应用来说,application对象只有一个。

使用应用域的业务场景:记录网站的在线人数。

<a th:href="@{/testApplicationScope}">测试在SpringMVC当中使用Servlet API实现application域数据共享</a>
@Controller
public class ApplicationScopeTestController {@RequestMapping("/testApplicationScope")public String testApplicationScope(HttpServletRequest request){// 将数据存储到application域当中// 获取application对象,其实就是获取ServletContext对象// 怎么获取ServletContext对象?通过request,通过session都可以获取。ServletContext application = request.getServletContext();application.setAttribute("testApplicationScope", "在SpringMVC中使用ServletAPI实现application域共享");return "ok";}
}
<div th:text="${application.testApplicationScope}"></div>

7. SpringMVC的视图实现原理

Spring MVC支持的常见视图包括:

1. InternalResourceView:内部资源视图(Spring MVC框架内置的,专门为JSP模板语法准备的,也是为转发准备的)

2. RedirectView:重定向视图(Spring MVC框架内置的,用来完成重定向效果)

3. ThymeleafView:Thymeleaf视图(第三方的,为Thymeleaf模板语法准备的)

4. FreeMarkerView:FreeMarker视图(第三方的,为FreeMarker模板语法准备的)

5. VelocityView:Velocity视图(第三方的,为Velocity模板语法准备的)

6. PDFView:PDF视图(第三方的,专门用来生成pdf文件视图)

7. ExcelView:Excel视图(第三方的,专门用来生成excel文件视图)


7.1 实现视图机制的核心接口

7.1.1 DispatcherServlet类(前端控制器)

职责:在整个Spring MVC执行流程中,负责中央调度。

核心方法:doDispatch

7.1.2 ViewResolver接口(视图解析器)

职责:负责将逻辑视图名转换为物理视图名,最终创建View接口的实现类,即视图实现类对象。

核心方法:resolveViewName

7.1.3 View接口(视图)

职责:负责将模型数据Model渲染为视图格式(HTML代码),并最终将生成的视图(HTML代码)输出到客户端。(它负责将模板语言转换成HTML代码)

核心方法:render

7.1.4 ViewResolverRegistry(视图解析器注册器)

负责在web容器(Tomcat)启动的时候,完成视图解析器的注册。

如果有多个视图解析器,会将视图解析器对象按照order的配置放入List集合。

如果想定制自己的视图组件:

  • 编写类实现ViewResolver接口,实现resolveViewName方法,在该方法中完成**逻辑视图名**转换为**物理视图名**,并返回View对象。

  • 编写类实现View接口,实现render方法,在该方法中将模板语言转换成HTML代码,并将HTML代码响应到浏览器。

如果Spring MVC框架中使用Thymeleaf作为视图技术。那么相关的类包括:

  • ThymeleafView

  • ThymeleafViewResolver


第一步:浏览器发送请求给web服务器

第二步:Spring MVC中的DispatcherServlet接收到请求

第三步:DispatcherServlet根据请求路径分发到对应的Controller

第四步:DispatcherServlet调用Controller的方法

第五步:Controller的方法处理业务并返回一个逻辑视图名给DispatcherServlet

第六步:DispatcherServlet调用ThymeleafViewResolver的resolveViewName方法,将逻辑视图名转换为物理视图名,并创建ThymeleafView对象返回给DispatcherServlet

第七步:DispatcherServlet再调用ThymeleafView的render方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。


7.2 Thymeleaf视图解析器源码

public class DispatcherServlet extends FrameworkServlet {// 前端控制器的核心方法,处理请求,返回视图,渲染视图,都是在这个方法中完成的。protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 根据请求路径调用映射的处理器方法,处理器方法执行结束之后,返回逻辑视图名称// 返回逻辑视图名称之后,DispatcherServlet会将 逻辑视图名称ViewName + Model,将其封装为ModelAndView对象。mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 这行代码的作用是处理视图processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 渲染页面(将模板字符串转换成html代码响应到浏览器)render(mv, request, response);}protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// 这个方法的作用是将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象ViewView view = resolveViewName(viewName, mv.getModelInternal(), locale, request);// 真正的将模板字符串转换成HTML代码,并且将HTML代码响应给浏览器。(真正的渲染)view.render(mv.getModelInternal(), request, response);}// 将 逻辑视图名称 转换成 物理视图名称,并且最终返回视图对象Viewprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {// 其实这一行代码才是真正起作用的:将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象ViewViewResolver viewResolver;  // 底层会创建一个ThymeleafViewResolver// 如果使用的是Thymeleaf,那么返回的视图对象:ThymeleafView对象。View view = viewResolver.resolveViewName(viewName, locale); return view;}}// 这是一个接口(负责视图解析的)
public interface ViewResolver { // 如果使用Thymeleaf,那么该接口的实现类就是:ThymeleafViewResolver// 这个方法就是将:逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象  ViewView resolveViewName(String viewName, Locale locale) throws Exception;
}// 这是一个接口(负责将 模板字符串 转换成HTML代码,响应给浏览器)
public interface View {void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)throws Exception;
}/*核心类:DispatcherServlet核心接口1:ViewResolver(如果使用的是Thymeleaf,那么底层会创建ThymeleafViewResolver对象)核心接口2:View(如果你使用的是Thymeleaf,那么底层会创建ThymeleafView对象)结论:如果自己想实现属于自己的视图。至少需要编写两个类,一个类实现ViewResolver接口,实现其中的resolveViewName方法。另一个类实现View接口,实现其中的render方法。
*/

8. 转发与重定向

8.1 转发forward与重定向redirect创建对象

@Controller
public class ForwardController {@RequestMapping("/a")public String toA(){// 返回的是一个逻辑视图名称// 默认采用的也是转发//return "a";// 采用SpringMVC的转发方式跳转到 /b// 转发的时候,格式有特殊要求: return "forward:下一个资源的路径";// 这个就不是逻辑视图名称了。//return "forward:/b"; // 创建InternalResourceView对象。// 这个使用较多。return "redirect:/b"; // 创建RedirectView}@RequestMapping("/b")public String toB(){// 返回的是一个逻辑视图名称return "b"; // 创建ThymeleafView对象。}
}

8.2 mvc:view-controller

<mvc:view-controller> 配置用于将某个请求映射到特定的视图上,即指定某一个 URL 请求到一个视图资源的映射,使得这个视图资源可以被访问。它相当于是一个独立的处理程序,不需要编写任何 Controller,只需要指定 URL 和对应的视图名称就可以了。

一般情况下,<mvc:view-controller> 配置可以替代一些没有业务逻辑的 Controller,例如首页、错误页面等。当用户访问配置的 URL 时,框架将直接匹配到对应的视图,而无需再经过其他控制器的处理。

<mvc:view-controller path="/如何访问该页面" view-name="对应的逻辑视图名称" />
<!--配置视图控制器-->
<!--只用这个组件单独配置之后,所有的注解就失效了-->

path="/test"表示处理所有访问 /test的请求

  • 支持 Ant 风格路径模式(如 /admin/*/docs/**)

  • 可配置多个路径(逗号分隔:path="/test,/demo"

8.3 mvc:annotation-driven

<!--开启注解驱动-->
<mvc:annotation-driven/>

8.4 访问静态资源

由于DispatcherServlet的url-pattern配置的是“/”,之前说过,这个"/"代表的是除jsp请求之外的所有请求,也就是说访问应用中的静态资源,也会走DispatcherServlet,这会导致404错误,无法访问静态资源,如何解决,两种方案:

  • 使用默认 Servlet 处理静态资源

  • 使用 mvc:resources 标签配置静态资源处理

8.4.1 默认Servlet处理静态资源

<!--开启注解驱动-->
<mvc:annotation-driven/><!--开启默认的Servlet处理-->
<mvc:default-servlet-handler/>

8.4.2 mvc:resources 标签配置静态资源处理

<!--开启注解驱动-->
<mvc:annotation-driven/><!--当请求路径符合 /static/** 的时候,去 /static/ 位置找资源-->
<mvc:resources mapping="/static/**" location="/static/" />

表示凡是请求路径是"/static/"开始的,都会去"/static/"目录下找该资源。

9. RESTFul编程风格

9.1 RESTFul是什么

REST对请求方式的约束是这样的:

  • 查询必须发送GET请求

  • 新增必须发送POST请求

  • 修改必须发送PUT请求

  • 删除必须发送DELETE请求

REST对URL的约束是这样的:

  • 传统的URL:get请求,/springmvc/getUserById?id=1

  • REST风格的URL:get请求,/springmvc/user/1


  • 传统的URL:get请求,/springmvc/deleteUserById?id=1
  • REST风格的URL:delete请求, /springmvc/user/1

9.2 RESTFul风格与传统方式对比

传统的 URL 与 RESTful URL 的区别是传统的 URL 是基于方法名进行资源访问和操作,

而 RESTful URL 是基于资源的结构和状态进行操作的。

传统的 URLRESTful URL
GET /getUserById?id=1GET /user/1
GET /getAllUserGET /user
POST /addUserPOST /user
POST /modifyUserPUT /user
GET /deleteUserById?id=1DELETE /user/1

9.3 RESTFul方式演示查询

<!--RESTful风格的:查看用户列表-->
<a th:href="@{/user}">查看用户列表</a><br><!--RESTful风格的:根据id查询用户信息-->
<a th:href="@{/user/110}">查询id=110的这个用户信息</a><br>
@Controller
public class UserController {@RequestMapping(value = "/user", method = RequestMethod.GET)public String getAll(){System.out.println("正在查询所有用户信息....");return "ok";}@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)public String getById(@PathVariable("id") String id){System.out.println("正在根据用户id查询用户信息...,用户id是" + id);return "ok";}
}

9.4 RESTFul方式演示增加

<!--RESTful风格的:新增用户信息。新增必须发送POST请求,需要使用form表单-->
<form th:action="@{/user}" method="post">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br>年龄:<input type="text" name="age"><br><input type="submit" value="保存">
</form>
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String save(User user){System.out.println("正在保存用户信息...");System.out.println(user);return "ok";
}

9.5 RESTFul方式演示修改

如何发送PUT请求?

第一步:首先你必须是一个POST请求。

第二步:在发送POST请求的时候,提交这样的数据:**_method=PUT**

第三步:在web.xml文件配置SpringMVC提供的过滤器:HiddenHttpMethodFilter

<!--RESTful风格的:修改用户信息。修改必须发送PUT请求,要发送PUT请求,
首先你必须是一个POST请求-->
<form th:action="@{/user}" method="post"><!--隐藏域--><!--强调:name必须是 _method,value必须是put/PUT。如果你要发送delete请求的话,value写delete即可。--><input type="hidden" name="_method" value="put">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br>年龄:<input type="text" name="age"><br><input type="submit" value="修改">
</form>
<!--添加一个过滤器,这个过滤器是springmvc提前写好的,直接用就行了,这个过滤器可以帮助你将请求POST转换成PUT请求/DELETE请求-->
<!--一定要在字符编码过滤器后面配置。-->
<filter><filter-name>hiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping><filter-name>hiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String modify(User user){System.out.println("正在修改用户信息:" + user);return "ok";
}

9.6 RESTFul方式演示删除

操作实现方式是否需要 JS说明
修改(PUT)直接用 <form method="post"> + _method=put❌ 不需要 JS表单自然提交
删除(DELETE)用 <a> 触发 JS,动态设置表单 action 并提交✅ 需要 JS因为没有“提交按钮”

💡 为什么删除要用 JS?因为删除通常是一个链接或按钮,不是表单提交行为。

<!--RESTful风格的:删除用户信息-->
<!--删除必须发送DELETE请求。和PUT请求实现方式相同。-->
<!--发送DELETE请求的前提是POST请求,并且需要通过隐藏域提交 _method=delete -->
<!--发送一个 DELETE 请求到 /user/120-->
<!--HTML 无法直接发送 DELETE-->
<!--用 JS 控制一个隐藏的 POST 表单提交-->
<!--表单中包含 _method=delete,由后端识别转换-->
<a th:href="@{/user/120}" onclick="del(event)">删除用户id=120的用户信息</a><form id="delForm" method="post"><input type="hidden" name="_method" value="delete">
</form><script>function del(event){// 获取表单let delForm = document.getElementById("delForm");// 给form的action赋值delForm.action = event.target.href;// 发送POST请求提交表单delForm.submit();// 非常重要,你需要阻止超链接的默认行为。event.preventDefault();}
</script>
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public String del(@PathVariable("id") String id){System.out.println("正在删除用户:" + id);return "ok";
}

Optional<Long> 是一个可能包含 Long 值的“盒子”,它明确告诉你:

  • ✅ 有值(比如 Optional[1003]
  • ❌ 没有值(空的,Optional.empty()

为什么 stream() 的某些操作返回 Optional

因为 有些操作的结果可能不存在

常见返回 Optional 的操作:

方法什么时候有值?什么时候是 Optional.empty()
.findFirst()找到了第一个元素列表为空或没匹配项
.findAny()找到了任意一个没找到
.reduce(...)聚合出一个结果列表为空
.max() / .min()找到了最大/最小值列表为空

为什么不是所有 Stream 操作都返回 Optional

因为有些操作一定有结果,不需要包装:

操作返回类型说明
.count()long空集合也是 0,不会为空
.collect()List<T>返回集合,空集合是 [],不是 null
.forEach()void直接执行,不返回值

文章转载自:

http://M80f1cbA.sLfym.cn
http://44Q9RtgI.sLfym.cn
http://xJsqYAYx.sLfym.cn
http://ne0nXNsd.sLfym.cn
http://grcabOb3.sLfym.cn
http://mfsPvHGj.sLfym.cn
http://VOU5ZPKZ.sLfym.cn
http://Np7zW9yY.sLfym.cn
http://BAUqUkjW.sLfym.cn
http://qdcp0mEV.sLfym.cn
http://XZ2us2an.sLfym.cn
http://DlgfT31Z.sLfym.cn
http://36alTDpy.sLfym.cn
http://h5dnb47e.sLfym.cn
http://dHK0ixEA.sLfym.cn
http://a02Uid58.sLfym.cn
http://bfwA0r1t.sLfym.cn
http://M5RvJDfA.sLfym.cn
http://fcDyzRDs.sLfym.cn
http://vGoSpa0Y.sLfym.cn
http://qsN9ZVH4.sLfym.cn
http://pV1njdTF.sLfym.cn
http://JzZD8hGZ.sLfym.cn
http://hz8eWVDN.sLfym.cn
http://pC1v9KAI.sLfym.cn
http://1OfnaYXy.sLfym.cn
http://Q8ak7CkW.sLfym.cn
http://ql3SizCR.sLfym.cn
http://jtjDx1yy.sLfym.cn
http://BeJickWb.sLfym.cn
http://www.dtcms.com/a/383002.html

相关文章:

  • Why Language Models Hallucinate 论文翻译
  • 从 WPF 到 Avalonia 的迁移系列实战篇5:Trigger、MultiTrigger、DataTrigger 的迁移
  • easyExcel动态应用案例
  • 目标计数论文阅读(2)Learning To Count Everything
  • 贪心算法应用:速率单调调度(RMS)问题详解
  • 【传奇开心果系列】基于Flet框架实现的用窗口管理器动态集中管理多窗口自定义组件模板特色和实现原理深度分析
  • [Android] 汉语大辞典3.2
  • 《嵌入式硬件(八):基于IMX6ULL的点灯操作》
  • css的基本知识
  • AOP 切面日志详细
  • 软件工程实践二:Spring Boot 知识回顾
  • 从美光暂停报价看存储市场博弈,2026年冲突加剧!
  • Bean.
  • Kafka 入门指南:从 0 到 1 构建你的 Kafka 知识基础入门体系
  • 从qwen3-next学习大模型前沿架构
  • 【Linux】深入Linux多线程架构与高性能编程
  • Python爬虫-爬取拉勾网招聘数据
  • Python|Pyppeteer解决Pyppeteer启动后,页面一直显示加载中,并显示转圈卡死的问题(37)
  • C++_STL和数据结构《1》_STL、STL_迭代器、c++中的模版、STL_vecto、列表初始化、三个算法、链表
  • 【计算机网络 | 第16篇】DNS域名工作原理
  • C++算法题中的输入输出形式(I/O)
  • 【算法详解】:编程中的“无限”可能,驾驭超大数的艺术—高精度算法
  • Linux基础开发工具(gcc/g++,yum,vim,make/makefile)
  • NLP:Transformer之多头注意力(特别分享4)
  • arm芯片的功能优化方案
  • 【C++】动态数组vector的使用
  • 软件工程实践三:RESTful API 设计原则
  • [硬件电路-221]:PN结的电阻率是变化的,由无穷大到极小,随着控制电压的变化而变化,不同的电场方向,电阻率的特征也不一样,这正是PN的最有价值的地方。
  • 用户争夺与智能管理:定制开发开源AI智能名片S2B2C商城小程序的战略价值与实践路径
  • 5 遥感与机器学习第三方库安装