目录
SpringMVC框架
SpringMVC框架简介
SpringMVC是是Spring Framework提供的Web组件,全称是Spring Web MVC,是目前主流的实现MVC设计模式的框架,提供前端路由映射、视图解析等功能。
一般我们开发服务端程序,基于两种形式,一种C/S架构,一种B/S架构,而我们JavaWeb项目一般使用的都是B/S架构,也就是我们常说的三层架构:
- 表现层:Web层,用来和客户端进行数据交互(一般会采用MVC的设计模型)
- 业务层:处理公司的具体业务逻辑
- 持久层:用来操作数据库,将数据保存在数据库中
B/S:Browser/Server,浏览器/服务器
C/S:Client/Server,客户端/服务器
什么是MVC模型
MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
-
Model
:数据模型,JavaBean的类,用来进行数据封装。JavaBean一般分为两类:实体类和业务处理类
-
View
:指JSP、HTML用来展示数据给用户 -
Controller
:用来接收用户的请求,整个流程的控制器。用来进行数据校验等。
大致是上面这个流程,在没有引入SpringMVC框架之前,我们前端传来的数据需要我们手动操作去寻找到对应Servlet进行逻辑处理,再保存到数据库中。SpringMVC帮我们封装了这套流程,提供了相应接口供我们使用,屏蔽了底层细节,使用更加方便。
SpringMVC具体实现原理
我们先来看一下SpringMVC的核心组件都有哪些:
-
DispatcherServlet:前端控制器,负责调用其他组件的执行,可以降低不同组件之间的耦合性,是整个SpringMVC的核心模块。我们的从前端接收到的请求都要经过它来发送到不同的组件进行处理。
-
Handler:处理器,用于完成我们的业务逻辑,相当于之前的Servlet。
-
HeandlerMapping:处理器映射器,用于接收前端控制器传来的前端请求,并映射到对应的Handler,并返回前端控制器一个执行链(HandlerExecutionChain)
-
HandlerExecutionChain:处理器执行链,这个对象中包含了两部分内容:我们的处理器对象(用于处理请求)和处理器拦截器接口(我们如果需要实现一些拦截功能可以通过它来完成)
-
HandlerInterceptor:处理器拦截器,是一个接口,可以通过实现这个接口构造拦截器
-
HandlerAdapter:处理器适配器,处理器映射器返回给前端控制器执行链之后,前端控制器就会请求适配器执行我们的处理器
Handler
,当然在这之前HandlerAdapter
还会进行一系列逻辑处理,如:表单数据验证,数据类型转换等 -
ModelAndView:模型和视图,在处理器处理完请求之后,会返回一个值,这个值通过HandlerAdapter返回给前端处理器,前端处理器将这个对象交给视图解析器,解析器返回给前端处理器视图,进行视图渲染到request域之后返回给客户端,也就是我们的Jsp页面。
-
ViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端
以下是SpringMVC框架的基本工作流程:
- 客户端请求被
DispatcherServlet
接收 DispatcherServlet
调用HandlerMapping
将请求映射到对应的Handler
,并返回一个执行链对象(HandlerExecutionChain
)其中包含了我们的处理器映射和拦截器接口DispatcherServlet
通过HandlerAdapter
调用Handler
的方法完成业务逻辑处理Handler
返回一个ModelAndView
对象给DispatcherServlet
DispatcherServlet
把获取的ModelAndView
对象传给ViewResolver
视图解析器,把逻辑视图解析成物理视图ViewResolver
返回一个View进行视图渲染(把模型填充到视图中DispatcherServlet
把渲染后的视图响应给客户端
了解大致流程之后我们开始创建我们的第一个SpringMVC项目。
SpringMVC入门程序
首先创建一个MavenWeb项目(不会创建的话可以去搜一些教程,不同版本的IDEA创建流程有些不一样,尤其是2024版本的,我就不过多介绍了),导入我们的核心依赖,这里我用的SpringFramework的版本为5.0.2.RELEASE,主要的核心依赖就是一下这些,你可以根据自己需求加一些测试或者是日志依赖。
<!-- 版本锁定 -->
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
第二步编写我们的index.jsp也就是我们的欢迎页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello SpringMVC</title>
</head>
<body>
<h1><a href="/hello/mvc">Hello SpringMVC Controller</a></h1>
</body>
</html>
这个就是我们的欢迎页面,里面放了一个a
标签,点击之后,浏览器会将a
标签对应的路径作为请求发送到DispatcherServlet
处理我们的请求,也就是/hello/mvc
这段东西,这个路径映射的就是我们的Controller
,后面会说怎么映射到这个Controller
的。
编写Controller类和方法,这个类就是用于执行我们的业务层逻辑操作,处理用户请求:
@Controller
// SpringMVC新注解 ---> 映射路径
@RequestMapping("/hello")
public class HelloController {
// 二级映射
// @GetMapping
// @PostMapping
// @PutMapping
// @DeleteMapping
@RequestMapping("/mvc")
// 类似于HttpServlet中的doGet方法
public String sayHello(){
System.out.println("Hello SpringMVC入门案例");
return "suc";
}
}
这个类首先我们要添加@Controller
注解交给Spring管理,除此之外还需要添加@RequestMapping
注解,这个注解我们后面会详细说,现在只需要知道他可以定义这个类的请求URL即可。
可以看到我们的sayHello()
方法返回了一个字符串suc
,这里其实我是想通过Controller
返回我们的suc.jsp
视图页面,也就是我们之前流程的最后一步返回视图。那么我们首先需要我们的核心——前端控制器:
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化加载springmvc.xml配置文件,配置的是Spring配置-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 配置启动加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 映射 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
前端控制器底层实现其实就是Servlet,他拦截了我们的请求,并将其转发给其他组件去处理。所以我们在配置的时候要加上<load-on-startup>1</load-on-startup>
,让他在项目启动的时候就加载,在映射部分添加<url-pattern>/</url-pattern>
,作用是拦截除了jsp之外的所有请求(这里涉及到Servlet的匹配规则,可以看看这篇文章,写的很详细:彻底理解servlet匹配顺序 / 和 /*的区别_default-wrapper-CSDN博客),以便于处理。
配置好我们的核心之后,就可以配置视图解析器:
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.qcby"/>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀路径 -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
配置我们的suc.jsp视图页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC入门案例</title>
</head>
<body>
<h1>JSP MVC 入门成功页面</h1>
<h4><a href="${pageContext.request.contextPath}/">返回首页</a></h4>
</body>
</html>
现在一个简单的SpringMVC项目就算是配置好了,启动tomcat,启动项目:
RequestMapping注解
现在来介绍一下RequestMapping
注解,它的的作用就是建立请求URL和处理方法之间的对应关系,他可以作用在类上或者是方法上,之后我们只需要通过注解中定义的路径就可以访问到这个类或者是调用到某个方法。
- 作用在类上:第一级的访问目录
- 作用在方法上:第二级访问目录
- 细节:路径可以不编写
/
表示应用的根目录开始
RequestMapping属性
- path:指定请求路径的URL
- value:value属性和path属性是一样的
- method:指定该方法的请求方式(GET、POST、PUT、DELETE…)
- params:指定限制请求参数的条件
我们来看一下演示效果:
@Controller
@RequestMapping("/role")
public class RoleController {
/**
* /role/save.do
* method="当前方法允许请求方式能访问"
* params="请求路径上传参数"
* @return
*/
@RequestMapping(value = "/save.do", method = RequestMethod.GET, params = "username")
public String save(String username) {
System.out.println("保存角色:" + username);
return "suc";
}
@RequestMapping(value = "/delete.do")
public String delete() {
System.out.println("删除角色...");
return "suc";
}
}
注意当我们添加了param属性的时候就必须在请求内容中传入对应参数:
如果不传就会报错:
提示我们所需的参数username
没有找到
请求参数的绑定
请求参数绑定机制
- 表单提交的数据都是k=v格式的 username=haha&password=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
支持的参数类型
- 基本数据类型和字符串类型
- 实体类型(JavaBean)
- 集合数据类型(List、map集合等)
基本数据类型
- 提交表单的name和参数的名称是相同的
- 区分大小写
实体类型
- 提交表单的name和JavaBean中的属性名称需要一致
- 如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如:address.name
集合数据类型
- JSP页面编写方式:list[0].属性
下面我们看一下实例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>请求参数绑定</title>
</head>
<body>
<h3>请求参数绑定入门</h3>
<form action="/user/savemsg.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
<input type="submit" value="提交" />
</form>
<h3>请求参数绑定--封装到实体类</h3>
<form action="/user/saveuser.do" method="post">
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
金额:<input type="text" name="address.money"/><br/>
<input type="submit" value="提交"/>
</form>
<h3>请求参数绑定--集合类型</h3>
<form action="/user/saveuser.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
金额:<input type="text" name="address.money" /><br/>
集合姓名:<input type="text" name="addressList[0].money" /><br/>
集合年龄:<input type="text" name="addressList[1].money" /><br/>
<input type="submit" value="提交" />
</form>
<h3>请求参数绑定--Map集合类型</h3>
<form action="/user/saveuser.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
金额:<input type="text" name="address.money" /><br/>
Map集合姓名:<input type="text" name="userMap['Ray'].username" /><br/>
Map集合年龄:<input type="text" name="userMap['Ray'].age" /><br/>
Map集合生日:<input type="text" name="userMap['Ray'].birthday" /><br/>
<input type="submit" value="提交" />
</form>
</body>
</html>
JavaBean代码部分:
public class User implements Serializable {
// 引入基本数据类型
private String username;
private Integer age;
// 引入对象
private Address address;
// 引入List集合
private List<Address> addressList;
// 引入Map集合
private Map<String, User> userMap;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public List<Address> getAddressList() {
return addressList;
}
public void setAddressList(List<Address> addressList) {
this.addressList = addressList;
}
public Map<String, User> getUserMap() {
return userMap;
}
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", address=" + address +
", addressList=" + addressList +
", userMap=" + userMap +
'}';
}
}
Controller类编写
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/savemsg.do")
public String saveMsg(String username, Integer age) {
System.out.println("姓名:" + username);
System.out.println("年龄:" + age);
return "suc";
}
@RequestMapping("/saveuser.do")
public String saveUser(User user) {
System.out.println("user对象:" + user);
return "suc";
}
}
输入对应的参数类型,可以输出控制台就会有对应的输出,这里Map集合传参有一点点不太一样,细节部分可以看这篇博客:SpringMVC用Map接收请求参数分析 - liuyiyuan - 博客园,简而言之就是:如果我们想只传一个Map集合类型的参数的话,那么不添加任何注解的情况下,SpringMVC是无法识别这个参数的,也就是说传不进来;我们需要借助@RequestParam
或者@RequestBody
来获取我们的参数。
但是如果你按照我上面的这种方式,传入一个User类型的对象,在User对象中包含Map集合,那么就可以直接传入参数:Map集合生日:<input type="text" name="userMap['Ray'].birthday" /><br/>
这里Key就是Ray
,Value就是我们传入的参数。
@RequestMapping("/saveuser.do")
public String saveUser(User user) {
System.out.println("user对象:" + user);
return "suc";
}
请求参数中文乱码解决方式
你可能会遇到这样的情况,比如这里我正确的传入参数:
我们期望会在控制台看到类似这样的输出:
但是事实却是这样:
中文部分乱码了,这是由于浏览器编码方式和我们的idea编码方式不同导致的,那我们通过一个过滤器来解决这个问题:
<!-- 配置过滤器,解决中文乱码的问题 -->
<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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
自定义类型转换器
我们如果在表单中想要提交日期类型的数据的话,必须要按照SpringMVC设置好的格式去填写,否则就会报400请求参数出错
的状态码,默认的格式为:yyyy/MM/dd
。当我们想用我们熟悉的方式填写,比如yyyy-MM-dd
或者是yyyy.MM.dd
这种方式,我们可以通过以下两种方式进行实现:
DateTimeFormat注解
首先记得在Spring配置文件中打开注解支持,因为这个注解是SpringMVC框架提供的:
<mvc:annotation-driven/>
然后在我们实体类的日期属性上添加@DateTimeFormat
注解,他其中有一个pattern
属性值,可写可不写,不写的情况就是识别默认格式。我们通过这个属性来自定义我们的格式:
@DateTimeFormat(pattern = "yyyy-MM-dd hh:MM:ss")
private Date birthday;
这样就可以正常获取到我们的日期了
自定义类型转换器
如果想自定义数据类型转换,可以实现Converter的接口:
public class StringToDateUtil implements Converter<String, Date> {
@Override
public Date convert(String source) {
// 判断
if (source == null) {
throw new RuntimeException("请输入日期!!");
}
// 进行转换
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return simpleDateFormat.parse(source);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
实现这个接口中的convert()
方法,通过方法中的SimpleDateFormat
类去设置我们的格式,效果也是一样的,只不过我们需要在配置文件中将我们自定义的类注入到Spring提供的ConversionServiceFactoryBean
转换工厂类中去:
<!--配置日期类型转换器,类型转换器的组件,把日期类型转换注入到组件对象中-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.qcby.Utils.StringToDateUtil" />
</set>
</property>
</bean>
<!--让映射器、适配器和处理器生效(默认不配置也是可以的)-->
<mvc:annotation-driven conversion-service="conversionService"/>
SpringMVC常用注解
在开发过程中,我们除了会用到上面提到的@RequestMapping
、@RequestParam
和@RequestBody
注解,还会用到一些其他的注解,我们来详细介绍一下:
1. RequestParam
- 作用:把请求中的指定名称的参数传递给控制器中的形参赋值
- 属性:
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数,默认值是true,必须提供
- defaultValue:当参数没有传入的时候使用的默认值,必须搭配
required = false
使用
@RequestMapping("/savemsg.do")
public String saveMsg(@RequestParam(value = "username", required = false, defaultValue = "Ray") String username, @RequestParam(value = "age") Integer age) {
System.out.println("姓名:" + username);
System.out.println("年龄:" + age);
return "suc";
}
2. RequestBody注解
- 作用:用于获取请求体的内容(注意:get方法不可以,因为get方法没有请求体)
- 属性:
- required:是否必须有请求体,默认值是true
/**
* 获取请求体的值
* @param body 请求体
* @return 成功页面
*/
@PostMapping("/body.do")
public String testAnno(@RequestBody String body) {
System.out.println("请求体:" + body);
return "suc";
}
3. PathVaribale注解
这个注解比较重要,在之后的开发中会经常利用这个注解去实现Restful风格的URL:RestFul简介和使用-CSDN博客。
特点:
- 请求路径一样,可以根据不同的请求方式去执行后台的不同方法
- restful风格的URL优点
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
比如我有如下三个表单:
<h3>注解测试PathVaribale注解GET</h3>
<form action="/emp" method="get">
<input type="submit" value="查询" />
</form>
<h3>注解测试PathVaribale注解POST</h3>
<form action="/emp" method="post">
<input type="submit" value="查询" />
</form>
<h3>注解测试PathVaribale注解按照名称查询</h3>
<form action="/emp" method="get">
<input type="text" name="username"/><br/>
<input type="submit" value="提交" />
</form>
可以看到我们三个表单的路径都是一样的,但是请求方法不一样或者参数名称不一样,那么我们就可以通过@PathVaribale
注解去指定我们该访问哪个方法,在Controller类中去配置:
@PostMapping("/emp")
public String save(){
System.out.println("保存员工...");
return "suc";
}
/**
* 查询所有
*/
@GetMapping("/emp")
public String findAll(){
System.out.println("查询员工...");
return "suc";
}
/**
* 根据用户名查询
*/
@GetMapping(path = "/emp/{username}")
public String findByName(@PathVariable(value = "username") Integer username){
System.out.println("通过id查询员工..." + username);
return "suc";
}
4. RequestHeader注解
-
作用:获取指定请求头的值
-
属性
value:请求头的名称
/**
* 获取请求头的值
* @param header 请求头
* @return 成功页面
*/
@RequestMapping("/header.do")
public String testAnno01(@RequestHeader(value = "Accept") String header) {
System.out.println("请求头:" + header);
return "suc";
}
5. CookieValue注解
-
作用:用于获取指定cookie的名称的值
-
属性:
value:cookie的名称
/**
* 获取Cookie的值
* @param cookie 值
* @return 成功页面
*/
@PostMapping("/cookie.do")
public String testAnno02(@CookieValue("JSESSIONID") String cookie) {
System.out.println("Cookie值:" + cookie);
return "suc";
}