文章目录
一、SpringMVC 介绍
面向表述成controller的框架
原生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";
}
1.1 主要作用
SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化!
SpringMVC的作用主要覆盖的是表述层,例如:
- 请求映射
- 数据输入
- 视图界面
- 请求分发
- 表单回显
- 会话控制
- 过滤拦截
- 异步交互
- 文件上传
- 文件下载
- 数据校验
- 类型转换
- 等等等
最终总结:
- 简化前端参数接收( 形参列表 )
- 简化后端数据响应(返回值)
- 以及其他…
1.2 核心组件和调用流程理解
DispatcherServlet
做整体请求处理调度。以及其他组件辅助对请求和响应的处理呈现。
SpringMVC处理请求流程:
SpringMVC涉及组件理解:
DispatcherServlet
: SpringMVC提供,我们需要使用web.xml
配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发!HandlerMapping
: SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet
调用,用于查找路径对应的handler!HandlerAdapter
: SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和响应数据,每次DispatcherServlet
都是通过handlerAdapter
间接调用handler,他是handler和DispatcherServlet之间的适配器!Handler
: handler又称处理器,他是Controller
类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果!ViewResovler
: SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的!
二、快速体验
- 需求:
- 配置分析
DispatcherServlet
: 设置处理所有请求!HandlerMapping,HandlerAdapter,Handler
需要加入到IoC容器,供DispatcherServlet调用!Handler
自己声明(Controller)需要配置到HandlerMapping
中供DispatcherServlet查找!
- 准备项目
- 创建项目
springmvc-base-quick - 导入依赖
web -> servlet
ioc -> spring-context
mvc -> spring-webmvc
<properties>
<spring.version>6.0.6</spring.version>
<servlet.api>9.1.0</servlet.api>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- springioc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- web相关依赖 -->
<!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
<!--
在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’
is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的
Servlet API,没有更新到 Jakarta EE 规范。
-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>${servlet.api}</version>
<scope>provided</scope>
</dependency>
<!-- springwebmvc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
-
模块转为maven/web
-
controller 声明
/**
* @Description: handler -> springmvc/hello return "hello springmvc";
*/
@Controller
public class HelloController {
// 对外访问的地址 到handlerMapping注册的注解
@RequestMapping("springmvc/hello")
// 直接返回字符串给前端,不用找视图解析器
@ResponseBody
public String hello(){
System.out.println("HelloController.hello");
// 返回给前端
return "hello springmvc";
}
}
- SpringMVC 核心组件配置类
/**
* @Description:
* 1. controller 配置IOC容器
* 2. handlerMapping handlerAdapter 加入到IOC容器
*/
@Configuration
@ComponentScan("com.wake.controller")
public class MvcConfig {
@Bean
public RequestMappingHandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter handlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
- SpringMVC 环境搭建
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @Description: 可以被web项目加载,会初始化IOC容器,会设置dispatcherServlet的地址
*/
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 设置SpringMVC对应的配置
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
/**
* 配置SpringMVC内部自带servlet的访问地址
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
- 测试
- 配置tomcat 启动项目
- 配置tomcat 启动项目
三、SpringMVC接收数据
3.1 访问路径设置
- 原生servlet注解:
- @WebServlet(“必须使用 / 打头”)
- springmvc 注解:
- @RequestMapping(“不必须使用 / 打头”)
- user/login 或 /user/login
3.1.1 精准路径匹配
精准地址: /user/login
@Controller
public class UserController {
@RequestMapping
public void general(){
}
@RequestMapping("user/login")
@ResponseBody
public String login(){
return "login";
}
@RequestMapping("user/register")
@ResponseBody
public String register(){
return "register";
}
}
3.1.2 模糊路径匹配
*
代表任意一层字符串- /user/*
- /user/a
- user/b
- …
- 不能是 /user/a/b
- 如果想准确匹配两层,那么就写“
/*/*
”以此类推
- /user/*
**
代表任意层任意字符串- /user/**
- user
- user/a
- /user/a/b
- user/a/b/c/d
- …
- /user/**
@RequestMapping("/user/*")
public String general(){
return "show";
}
3.1.3 类和方法级别区别
- 关于 类上和方法上添加 @RequestMapper
- 类上提取通用的访问地址
- 方法上是具体的handler地址
- 访问 : 类地址 + 方法地址
@Controller
@RequestMapping("/user")
public class UserController {
// 不写默认是直接使用 /user 地址
@RequestMapping
public String general(){
return "show";
}
@RequestMapping("/login")
@ResponseBody
public String login(){
return "login";
}
@RequestMapping("/register")
@ResponseBody
public String register(){
return "register";
}
}
3.1.4 附带请求方式限制
客户端 -> http(get | post | put | delete ) -> ds -> handler
HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
默认情况下:@RequestMapping("/login")
地址正确,任何请求方式都可以访问。
如果需要特定指定:
method = RequestMethod.POST
method = {RequestMethod.POST,RequestMethod.GET}
@Controller
@RequestMapping("/user")
public class UserController {
//多个用 {}
@RequestMapping(method = {RequestMethod.POST,RequestMethod.GET})
public String general(){
return "show";
}
// 单个
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public String login(){
return "login";
}
// 直接用注解
@GetMapping("/register")
@ResponseBody
public String register(){
return "register";
}
}
不符合的请求方式:会出现 405
异常!
3.1.5 进阶注解 与 常见配置问题
@RequestMapping
的 HTTP 方法特定快捷方式变体:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
注意:
进阶注解只能用在handler方法上!不能添加到类上!
@RequestMapping("/login",method=RequestMethod.GET)
||
@GetMapping("/login")
报错:
There is already ‘demo03MappingMethodHandler’ bean method com.wake.mvc.handler.Demo03MappingMethodHandler#empGet() mapped.
原因:
多个 handler 方法映射了同一个地址,导致 SpringMVC 在接收到这个地址的请求时不知道该找哪个 handler 方法处理。
3.2 接收参数(重点)
3.2.1 param 和 json参数比较
-
param
- 格式 字符串: “key = value & key = value”
- 参数会被编码为 ASCII 码
name=john doe
,则会被编码为name=john%20doe
- 参数没有顺序限制
- 参数仅支持字符串类型、数值类型和布尔类型等简单数据类型
- 参数不支持嵌套
-
json
- {key:value,key:value}
- 参数会被编码为 UTF-8
- 键值对是有序排列,参数是有序的
- JSON 类型的参数则支持更复杂的数据类型,如数组、对象等
- 支持嵌套,可以传递更为复杂的数据结构
param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。
.
在实际开发中,常见的做法是:
在 GET 请求中采用 param 类型的参数,
在 POST 请求中采用 JSON 类型的参数传递。
3.2.2 param参数接收
- 1. 直接接值
@Controller
@RequestMapping("param")
public class ParamController {
@GetMapping("data")
@ResponseBody
public String data(String name,int age){
System.out.println("name : " + name+"age : " +age);
return "name : " + name+"age : " +age;
}
}
http://localhost:8080/param/data?age=0&name=haha
- 参数 地址 名称相同
- 可以不传递 包装类不报错是直接为Null
包装类参数可以为空,简单数据类型不能为空会报错(500)
Request processing failed: java.lang.IllegalStateException: Optional int parameter ‘age’ is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
请求处理失败:java.lang.IllegalStateException:可选的 int 参数“age”存在,但由于被声明为基元类型,因此无法转换为 null 值。请考虑将其声明为相应基元类型的对象包装器。
- 2.@RequestParam注解
@GetMapping("data1")
@ResponseBody
public String data1(@RequestParam(value = "account") String username,
@RequestParam(required = false,defaultValue = "1") int pwd){
System.out.println(username + " , "+ pwd);
return username + " , "+ pwd;
}
value = "account"
设置传值地址名字为account,输入别的报错(400)required = false
设置当前参数 非必需 (默认是必须{TRUE}defaultValue = "1"
默认值为1
400
必须传递数值的参数 没有传递数值
- 3.特殊场景接值
- 集合 多值
?hbs=A&hbs=B
参数前需要加注解 不然会报错
@GetMapping("data2")
@ResponseBody
public String data2(@RequestParam List<String> hbs){
System.out.println("hbs : "+hbs);
return "ok!";
}
2. 实体类参数
@GetMapping("data3")
@ResponseBody
public String data3(User user){
System.out.println("user : "+user);
return user.toString();
}
实体类 需要对应属性的setget方法 ->
实体类属性名、形参与地址实参 名字要相同
3.2.3 路径 参数接收
@Controller
@RequestMapping("path")
@ResponseBody
public class PathController {
/*
* path/账号/密码
* 必须使用@PathVariable
* */
@RequestMapping("{account}/{password}")
public String login(@PathVariable(value = "account") String username,
@PathVariable String password){
System.out.println(username + " , " +password);
return username + " , " +password;
}
}
必须写完整格式:否则404
3.2.4 json参数接收
报错: 415
不支持数据类型
原因:
JAVA原生API , 只支持路径参数和param参数 request.getParameter(“key”); param 不支持json
json 是前端格式
解决:
- 导入JSON处理的依赖
- handlerAdapter配置json转换器 (MvcConfig配置类中添加注解
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
@EnableWebMvc // HandlerAdapter配置JSON转换器
success!!
3.3 接收Cookie数据
@Controller
@ResponseBody
@RequestMapping("cookie")
public class CookieController {
// 获取cookie
@RequestMapping("data")
public String data(@CookieValue("cookieName") String value){
System.out.println("cookie : " + value);
return value;
}
// 创建一个cookie
@GetMapping("cooking")
public String cookie(HttpServletResponse response){
Cookie cookie = new Cookie("cookieName", "wake");
response.addCookie(cookie);
return "OK!";
}
}
先创建一个cookie
再获取cookie
3.4 接收请求头数据
@Controller
@ResponseBody
@RequestMapping("header")
public class HeaderController {
@RequestMapping("data")
public String header(@RequestHeader("Host") String host){
System.out.println("host" + host);
return "host" + host;
}
}
3.5 原生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 | 验证和数据绑定中的错误信息获取对象! |
/**
* 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
* 注意: 接收原生对象,并不影响参数接收!
*/
@Controller
public class ApiController {
//方案二 : servletContext 会自动装入到IOC容器 直接全局注入
@Autowired // ioc容器获取对应类型实体对象(组件) 并且 自动装配
private ServletContext servletContext;
public void data1(HttpServletRequest request,
HttpServletResponse response,
HttpSession session){
//使用原生对象就可以使用相应方法
//servletContext [ 1. 最大的配置文件 2.全局最大共享域 3.核心API getRealPath ]
//方案一 : request 获取 session 获取
ServletContext servletContext = request.getServletContext();
ServletContext servletContext1 = session.getServletContext();
}
}
3.6 共享域对象操作
一个 requset 请求的数据 可以存放在公共空间 其他request可以拿数据进行相应操作
3.6.1 属性(共享)域作用回顾
在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。
常见的共享域有四种:ServletContext
、HttpSession
、HttpServletRequest
、PageContext
。
ServletContext
共享域:ServletContext
对象可以在整个 Web 应用程序中共享数据,是最大的共享域。- 一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。
- 在
ServletContext
中保存的数据是线程安全的。
HttpSession
共享域:HttpSession
对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。- 比如,可以将用户登录状态保存在
HttpSession
中,让用户在多个页面间保持登录状态。
HttpServletRequest
共享域:HttpServletRequest
对象可以在同一个请求的多个处理器方法之间共享数据。- 比如,可以将请求的参数和属性存储在
HttpServletRequest
中,让处理器方法之间可以访问这些数据。
PageContext
共享域:PageContext
对象是在 JSP 页面Servlet 创建时自动创建的。- 它可以在 JSP 的各个作用域中共享数据,包括
pageScope
、requestScope
、sessionScope
、applicationScope
等作用域。
同一 Web 应用程序的多个组件之间传递数据,并且可以将数据保存在不同的共享域中,根据需要进行选择和使用。
3.6.2 Request级别属性(共享)域
用原生API就好了!
- 使用原生 request 对象
@RequestMapping("/attr/request/original")
@ResponseBody
public String testAttrOriginalRequest(
// 拿到原生对象,就可以调用原生方法执行各种操作
HttpServletRequest request) {
request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");
return "target";
}
- 使用 Model 类型的形参
@RequestMapping("/attr/request/model")
@ResponseBody
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
- 使用 ModelMap 类型的形参
@RequestMapping("/attr/request/model/map")
@ResponseBody
public String testAttrRequestModelMap(
// 在形参位置声明ModelMap类型变量,用于存储模型数据
ModelMap modelMap) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
modelMap.addAttribute("requestScopeMessageModelMap","i am very happy[model map]");
return "target";
}
- 使用 Map 类型的形参
@RequestMapping("/attr/request/map")
@ResponseBody
public String testAttrRequestMap(
// 在形参位置声明Map类型变量,用于存储模型数据
Map<String, Object> map) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
map.put("requestScopeMessageMap", "i am very happy[map]");
return "target";
}
- 使用 ModelAndView 对象
@RequestMapping("/attr/request/mav")
public ModelAndView testAttrByModelAndView() {
// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 2.存入模型数据
modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");
// 3.设置视图名称
modelAndView.setViewName("target");
return modelAndView;
}
3.6.3 Session级别属性(共享)域
@RequestMapping("/attr/session")
@ResponseBody
public String testAttrSession(HttpSession session) {
//直接对session对象操作,即对会话范围操作!
return "target";
}
3.6.4 Application级别属性(共享)域
springmvc会在初始化容器的时候,将servletContext对象存储到ioc容器中!
@Autowired
private ServletContext servletContext;
@RequestMapping("/attr/application")
@ResponseBody
public String attrApplication() {
servletContext.setAttribute("appScopeMsg", "i am hungry...");
return "target";
}
总结
@EnableWebMvc 原理
@EnableWebMvc
注解效果等同于在 XML 配置中,可以使用 <mvc:annotation-driven>
元素
@EnableWebMvc
帮助我们添加 :
HandlerMapping
和 HandlerAdapter
以及给这两个都加入到IOC容器和添加上了json转换器
源码: