Spring MVC 是 Java 中最受欢迎的 Web 框架之一,它为开发人员提供的强大的工具和灵活的架构,帮助构建高效、可扩展且易于维护的 Web 应用程序。本文将深入探讨 Spring MVC 的核心概念、使用方法以及实践操作。
在了解什么是 Spring MVC之前,我们首先有必要先了解其中的 MVC 到底是什么。
MVC 是 “Mode View Controller” 的缩写,它是一种软件设计模式,用于将应用程序划分为三个核心组件,即:Model(模型)、View(视图)和 Controller(控制器)。每个组件都有自己独特的职责,它们之间协调工作以实现应用程序的分层设计和低耦合。
Model(模型):负责管理应用程序的数据和业务逻辑。它独立于视图和控制器,通过提供数据接口供视图展示数据,同时也能接受来自控制器的指令来更新数据状态。
View(视图):负责展示数据给用户,并将用户的输入传递给控制器。它通常是用户界面的呈现,如 HTML 页面、用户界面控件等。
Controller(控制器):充当模型和视图之间的中介,处理用户的请求并作出响应。它接受用户输入,根据输入来更新模型状态,并选择合适的视图来展示更新后的数据。
MVC 模式的优点在于它能够是代码分离,使得应用程序更易于维护和实现
。例如,当需要更改应用程序的外观时,只需更改视图层而不会影响模型和控制器的逻辑。这种解耦合的设计使得团队成员可以并行开发不同组件,从而提高开发效率。
Spring MVC 是 Spring 框架中的一个 Web 框架,用于构建基于 Java 的 Web 应用程序。它是一个轻量级、灵活且功能强大的框架,采用MVC(Model-View-Controller)设计模式,将应用程序划分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。
具体来说,Spring MVC的定义如下:
Web 框架:Spring MVC 专注于处理 Web 应用程序的请求和响应,提供了处理 HTTP 请求和构建 Web 页面的核心功能。
基于Servlet API:Spring MVC 构建在 Java Servlet API 之上,利用 Servlet 容器来处理 HTTP 请求和响应,因此可以与常见的 Java Web 容器无缝集成。
MVC 设计模式:Spring MVC 采用了MVC 设计模式,将应用程序划分为三个主要组件,使得各组件的职责清晰分离,从而提高了代码的可维护性和可测试性。
轻量级和灵活:Spring MVC 是一个轻量级框架,不需要复杂的配置和依赖,同时具有很高的灵活性,允许开发者根据具体需求定制处理请求的流程。
与 Spring 整合:Spring MVC 是Spring 框架的一部分,因此可以与其他 Spring 组件(如 Spring Core、Spring Boot 等)无缝集成,享受 Spring 生态系统的丰富特性和便利性。
Spring MVC 通过处理器映射(Handler Mapping)将 URL 请求映射到相应的控制器方法上,控制器方法处理请求并更新模型状态,然后选择适当的视图来展示数据给用户。它还提供了视图解析器(View Resolver),用于将控制器返回的逻辑视图映射到具体的视图技术(如JSP、Thymeleaf等)。
总体而言,Spring MVC 是 Java Web 开发中最受欢迎的框架之一,它提供了丰富的功能和特性,使得开发者能够快速构建高效、可维护的Web应用程序。
Spring MVC 项目的创建和 Spring Boot 项目的创建的方式相同,因为之前在创建 Spring Boot 项目的时候,一直都引入了一个叫做 Spring Web
的依赖。因此,当在创建 Spring Boot 项目的时候引入了这个依赖,那么我们的项目就变成了一个 Spring MVC 项目了。
在demo
目录下创建一个控制层的类 TestController
,通过浏览器访问时,页面上输出 “Hello World” :
@RestController
public class TestController {
@RequestMapping("/hello")
public String hello(){
return "Hello World";
}
}
运行启动类,通过浏览器访问 http://localhost:8080/hello
得到以下结果:
在 Spring MVC 中,@RequestMapping
是一个非常重要的注解,用于将 HTTP 请求映射到控制器的处理方法上。通过 @RequestMapping
注解,我们可以定义 URL 路径、HTTP 请求方法、请求参数等于控制器方法的映射关系。
通过查看 @RequestMapping
注解的源码可以看到里面有许多的属性:
下面是@RequestMapping
注解的常用属性及其说明:
属性名 | 说明 |
---|---|
name |
设置处理器方法的名称,用于在其他组件中进行引用。 |
value |
指定URL路径,可以是一个字符串或字符串数组。 |
path |
与value 属性等价,用于指定URL路径,可以是一个字符串或字符串数组。 |
method |
指定HTTP请求方法,可以是RequestMethod枚举值或RequestMethod数组。 |
params |
指定请求参数的条件,可以是一个字符串或字符串数组。 |
headers |
指定HTTP请求头部的条件,可以是一个字符串或字符串数组。 |
consumes |
指定请求的Content-Type媒体类型,可以是一个字符串或字符串数组。 |
produces |
指定响应的Content-Type媒体类型,可以是一个字符串或字符串数组。 |
@RequestMapping
注解是一个元注解(meta-annotation),它本身也被其他注解如@GetMapping
、@PostMapping
、@PutMapping
和@DeleteMapping
所使用。通过设置不同的属性,可以灵活地定义控制器方法的URL映射、HTTP请求方法、请求参数和请求头等条件,从而实现多样化的请求处理。
@RequestMapping
注解既可以用在方法级别上,也可以用在类级别上。
方法级别:
在方法级别上,@RequestMapping注解用于指定具体的请求映射路径。例如:
@Controller
public class UserController {
// 方法级别路径为"/profile"
// 最终请求路径为"/profile"
@RequestMapping("/profile")
public String userProfile() {
// 处理方法逻辑
return "profile";
}
}
类级别:
在类级别上,@RequestMapping
注解用于指定该类中所有处理方法的共同父路径。例如:
@Controller
@RequestMapping("/users")
public class UserController {
// 类中所有方法的父路径为"/users"
// 具体路径为"/users/profile"
@RequestMapping("/profile")
public String userProfile() {
// 处理方法逻辑
return "profile";
}
}
回到第一个 “Hello World” 程序,我们可以通过浏览器访问 http://localhost:8080/hello
,然后在页面上输出 “Hello World”。
那么这个HTTP请求使用的是什么方法呢?
通过浏览器抓包可以发现,使用的是 GET请求,那么是否支持其他请求呢?
通过 Postman 发送 POST 请求,发现也能够获取到返回的内容:
另外发送 PUT 和 DELETE 方法请求,发现都能获取到结果,也就是说在默认情况下,使用@RequestMapping
注解支持所有的方法。当然,如果要想只支持某种方法,可以通过其 method
属性进行指定,例如指定 GET 方法:
则发现此时返回的状态码为 405,即不支持 POST 请求方法。
为了简化@RequestMapping
注解的使用,Spring MVC 提供了更具体的缩写注解:@GetMapping
、@PostMapping
、@PutMapping
和@DeleteMapping
。这些注解分别对应 HTTP 的 GET、POST、PUT 和 DELETE 请求方法,可以让代码更加简洁和直观。
例如,上述代码可以使用@GetMapping
来替代:
@RestController
public class TestController {
@GetMapping("/hello")
public String hello(){
return "Hello World";
}
}
在 Web 开发中,处理用户提交的请求时,经常需要从请求中获取参数来进行后续的处理。Spring MVC提供了多种方式来获取参数,包括获取简单参数、获取对象、设置参数重命名和设置参数是否必传等。
在 HTTP 请求中,查询字符串是 URL 中问号后面的参数部分,例如:http://example.com?name=zhangsan&age=25
。我们可以通过在控制器方法中添加参数,使用@RequestParam
注解来获取查询字符串中的参数值。
@RestController
public class TestController {
@GetMapping("/get")
public String getParam(@RequestParam String name, @RequestParam Integer age){
return "name = " + name + ", age = " + age;
}
}
如果查询字符串中的参数名和
getParam1中的参数名称一样
的情况下,@RequestParam
注解可以省略,此时 Spring MVC 会自动将请求中对应的参数值绑定到方法参数上。例如:
@RestController
public class TestController {
@GetMapping("/get")
public String getParam(@RequestParam String name, @RequestParam Integer age){
return "name = " + name + ", age = " + age;
}
}
在表单提交时,同样可以通过@RequestParam
注解来获取表单中的参数值,此时和上面获取查询字符串中的参数方法基本一样,只是 HTTP 的方法变成了 POST:
表单页面:
<form action="/submit" method="post">
<input type="text" name="name">
<input type="number" name="age">
<input type="submit" value="提交">
form>
获取方法:
@RestController
public class TestController {
@PostMapping("/submit")
public String submitForm(@RequestParam String name, @RequestParam Integer age){
return "name = " + name + ", age = " + age;
}
}
此时,可以通过 URL 中的查询字符串传递name
和age
参数,也可以通过form
表单进行提交,只要传递的参数名称和 User
类中的参数名称能够匹配,那么 Spring MVC 就是自动将请求中的参数绑定到User
对象中。
@RestController
public class TestController {
@RequestMapping("/user")
public String getUser(User user){
return user.toString();
}
}
通过浏览器访问http://localhost:8080/user?name=zhangsan&age1=25
:
当客户端通过 JSON 格式提交数据时,可以使用 @RequestBody
注解将请求体中的 JSON 数据绑定到 Java 对象上。
@RestController
public class TestController {
@RequestMapping("/user")
public String getJsonUser(@RequestBody User user){
return user.toString();
}
}
通过 Postman 提交以下 JSON 格式的内容:
{
"name": "zhangsan",
"age": 25
}
当前后端参数不匹配的时候,如果不加处理,就不能够正确的获取到想要的参数,此时就可以通过 @RequestParam
注解来设置参数的重命名。
在 Java 代码中可以查看 @RequestParam
的源码:
其中的 value
或者 name
就可以用来进行参数重命名操作,例如此时前端请求 URL 中的查询字符串是 username=zhangsan&age=25
,而后端用于接收的是 name
和 age
:
@RestController
public class TestController {
@GetMapping("/get")
public String getParam(@RequestParam(value = "username") String name, @RequestParam Integer age){
return "name = " + name + ", age = " + age;
}
}
例如,通过下面的代码来获取 URL 中的请求参数:
@RestController
public class TestController {
@GetMapping("/get")
public String getParam(@RequestParam String name, @RequestParam Integer age){
return "name = " + name + ", age = " + age;
}
}
那么收到的响应状态码就为 400,即请求错误。在上文的 @RequestParam
注解中可以发现有一个参数叫做 required
,其默认值为 true
,意味着参数是必传的,如果缺少的参数就会保存,那么此时,我们可以将age
参数设置为非必传的,如下面的代码:
@RestController
public class TestController {
@GetMapping("/get")
public String getParam(@RequestParam String name, @RequestParam(required = false) Integer age){
return "name = " + name + ", age = " + age;
}
}
在 Spring MVC 中,URL 路径通常包含一些占位符,这些占位符可以用于传递参数。通过@PathVariable
注解,可以将这些 URL 路径中的参数值绑定到方法的参数上,从而在控制器方法中使用这些参数进行处理。
例如,假设我们有一个URL路径为 /root/{name}/{age}
,其中 {name}
和 {age}
就是占位符。我们可以通过@PathVariable
注解将这个占位符对应的值绑定到方法的参数上,如下所示:
@RestController
public class TestController {
@RequestMapping("/root/{name}/{age}")
public String getParam(@PathVariable String name, @PathVariable String age){
return "name = " + name + ", age = " + age;
}
}
通过浏览器访问,就能够获取到 URL 中的参数了:
另外需要注意的是,@PathVariable
注解的参数名称需要与 URL 路径中的占位符名称一致,如果名称不一致,可以使用@PathVariable("placeholderName")
指定占位符的名称。
例如,假设 URL 路径为 /root/{username}/{age}
,但想在方法中使用name
来表示这个参数,可以这样做:
@RestController
public class TestController {
@RequestMapping("/root/{name}/{age}")
public String getParam(@PathVariable(name = "username") String name, @PathVariable String age){
return "name = " + name + ", age = " + age;
}
}
@RequestPart
注解用于实现文件的上传。它是 Spring MVC 中处理文件上传的一种方式。
通常,在文件上传过程中,客户端将文件数据以multipart/form-data
的形式提交到服务器。在 Spring MVC 中,可以使用 @RequestPart
注解将 multipart/form-data
中的文件部分绑定到控制器方法的参数上,从而实现文件的上传。
例如,现在要实现一个图片上传的功能,要求将上传的图片保存到一个文件夹中,并且每次上次的图片不能被下一次的上传操作覆盖,实现代码如下:
@RestController
public class TestController {
private static final String SAVE_PATH = "C:\\Users\\Administrator\\Desktop\\image\\";
@PostMapping("/upload")
public Object uploadImg(@RequestPart("img") MultipartFile file) {
if (file.isEmpty()) {
return "请选择要上传的图片";
}
// 检查文件类型
if (!isValidImageFile(file)) {
return "只能上传图片文件";
}
// 检查文件大小
if (!isValidFileSize(file)) {
return "图片文件大小不能超过2MB";
}
String originalFileName = file.getOriginalFilename(); // 获取原始文件名称
String extension = originalFileName.substring(originalFileName.lastIndexOf('.')); // 获取文件后缀
String fileName = generateUniqueFileName(extension);
File saveFile = new File(SAVE_PATH + fileName);
try {
file.transferTo(saveFile);
return "上传成功";
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
}
private boolean isValidImageFile(MultipartFile file) {
// 实现文件类型校验,根据实际需求进行判断
// 例如可以判断文件的后缀名是否为常见的图片格式:jpg/jpeg、png、gif等
// return file.getContentType().startsWith("image/");
return true; // 这里简化处理,总是返回true
}
private boolean isValidFileSize(MultipartFile file) {
// 实现文件大小校验,根据实际需求进行判断
// 例如可以判断文件的大小是否小于2MB
return file.getSize() <= 2 * 1024 * 1024; // 2MB
}
private String generateUniqueFileName(String extension) {
String fileName = UUID.randomUUID().toString() + extension;
return fileName;
}
}
这段代码实现了一个简单的文件上传功能。下面是对代码的解析:
private static final String SAVE_PATH = "C:\\Users\\Administrator\\Desktop\\image\\";
这是一个常量,表示文件保存的路径。在这个示例中,文件将被保存到C:\Users\Administrator\Desktop\image\
目录下。
@PostMapping("/upload")
这个注解表示uploadImg
方法用于处理 HTTP POST 请求,并映射到路径/upload
上。
public Object uploadImg(@RequestPart("img") MultipartFile file)
这个方法用于处理文件上传。@RequestPart("img")
注解用于绑定请求体中名为img
的部分到file
参数上。MultipartFile
是Spring框架提供的用于处理文件上传的接口,它表示上传的文件数据。
文件上传处理逻辑:
首先,检查file
是否为空,如果为空,则返回提示信息"请选择要上传的图片"。
接着,调用isValidImageFile
方法,检查文件类型。在示例中,这个方法被简化为总是返回true
,实际使用中可以根据需要实现文件类型的检查。
然后,调用isValidFileSize
方法,检查文件大小。在示例中,文件大小不能超过2MB(2 * 1024 * 1024字节)。
如果文件类型或文件大小校验不通过,返回相应的错误提示信息。
最后,如果文件上传校验通过,生成一个唯一的文件名,通过UUID.randomUUID().toString()
生成唯一的字符串,并根据原始文件的后缀名拼接成完整的文件名。然后将文件保存到SAVE_PATH
指定的目录中。
private boolean isValidImageFile(MultipartFile file)
这是一个私有方法,用于实现文件类型校验。在示例中,这个方法被简化为总是返回true
,实际使用中可以根据实际需求进行文件类型的检查,例如判断文件的后缀名是否为常见的图片格式:jpg/jpeg、png、gif等。
private boolean isValidFileSize(MultipartFile file)
这是一个私有方法,用于实现文件大小校验。在示例中,文件大小不能超过2MB(2 * 1024 * 1024字节)。
private String generateUniqueFileName(String extension)
这是一个私有方法,用于生成唯一的文件名。在示例中,使用UUID.randomUUID().toString()
生成一个唯一的字符串,然后根据文件的后缀名拼接成完整的文件名。
通过这段代码,实现了将上传的图片保存到指定的文件夹中,并在文件名中添加唯一标识,避免了文件覆盖的问题。同时,加入了文件类型和大小校验,增强了代码的健壮性和安全性。但实际文件保存路径、文件类型校验和文件大小校验等还需要根据具体需求进行进一步调整和完善。
通过 Postman 发起上传图片的请求:
通过@RequestHeader
注解,可以获取HTTP请求头部字段的值。
在 Spring MVC 中,HTTP 请求头部包含了一些元数据信息,例如User-Agent
、Content-Type
、Authorization
等。使用@RequestHeader
注解,可以将这些 HTTP 请求头部字段的值绑定到控制器方法的参数上,从而在方法中使用这些值进行处理。
以下是一个示例代码,演示如何使用@RequestHeader
获取HTTP头部字段的值:
@RestController
public class TestController {
@GetMapping("/headers")
public String getHeaders(@RequestHeader("User-Agent") String userAgent,
@RequestHeader("Accept-Language") String acceptLanguage
) {
// 在方法中使用获取到的HTTP头部字段值进行处理
// ...
return "User-Agent: " + userAgent + "
" +
"Accept-Language: " + acceptLanguage;
}
}
在上述示例中,我们定义了一个名为getHeaders
的控制器方法,并使用@GetMapping
注解将该方法映射到GET请求的/headers
路径上。通过浏览器访问并抓包,发现得到的内容和抓包的内容一模一样。
在Web应用中,Session用于在服务器端存储用户的状态信息。在 Spring MVC 存储和获取Session中的数据的方法和Servlet中的方法一样。
在Spring MVC中,可以使用HttpSession
对象来储存和获取Session中的数据。HttpSession
对象代表了当前用户的Session,并可以通过HttpServletRequest
对象的getSession()
方法来获取。
@RestController
public class TestController {
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request){
HttpSession session = request.getSession(true);
session.setAttribute("username", "zhangsan");
return "session has set.";
}
}
在上述示例中,在/setSession
路径下定义了一个setSession
方法,当用户访问该路径时,如果会话没有被创建,则会在Session
中存储一个名为"username"的属性,并设置其值为 “zhangsan” 的记录。
通过 Fiddle 抓包,可以发现成功设置了 Session
:
值得注意的是:
- 在Spring MVC中,当
@RequestMapping
注解的方法参数包含HttpServletRequest
和HttpServletResponse
时,Spring MVC 会自动将当前的HttpServletRequest
和HttpServletResponse
对象传递给这些方法参数。- 这意味着在
@RequestMapping
注解的方法中,可以直接声明HttpServletRequest
和HttpServletResponse
类型的参数,而不需要额外的配置或处理,Spring MVC 会自动将请求和响应对象传递给这些参数。
要获取Session中存储的数据,可以使用HttpSession
对象的getAttribute()
方法来获取。
@Controller
public class TestController {
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request) {
HttpSession session = request.getSession(false);
String username = (String) session.getAttribute("username");
return "Session中的username值为:" + username;
}
}
在上述示例中,我们在/getSession
路径下定义了一个getSession
方法,当用户访问该路径时,会从Session中获取名为"username"的属性,并将其值返回给用户。
除了使用 Session
来存储用户状态信息,Web 应用还可以使用 Cookie 来存储少量的用户信息。Spring MVC 提供了@CookieValue
注解来获取 Cookie 的值。
@RestController
public class TestController {
@GetMapping("/getCookie")
public String getCookie(@CookieValue("username") String username) {
return "Cookie中的username值为:" + username;
}
}
在上述示例中,我们在/getCookie
路径下定义了一个getCookie
方法,当用户访问该路径时,会从 Cookie 中获取名为 “username” 的值,并将其返回给用户。
请注意,在使用@CookieValue
注解时,要确保指定的 Cookie 名称存在。如果不存在,可以使用defaultValue
属性指定一个默认值,或者使用required
属性来设置是否必须存在。如果required
属性为true
,并且Cookie不存在,会抛出异常。
在Spring MVC中,控制器方法可以返回不同类型的数据内容。这些数据内容可以作为响应返回给客户端。
@Controller
public class DataController {
// 返回字符串
@GetMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello, World!";
}
// 返回数字
@GetMapping("/number")
@ResponseBody
public int getNumber() {
return 42;
}
// 返回布尔值
@GetMapping("/boolean")
@ResponseBody
public boolean getBoolean() {
return true;
}
}
在上述示例中,我们定义了一个名为DataController
的控制器,并使用@GetMapping
注解将三个不同的方法映射到不同的路径上。
sayHello
方法返回一个字符串"Hello, World!"。使用@ResponseBody
注解,将返回的字符串作为响应体直接返回给客户端。
getNumber
方法返回一个整数42。同样使用@ResponseBody
注解,将返回的整数作为响应体直接返回给客户端。
getBoolean
方法返回一个布尔值true。也使用@ResponseBody
注解,将返回的布尔值作为响应体直接返回给客户端。
除了返回数据内容,Spring MVC还支持返回静态页面。这通常用于返回HTML页面或其他静态资源。
@Controller
public class PageController {
@GetMapping("/index")
public String showPage() {
return "/index.html";
}
}
在上述示例中,我们定义了一个名为PageController
的控制器,并使用@GetMapping
注解将showPage
方法映射到GET请求的/index
路径上。在这个示例中,返回的视图逻辑名称是"index",Spring MVC会查找名为index.html
的HTML模板,并返回给客户端。
在Spring MVC中,还可以返回JSON格式的数据,通常用于提供API接口。
@Controller
public class JSONController {
@GetMapping("/user")
@ResponseBody
public User getUser() {
User user = new User("zhangsan", 30);
return user;
}
}
在上述示例中,定义了一个名为JSONController
的控制器,并使用@GetMapping
注解将getUser
方法映射到GET请求的/user
路径上。
getUser
方法返回一个User
对象,使用@ResponseBody
注解将这个对象转换为JSON格式,并作为响应体直接返回给客户端。
在实际应用中,可能还需要引入 JSON 序列化库(例如Jackson)来将 Java 对象转换为 JSON 格式。但是Spring MVC 会自动将返回值转换为 JSON 格式,并设置正确的 Content-Type
头部,以便客户端能够正确解析返回的 JSON 数据。
请求转发是指将请求从一个 Servlet 转发到另一个 Servlet 或 JSP 页面,转发过程在服务器端完成,客户端浏览器不感知。在 Spring MVC中,可以使用RequestDispatcher
对象实现请求转发。
@Controller
public class ForwardController {
@GetMapping("/forward")
public String forward() {
// 实现请求转发到 /index.html路径
return "forward:/index.html";
}
}
在上述示例中,定义了一个名为ForwardController
的控制器,并使用@GetMapping
注解将forward
方法映射到 GET 请求的/forward
路径上。
forward
方法返回一个字符串 “forward:/index.html”,这表示将请求转发到/index.html
路径。Spring MVC会在服务器端进行请求转发,将控制权交给/index.html
路径对应的处理器方法。
通过浏览器访问地址http://localhost:8080/forward
:
请求重定向是指在接收到请求后,服务器端发送一个 HTTP 响应,响应头部包含了 Location
字段,客户端根据该字段内容重新发送一个新的请求。因此,请求重定向涉及两次请求和响应过程,客户端感知到的是两次请求的结果
。
@Controller
public class RedirectController {
@GetMapping("/redirect")
public String redirect() {
// 实现请求重定向到 /index.html路径
return "redirect:/index.html";
}
}
在上述示例中,定义了一个名为RedirectController
的控制器,并使用@GetMapping
注解将redirect
方法映射到GET请求的/redirect
路径上。
redirect
方法返回一个字符串"redirect:/index.html",这表示将请求重定向到/index.html
路径。Spring MVC会在服务器端发送一个HTTP响应,响应头部包含了Location
字段,告诉客户端重定向到/index.html
路径。
通过浏览器访问地址http://localhost:8080/redirect
:
最后跳转到了http://localhost:8080/index.html
。
可以通过 Fiddle 抓包查看详情:
可以发现,请求http://localhost:8080/redirect
,最后出现了两次请求结果。
当请求/redirect
的时候,服务器会告诉浏览器去请求/index.html
:
执行阶段:
在服务器端完成的,客户端浏览器不感知
。请求从一个 Servlet 转发到另一个 Servlet 或 JSP 页面,整个过程是在服务器内部完成的。涉及两次请求和响应过程
。客户端接收到第一个请求的响应后,会根据响应头部的 Location
字段再发送一个新的请求。地址栏变化:
不会改变
浏览器的地址栏内容,地址栏中仍然是原始请求的URL。会改变
浏览器的地址栏内容,地址栏中会显示重定向后的URL。请求属性传递:
选择使用请求转发还是请求重定向取决于具体的需求。请求转发适用于在服务器内部实现页面跳转、共享请求属性等情况。而请求重定向适用于需要修改浏览器地址栏、避免表单重复提交、刷新页面等情况。