一、三层结构与MVC

1.三层结构与MVC关系架构图

image-20240703153035031

二、SpringMVC的优势

  1. 灵活性: Spring MVC提供了灵活的配置选项,开发人员可以根据项目需求进行定制,而不受限于框架的约束。这种灵活性使得开发人员能够更好地控制应用程序的行为。

  2. 松耦合: Spring MVC采用了MVC架构模式,将应用程序分为模型、视图和控制器三个部分,使得它们之间的耦合度降低。这种松耦合性使得代码更易于维护、扩展和测试。

  3. 强大的验证支持: Spring MVC提供了强大的数据验证支持,可以轻松地对用户输入数据进行验证和处理,从而提高应用程序的安全性和稳定性。

  4. 集成性: Spring MVC可以与其他Spring框架模块(如Spring Core、Spring Security等)无缝集成,同时也可以与其他流行的框架和技术(如Hibernate、MyBatis、RESTful Web Services等)集成,为开发人员提供更多选择。

  5. 易于测试: Spring MVC框架支持单元测试和集成测试,开发人员可以轻松地编写和运行测试用例,确保应用程序的质量和稳定性。

  6. 丰富的功能: Spring MVC提供了许多功能,如请求映射、数据绑定、数据转换、国际化支持等,使得开发人员能够快速构建功能丰富的Web应用程序。

  7. 社区支持: Spring Framework拥有庞大的社区和活跃的开发团队,开发人员可以从社区中获取支持、解决问题,并分享经验和最佳实践。

三、SpringMVC处理流程

一图胜千言

image-20240703165611046

四、常用注解

1. RequestParam

作用:把请求中指定名称的参数给控制器中的形参赋值。

属性:

​ value:请求参数中的名称。

​ required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

1
2
3
4
5
@RequestMapping("/useRequestParam")
public String useRequestParam(@RequestParam("name")Stringusername, @RequestParam(value="age",required=false)Integer age){
System.out.println(username+","+age);
return "success";
}

2.RequestBody

作用:

​ 用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。

get请求方式不适用。

属性:

​ required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值

为 false,get请求得到是 null。

1
2
3
4
5
@RequestMapping("/useRequestBody")
public String useRequestBody(@RequestBody(required=false) String body){
System.out.println(body);
return "success";
}

3.PathVaribale

作用:

​ 用于绑定 url中的占位符。例如:请求 url中 /delete/**{id},这个{id}**就是 url占位符。

url支持占位符是 spring3.0之后加入的。是 springmvc支持 rest风格 URL的一个重要标志。

属性:

​ value:用于指定 url中占位符名称。

​ required:是否必须提供占位符。

1
2
3
4
5
@RequestMapping("/usePathVariable/{id}")
public String usePathVariable(@PathVariable("id") Integer id){
System.out.println(id);
return "success";
}

4.RequestHeader

作用:

​ 用于获取请求消息头。

属性:

​ value:提供消息头名称

​ required:是否必须有此消息头

1
2
3
4
5
6
@RequestMapping("/useRequestHeader")
public String useRequestHeader(@RequestHeader(value="Accept-Language",
required=false)String requestHeader){
System.out.println(requestHeader);
return "success";
}

5.CookieValue

作用:

​ 用于把指定 cookie名称的值传入控制器方法参数。

属性:

​ value:指定 cookie的名称。

​ required:是否必须有此 cookie。

1
2
3
4
5
6
@RequestMapping("/useCookieValue")
public String useCookieValue(@CookieValue(value="JSESSIONID",required=false)
String cookieValue){
System.out.println(cookieValue);
return "success";
}

6.ModelAttribute

作用:

​ 通过使用@ModelAttribute注解,可以简化数据绑定、模型数据传递和表单数据处理过程,提高开发效率。

属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ModelAttribute
public User getDefaultUser() {
User user = new User();
user.setUsername("Guest");
user.setEmail("guest@example.com");
return user;
}

@GetMapping("/user")
public String getUserProfile(@ModelAttribute("user") User user) {
// 这里可以访问存储在模型中的User对象
return "userProfile";
}

@PostMapping("/updateUser")
public String updateUserProfile(@ModelAttribute("user") User user) {
// 处理用户提交的表单数据
// 这里的User对象会自动绑定表单数据
return "userProfile";
}

7.SessionAttribute

作用:

​ 在Spring MVC中,@SessionAttributes注解用于将模型中的特定属性存储到会话(Session)中,以便它们在多个请求之间共享。这样可以在不同请求之间保持特定的模型属性,通常用于在多个请求之间传递数据或在整个会话期间保持数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Controller
@SessionAttributes("user")
public class UserController {

@ModelAttribute("user")
public User getDefaultUser() {
User user = new User();
user.setUsername("Guest");
user.setEmail("guest@example.com");
return user;
}

@GetMapping("/user")
public String getUserProfile() {
return "userProfile";
}

@PostMapping("/updateUser")
public String updateUserProfile(@ModelAttribute("user") User user) {
// 处理用户提交的表单数据
return "userProfile";
}

@GetMapping("/otherPage")
public String getOtherPage(@ModelAttribute("user") User user) {
// 在其他请求中也可以访问存储在会话中的User对象
return "otherPage";
}
}

在上面的示例中:

  • @SessionAttributes("user")注解指定了需要存储在会话中的模型属性名为”user”。
  • getDefaultUser()方法使用@ModelAttribute("user")注解,返回一个默认的User对象,并将其存储在模型中。由于”user”属性被标记为会话属性,因此它会自动存储到会话中。
  • getUserProfile()方法用于显示用户信息页面,不接收任何参数。
  • updateUserProfile()方法接收一个User对象作为参数,用于处理用户提交的表单数据。由于”user”属性被标记为会话属性,因此在这个方法中可以直接访问这个User对象。
  • getOtherPage()方法演示了在其他请求中也可以访问存储在会话中的User对象。

通过使用@SessionAttributes注解,可以在不同请求之间共享特定的模型属性,从而实现数据在会话期间的共享。

五、响应数据和结果视图

1.返回值类型

1.1 字符串

1
2
3
4
5
@RequestMapping("/testReturnString")
public String testReturnString() {
System.out.println("AccountController的 testReturnString 方法执行了。。。。");
return "success";
}

1.2 void

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/testReturnVoid")
public void testReturnVoid(HttpServletRequest request,HttpServletResponse response)
throws Exception {
// 1、使用 request转向页面
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,
response);
// 2、也可以通过 response页面重定向
response.sendRedirect("testRetrunString")
// 3、也可以通过 response指定响应结果,例如响应 json数据
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json 串");
}

1.3 ModelAndView

1
2
3
4
5
6
7
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "张三");
mv.setViewName("success");
return mv;
}

2.转发和重定向

请求转发(Forward):

  1. 服务器内部跳转: 请求转发是服务器内部的跳转方式,所有操作都在服务器端完成,客户端浏览器不知道页面发生了跳转。

  2. 单次请求: 在请求转发过程中,浏览器发出一次请求,服务器接收到请求后将请求转发到另一个资源,最终响应给客户端的仍然是原始请求的结果。

  3. 共享Request域: 在请求转发过程中,原始请求和目标资源之间可以共享Request域中的数据,因为它们属于同一个请求。

  4. 地址栏不变: 由于请求转发是服务器内部跳转,因此浏览器的地址栏不会发生变化,仍然显示原始请求的URL。

重定向(Redirect):

  1. 客户端跳转: 重定向是一种客户端跳转方式,服务器端返回一个特殊的响应码(如302),告诉浏览器需要跳转到另一个URL。

  2. 两次请求: 在重定向过程中,浏览器会收到服务器返回的重定向响应,并发起一个新的请求到指定的URL,这样会导致两次请求-响应周期。

  3. 不共享Request域: 重定向会导致两次请求-响应周期,因此原始请求和重定向目标之间无法共享Request域中的数据。

  4. 地址栏变化: 由于重定向是客户端跳转,浏览器会显示新的URL地址,因此地址栏会发生变化。

选择使用场景:

  • 请求转发适用于:在同一个应用程序内部进行页面跳转,共享Request域中的数据,且不希望用户看到跳转后的URL。

  • 重定向适用于:需要跳转到不同应用程序、不同域名下或者希望用户能够看到跳转后的URL的情况。

2.1 forward转发

1
2
3
4
5
6
7
8
@RequestMapping("/testForward")
public String testForward() {
System.out.println("AccountController的 testForward 方法执行了。。。。");
// 转发到jsp
return "forward:/WEB-INF/pages/success.jsp";
// 转发到控制器
return "forward:success";
}

需要注意的是,如果用了 formward:则路径必须写成实际视图 url,不能写逻辑视图。

它相当于“request.getRequestDispatcher(“url“).forward(request,response)”。

使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

2.2 Redirec重定向

1
2
3
4
5
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("AccountController的 testRedirect 方法执行了。。。。");
return "redirect:success";
}

它相当于“response.sendRedirect(url)”。

需要注意的是,WEB-INF目录中的jsp页面,重定向无法找到;需要先写个返回WEB-INF目录中的jsp页面的控制器,重定向时再返回此控制器。

3.ResponseBody响应json数据

1
2
3
4
5
6
7
8
9
10
@Controller("jsonController")
public class JsonController {

@RequestMapping("/testResponseJson")
public @ResponseBody Account testResponseJson(@RequestBody Account account) {
System.out.println("异步请求:"+account);
return account;
}

}

六、文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {

@RequestMapping(value = "/upload", method = RequestMethod.GET)
public String showUploadForm() {
return "upload";
}

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
String uploadsDir = "/uploads/";
String realPathtoUploads = request.getServletContext().getRealPath(uploadsDir);
if (!new File(realPathtoUploads).exists()) {
new File(realPathtoUploads).mkdir();
}

String orgName = file.getOriginalFilename();
String filePath = realPathtoUploads + orgName;
File dest = new File(filePath);
file.transferTo(dest);
return "uploadSuccess";
} catch (IOException e) {
return "uploadFailure";
}
} else {
return "uploadFailure";
}
}
}

在上面的示例中,我们创建了一个 FileUploadController 类来处理文件上传请求。showUploadForm 方法用于显示上传表单,handleFileUpload 方法用于处理文件上传请求。在 handleFileUpload 方法中,我们首先检查文件是否为空,然后将文件保存到服务器上的指定位置。

七、异常处理

1.异常处理流程图

image-20240703163303436

2.自定义异常处理器

2.1 创建一个自定义异常类,例如CustomException

1
2
3
public class CustomException extends RuntimeException {
// 添加自定义异常信息
}

2.2 创建一个实现HandlerExceptionResolver接口的异常处理器类,例如CustomExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CustomExceptionHandler implements HandlerExceptionResolver {

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 处理自定义异常
if (ex instanceof CustomException) {
// 在这里编写自定义异常处理逻辑,可以返回一个包含自定义异常信息的ModelAndView对象
ModelAndView modelAndView = new ModelAndView("errorPage");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}

// 返回null表示不处理该异常,交给其他异常处理器处理
return null;
}
}

2.3 配置自定义异常处理器到Spring MVC配置文件中(例如dispatcher-servlet.xml):

1
<bean class="com.example.CustomExceptionHandler" />

2.4 创建一个错误页面(例如errorPage.jsp),用来显示异常信息:

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>Error Page</title>
</head>
<body>
<h1>Error</h1>
<p>${errorMessage}</p>
</body>
</html>

八、拦截器

实现HandlerInterceptor接口来自定义拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class CustomInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前进行调用
// 返回true,则继续向下执行,返回false,则取消当前请求
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求结束之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行(主要用于进行资源清理工作)
}
}

将这个拦截器注册到Spring MVC配置中,可以通过Java配置或XML配置来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns("/*") 表示拦截所有的请求,也可以根据需要指定特定的路径。
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/*");
}
}