SpringMVC入门
1.1 SpringMVC简介
Spring MVC 全称:Spring Web MVC是 Spring 框架的一部分,专注于实现 Web 应用程序的模型-视图-控制器(Model-View-Controller, MVC)设计模式。
1.2 SpringMVC特点
- Spring 家族原生产品,与IOC容器等基础设施无缝对接
- 异常处理机制
- 使用
@ExceptionHandler注解可以定义全局异常处理器,集中处理不同类型的异常,简化错误页面展示和消息反馈。 - 支持自定义错误页面和 HTTP 状态码返回。
- 使用
- 强大的 RESTful 支持
- 内置对 RESTful Web 服务的支持,使用
@RestController和@RequestMapping注解可以轻松创建 RESTful API。 - 支持多种数据格式(如 JSON、XML),并能够自动将对象序列化为响应体内容。
- 内置对 RESTful Web 服务的支持,使用
- AOP 集成 - 与 Spring AOP 集成良好,允许开发者添加横切关注点(如日志记录、事务管理)而不影响业务逻辑代码。 - 可以通过拦截器(Interceptor)在请求到达控制器之前或响应发送给客户端之后执行某些操作。
1.3 核心组件及调用流程
1.3.1 SpringMVC核心组件
-
DispatcherServlet
- 功能:作为前端控制器,
DispatcherServlet是整个 Spring MVC 请求处理的核心。它接收所有的 HTTP 请求,并将它们分发给适当的处理器(Handler)。 - 作用:统一入口点,负责初始化 WebApplicationContext 和其他必要组件。
- 功能:作为前端控制器,
-
HandlerMapping (处理器映射)
- 功能:定义了一组规则来决定哪个处理器应该处理特定的请求。它可以基于 URL、HTTP 方法、参数等进行匹配。
- 作用:将请求映射到具体的处理器上,如 Controller 中的方法。
-
HandlerAdapter (处理器适配器)
- 功能:适配器接口,用于执行由
HandlerMapping映射到的处理器。它负责调用处理器的方法并处理其结果。 - 作用:使不同类型的处理器可以被一致地调用,增加了灵活性。
- 功能:适配器接口,用于执行由
-
ViewResolver (视图解析器)
- 功能:根据逻辑视图名找到实际的视图资源(如 JSP 文件或 Thymeleaf 模板),然后由
DispatcherServlet将模型数据传递给视图进行渲染。 - 作用:解耦视图名称和物理位置之间的关系,便于维护和扩展。
- 功能:根据逻辑视图名找到实际的视图资源(如 JSP 文件或 Thymeleaf 模板),然后由
-
Controller (控制器)
- 功能:接收来自
DispatcherServlet的请求,并调用相应的业务逻辑。通常是一个带有@Controller或@RestController注解的类。 - 作用:处理用户输入,准备数据,并选择一个合适的视图来展示结果。
- 功能:接收来自
-
ModelAndView (模型和视图)
- 功能:包含模型数据和逻辑视图名,由控制器返回给
DispatcherServlet用于渲染视图。在 RESTful API 中,通常只返回模型数据(即实体对象)。 - 作用:连接了业务逻辑层与表示层,保证数据能够正确传递给视图。
- 功能:包含模型数据和逻辑视图名,由控制器返回给
-
view
- 功能:
View接口表示一个逻辑视图,它负责将模型(Model)中的数据转换为最终的用户界面,如 HTML 页面、JSON 响应等。 - 作用:
View主要用于数据转换及数据渲染,根据传入的数据,使用模板引擎(如 JSP、Thymeleaf、FreeMarker 等)或直接生成响应内容,最终形成用户能够看到的页面。
- 功能:
-
interceptor(拦截器)
- 功能:
Interceptor(拦截器)是一个非常有用的组件,它允许开发者在请求到达控制器之前或响应发送给客户端之后执行某些操作。 - 作用:拦截器可以用来实现诸如日志记录、权限检查、性能监控、国际化设置等功能,而无需修改业务逻辑代码。
- 功能:
-
ExceptionHandler (异常处理器)
- 功能:用于全局捕获和处理控制器抛出的异常。可以通过
@ExceptionHandler注解定义。 - 作用:提供一种集中处理错误的方式,简化了代码结构,提高了可读性和维护性。
- 功能:用于全局捕获和处理控制器抛出的异常。可以通过
1.3.2 SpringMVC组件调用关系
1.4 SpringMVC之Helloworld(入门体验)
1.4.1 环境准备
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.4.2 定义请求处理器Handler(Controller)
package com.mytest.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller("helloController") //定义请求处理器
public class HelloController {
//为当前方法映射URL(/hello)
@RequestMapping("/hello")
//设置响应体
@ResponseBody
public String hello(){
return "hello springMVC!!!";
}
}
1.4.3 启动及测试项目
- 启动SpringBoot项目
-
测试项目
- springBoot内置Tomcat默认端口号:8080
- springBoot项目默认上下文路径:/
- http://localhost:8080/hello
1.5 @RequestMapping详解
@RequestMapping是 Spring MVC 中用于映射 Web 请求到处理方法的注解。它可以应用于类或方法级别,用来指定 URL 模式、HTTP 方法类型等条件。
1.5.1 @RequestMapping位置
-
类级别:为当前类映射URL(不能单独使用)
@Controller @RequestMapping("/users") public class UserController { // ... } -
方法级别:为当前方法映射URL(可以单独使用)
@RequestMapping(value = "/getUser", method = RequestMethod.GET) @ResponseBody public String getUser() { // 返回字符串 return "success"; }
1.5.2 @RequestMapping路径匹配
-
精确匹配
@RequestMapping("/hello") @RequestMapping("/helloController/hello") -
模糊匹配
?:任意单个字符
*:任意数量任意字符(单层)
**:代表任意层(目录)的任意数量的任意字符
@RequestMapping("/hello/?") @RequestMapping("/helloController/*") @RequestMapping("/helloController/**")
1.5.3 @RequestMapping常用属性
- value:指定请求的 URL 模式。可以是绝对路径(如
/users)或者相对路径(相对于父级路径)。如果在类上使用,则该路径会作为子路径添加到所有方法级别的路径前。 - method:指定允许的 HTTP 方法(GET, POST, PUT, DELETE 等)。默认情况下,不设置此属性意味着该方法可以响应任何 HTTP 方法。注意:如违背请求方式,会报错405
- params:限定只有当特定的请求参数存在时,才匹配该请求。例如
params = "id"表示 URL 中必须包含名为id的参数。 - headers:限定只有当特定的请求头存在时,才匹配该请求。例如
headers = "Referer=http://example.com/"。 - consumes:限定只有当请求的内容类型与给定值匹配时,才匹配该请求。例如
consumes="application/json"。 - produces:限定只有当响应的内容类型与给定值匹配时,才匹配该请求。例如
produces="application/json"表示该方法将生成 JSON 格式的响应。
1.5.4 @RequestMapping四大组合注解
Spring 还提供了一些更具体的注解,它们是
@RequestMapping的简化版本,用于直接映射特定类型的 HTTP 请求:
@GetMapping:组合了@RequestMapping(method = RequestMethod.GET)。@PostMapping:组合了@RequestMapping(method = RequestMethod.POST)。@PutMapping:组合了@RequestMapping(method = RequestMethod.PUT)。@DeleteMapping:组合了@RequestMapping(method = RequestMethod.DELETE)。@PatchMapping:组合了@RequestMapping(method = RequestMethod.PATCH)。
SpringMVC处理请求数据
2.1 接收查询&请求体参数
2.1.1 默认情况:参数名与形参名一致
springmvc中形参列表中可直接接收请求参数
- 参数名与形参列表中参数名必须一致
- 如不一致:默认值null
- Apipost工具发送请求
- 查询参数也叫query参数
- 请求体参数:必须使用POST方式
-
Controller接收数据
//springmvc处理请求参数(默认) @ResponseBody @GetMapping("/doRequestParameter") public String doRequestParameter(String stuName,Integer stuAge){ System.out.println("stuName = " + stuName); System.out.println("stuAge = " + stuAge); return "success"; }
2.1.2 @RequestParam注解
参数名与形参名不一致时,可以使用@RequestParam注解获取请求参数
@RequestParam是 Spring MVC 中用于将 HTTP 请求中的查询参数或表单字段绑定到控制器方法的参数上的注解。它使得从请求中提取参数并传递给处理方法变得非常简单和直观。下面是对@RequestParam注解的常用属性
- value:
- 指定请求参数的名称,必须与 URL 或表单中的参数名相匹配。
- 如果省略此属性,则默认使用方法参数的名字作为参数名。
- required(默认为
true):
- 表示该参数是否必需。如果设置为
true并且请求中没有提供相应的参数,Spring 将抛出MissingServletRequestParameterException异常。- 设置为
false时,即使请求中缺少该参数,也不会报错,参数值将被设为null或者是基本类型的默认值(如0对于整数类型)。- defaultValue:
- 当请求中未包含指定参数时使用的默认值。这可以避免因缺少参数而导致的异常,并为参数提供一个备用值。
- 注意:当设置了
defaultValue时,required属性自动视为false,因为有了默认值,参数就不再是必需的了。- name:
- 这是一个别名属性,等价于
value,用于指定参数名称。
- ApiPost发送请求
-
Controller接收数据
//springmvc处理请求参数(默认) @ResponseBody @GetMapping("/doRequestParameter") public String doRequestParameter(String stuName, @RequestParam(value = "stuAge2",required = false) Integer stuAge){ System.out.println("stuName = " + stuName); System.out.println("stuAge = " + stuAge); return "success"; }
2.1.3 POJO入参
SpringMVC中支持POJO入参,将POJO对象设置为形参即可
- 使用POJO入参,要求参数名与POJO中的属性名必须一致
- 如不一致会注入null值
- ApiPost发送请求
-
Controller接收数据
package com.mytest.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Student { private Integer stuId; private String stuName; private Integer stuAge; private String[] hobbys; } //测试pojo入参 @ResponseBody @PostMapping("/testPojoParam") public String testPojoParam(Student student){ System.out.println("student = " + student); return "success"; }
2.2 接收路径参数(@PathVariable)
SpringMVC中支持在URL中使用占位符入参,一般在后续RESTFul风格CRUD中使用,语法如下:
- 占位符语法:{参数名}
- @PathVariable("参数名")
- ApiPost发送请求
- Controller接收数据
//测试URL参数
@ResponseBody
@GetMapping("/testURLParam/{stuId}")
public String testURLParam(@PathVariable("stuId") Integer stuId){
System.out.println("stuId = " + stuId);
return "success";
}
2.3 接收JSON参数
SpringMVC中使用@RequestBody注解接收Json数据
- 使用String类型接收:获取的是Json类型的字符串
- 使用POJO类型接收:获取的是POJO对象
-
Controller接收数据
//测试pojo入参 @ResponseBody @PostMapping("/testJSONParam") public String testJSONParam(@RequestBody Student student /*String jsonStr*/){ // System.out.println("jsonStr = " + jsonStr); //json字符串 System.out.println("student = " + student); return "success"; }
2.4 接收Cookie数据
SpringMVC中使用@CookieValue注解获取Cookie数据
- ApiPost发送请求
-
Controller接收数据
//测试Cookie信息 @ResponseBody @GetMapping("/testCookieInfo") public String testCookieInfo(@CookieValue("JSESSIONID") String jsessionId){ System.out.println("jsessionId = " + jsessionId); return "success"; }
2.5 接收请求头数据
SpringMVC中使用@RequestHeader注解接收请求头信息
- ApiPost发送请求
-
Controller接收数据
//测试Cookie信息 @ResponseBody @GetMapping("/testRequestHeaderInfo") public String testRequestHeaderInfo(@RequestHeader("User-Agent") String userAgent){ System.out.println("User-Agent = " + userAgent); return "success"; }
2.6 接收文件数据(文件上传)
SpringBoot环境中,自带文件上传处理器,也可以在application.properties配置文件中,设置文件上传大小和数量等问题
spring.application.name=spring09-mvc # 总文件大小限定 spring.servlet.multipart.max-request-size=200MB # 单文件大小限定 spring.servlet.multipart.max-file-size=20MB # 文件数量限定 spring.webflux.multipart.max-parts=10
-
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 文件上传表单要求 1 method必须是Post 2 enctype必须是multipart/form-data 3 springmvc默认限定,单个文件大小为1M以内,总文件大小是10M以内 --> <form method="post" enctype="multipart/form-data" action="file/upload"> 用户名 <input type="text" name="username"> <br> 头像 <input type="file" name="headImg"> <br> <!--单文件输入框--> 生活照 <input type="file" name="lifeImg" multiple="multiple"> <br> <!--多文件输入框--> <input type="submit"> </form> </body> </html> -
或ApiPost工具测试
-
Controller处理文件上传
package com.mytest.spring09mvc.controller; import org.springframework.util.ResourceUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.util.UUID; @RequestMapping("/file") @RestController public class FileController { @RequestMapping("/upload") public String upload( @RequestParam("username") String username , @RequestParam("headImg") MultipartFile headImg, @RequestParam("lifeImg") MultipartFile[] lifeImg) throws Exception { // 获取文件信息 /* System.out.println(headImg.getName()); // 文件参数名 System.out.println(headImg.getOriginalFilename()); // 文件原始名 System.out.println(headImg.getContentType()); // 文件媒体类型 System.out.println(headImg.getSize()); // 文件大小 System.out.println(headImg.getBytes().length); // 文件字节数组 System.out.println(headImg.isEmpty()); // 文件是否为空 */ // 文件名处理 String originalFilename = headImg.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFileName = UUID.randomUUID().toString() + suffix; // 保存到指定目录 File classPath = ResourceUtils.getFile("classpath:static/");// 获取静态资源目录 File dir = new File(classPath, "upload"); if (!dir.exists()) { dir.mkdirs(); } headImg.transferTo(new File(dir, newFileName)); return "success"; } }
2.7 原生ServletAPI入参
SpringMVC中支持原生ServletAPI,直接作为形参入参即可
//测试Cookie信息
@ResponseBody
@PostMapping("/testServletAPIInfo")
public String testServletAPIInfo(HttpServletRequest request, HttpServletResponse response){
HttpSession session = request.getSession();
String stuId = request.getParameter("stuId");
System.out.println("stuId = " + stuId);
String stuName = request.getParameter("stuName");
System.out.println("stuName = " + stuName);
return "success";
}
2.8 接收HttpEntity 获取所有请求信息(了解)
SpringMVC中可以使用HttpEntity获取请求信息,包括请求头,请求体等
package com.mytest.spring09mvc.controller;
import com.mytest.spring09mvc.pojo.Product;
import org.springframework.http.HttpEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/httpEntity")
@RestController
public class HttpEntityController {
@RequestMapping("/test1")
public String test1(HttpEntity<Product> entity) {
System.out.println(entity.getHeaders());
System.out.println(entity.getBody());
return "test1";
}
}
SpringMVC处理响应数据
3.1 响应数据
SpringMVC中使用@ResponseBody注解响应数据,包括响应普通文本或Json数据,如响应过程中未添加@ResponseBody注解,SpringMVC会寻找视图解析器.@ResponseBody具体源码如下:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseBody {}
- @ResponseBody可以书写的位置
- 方法上:表示当前方法响应数据
- 类上:表示当前类中所有方法响应数据
- 注意:当前类中所有方法都响应数据时,使用@RestController
- @RestController = @Controller + @ResponseBody
3.1.1 普通文本
@ResponseBody
@RequestMapping("/testJsonData")
public String doJsonDataString(){
return "success";
}
3.1.2 Json数据
@ResponseBody
@RequestMapping("/testJsonData")
public Student /*String*/ doJsonDataString(){
Student student = new Student();
student.setStuId(1005);
student.setStuName("wangwu");
student.setStuAge(18);
student.setHobbys(new String[]{"打游戏","看电影"});
return student;
// 响应json数据(json字符串)
//String jsonStr = "{\"name\":\"zhangsan\",\"age\":18}";
//return jsonStr;
}
3.2 响应静态资源
3.2.1 转发与重定向(html)
-
转发
@RequestMapping("/testStaticResource") public String doStaticResource(){ System.out.println(" 转发======= " ); return "/html/index.html"; //默认跳转路径方式:转发 //return "forward:/html/index.html" } -
重定向
@RequestMapping("/testStaticResource") public String doStaticResource(){ System.out.println(" 重定向======= " ); return "redirect:/html/index.html"; //重定向 }
3.2.2 其他资源(了解)
@RequestMapping("/testStaticResource")
public String doStaticResource(){
System.out.println(" 其他静态资源 ======= " );
return "/imgs/a.jpg";
}
3.3 响应文件(文件下载)
-
html代码
<a href="fileController/filedownload?fileName=3.jpg">3.jpg下载</a> -
Controller下载
//实现文件下载,返回ResponseEntity
@RequestMapping("/filedownload")
public ResponseEntity<byte[]> filedownload(String fileName) throws Exception {
File staticDir = ResourceUtils.getFile("classpath:static/path/"+fileName);
InputStream inputStream = new FileInputStream(staticDir);
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
//响应头[设置响应头类,通知浏览器该文件需要下载,不要打开]
// ①创建MultiValueMap接口类型的对象,实现类是HttpHeaders
MultiValueMap responseHeaderMap = new HttpHeaders();
// ②存入下载文件所需要的响应消息头
responseHeaderMap.add("Content-Disposition", "attachment; filename="+fileName);
ResponseEntity responseEntity = new ResponseEntity(bytes,responseHeaderMap, HttpStatus.OK);
return responseEntity;
}
3.4 跨域问题
3.4.1 跨域问题概述
-
什么是跨域
跨域指的是客户端脚本(如 JavaScript)尝试访问与当前页面不在同一个域下的资源。这里的“域”指的是协议、主机名和端口号的组合。例如:
http://example.com和https://example.com是不同域(因为协议不同)。http://example.com:8080和http://example.com是不同域(因为端口不同)。http://example.com和http://sub.example.com是不同域(因为主机名不同)。
-
跨域的原因
跨域问题的存在主要是为了防止 CSRF(跨站请求伪造)攻击和其他潜在的安全风险。浏览器实施了同源策略(Same-Origin Policy),这是一种安全机制,它阻止了一个源加载的文档或脚本对另一个源加载的资源进行某些类型的访问。
3.4.2 跨域问题解决方案
- CORS (Cross-Origin Resource Sharing)
CORS 是一种基于 HTTP 头的机制,它允许服务器声明哪些源可以访问它的资源。通过设置适当的响应头,服务器可以指示浏览器是否允许跨域请求。
- JSONP (JSON with Padding)
JSONP 是一种早期用于绕过跨域限制的技术,适用于只读操作(GET 请求)。它的工作原理是在客户端动态创建
<script>标签,并通过 URL 参数传递回调函数名称。服务器返回的数据会包裹在这个回调函数中,从而执行客户端定义的代码。然而,由于 JSONP 只支持 GET 请求且存在安全风险,现代应用更倾向于使用 CORS 或其他解决方案。
- 代理服务器
通过在同一个域下设置一个代理服务器来转发请求到目标服务器,这样客户端就可以直接与代理服务器通信而不会违反同源策略。这种方法适用于无法修改目标服务器配置的情况。
- WebSocket
对于实时通信的需求,WebSocket 提供了一种全双工通信协议,它不受同源策略的限制。因此,即使在不同域之间也可以建立 WebSocket 连接。
- Nginx 配置
如果你使用 Nginx 作为反向代理,可以在 Nginx 配置文件中添加 CORS 相关的头信息来处理跨域请求。
-
配置类方式(了解)
import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("https://example.com") // 允许的来源 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法 .allowedHeaders("*") // 允许的头部 .allowCredentials(true); // 是否允许发送凭证 } } -
注解方式:@CrossOrigin
@CrossOrigin是 Spring 框架提供的一个注解,专门用于简化跨域资源共享(CORS, Cross-Origin Resource Sharing)的配置。它可以在类级别或方法级别使用,以允许来自不同源的请求访问受保护的资源。以下是@CrossOrigin注解的常用属性:
- origins:
- 指定允许访问的来源域名,默认值是
"*", 表示允许所有来源。 - 可以是一个字符串数组,例如
{"https://example.com", "https://anotherdomain.com"}。
- methods:
- 指定允许的 HTTP 方法,默认是所有方法 (
RequestMethod.GET,RequestMethod.POST, 等)。 - 可以指定具体的 HTTP 方法,如
RequestMethod.GET或RequestMethod.POST。
- allowedHeaders:
- 指定允许的请求头字段,默认是所有头字段。
- 例如,
{"X-Requested-With", "Content-Type"}。
- exposedHeaders:
- 指定哪些响应头可以被客户端访问,默认为空列表。
- 这对于某些需要暴露给前端的自定义响应头非常有用。
- allowCredentials:
- 是否允许发送凭证(如 Cookies),默认是
false。 - 如果设置为
true,则origins不能为"*",必须指定具体来源。
- maxAge:
-
设置预检请求(preflight request)的结果缓存时间(秒),默认是
1800秒(30 分钟)。 -
预检请求是指浏览器在发送实际请求之前发出的 OPTIONS 请求,用以确认服务器是否允许跨域请求。
-
类级别应用
@CrossOrigin(origins = "https://example.com") @RestController @RequestMapping("/api") public class MyController { // 所有该类中的方法都将允许来自 https://example.com 的请求 } -
方法级别应用
@RestController @RequestMapping("/api") public class MyController { @CrossOrigin(origins = "https://example.com", methods = {RequestMethod.GET, RequestMethod.POST}) @GetMapping("/data") public ResponseEntity<String> getData() { return new ResponseEntity<>("Some data", HttpStatus.OK); } // 其他方法可能有不同的 CORS 设置或没有 CORS 设置 }