SpringMVC框架
三层架构与MVC
在 B/S (浏览器 / 服务器)架构的程序开发中,常采用三层架构作为设计模式,三层架构从用户交互到数据存储,自上而下分为表现层、业务逻辑层、数据访问层,每层仅负责特定职责,且通过接口 / 数据模型通信,不直接渗透其他层的内部实现。
- 表现层:接收前端请求,解析参数,调用业务逻辑层处理,最终返回响应结果,一般会采用MVC的设计模型;
- 业务层:封装核心业务逻辑,实现复杂业务处理;
- 持久层:负责与数据库交互,执行 CRUD操作,不包含业务逻辑。
MVC模型
MVC(Model-View-Controller)是一种软件架构模式,核心思想是通过 “关注点分离” 将应用程序的三大核心职责拆分为三个独立组件,以提升代码的可维护性、可复用性和可扩展性。
MVC 的三大核心组件
- Model(数据模型):Model 是应用程序的数据与业务逻辑核心,封装应用程序数据,即JavaBean;
- View(视图):View 是应用程序的界面展示层,负责负责数据渲染,同时接收用户的输入操作,但不处理业务逻辑;
- Controller(控制器):Controller 是 Model 与 View 之间的中间协调者,控制器处理用户请求并将其传递给视图进行渲染。
SpringMVC的入门案例,创建 MavenJavaWeb 工程,在 pom 文件中引入开发的jar包,具体的坐标如下
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<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>入门程序</title>
</head>
<body>
<%--超链接--%>
<h3>入门</h3>
<a href="/hello.do" >入门程序</a>
</body>
</html>
编写suc.jsp页面,路径为:/WEB-INF/pages/suc.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功</title>
</head>
<body>
<h3>入门成功了...</h3>
</body>
</html>
package com.qcby.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 控制器类,处理用户的请求
*/
@Controller
public class HelloController {
@RequestMapping(path = "/hello.do")
public String sayHello(){
System.out.println("入门方法执行了2...");
// 跳转的JSP页面的路径,默认使用的是请求的转发
//return "/WEB-INF/pages/suc.jsp";
// 配置了视图解析器后,写法
return "suc";
}
}
在web.xml配置文件中配置DispatcherServlet核心的控制器,初始化 MVC 的核心控制器、配置文件位置和启动时机,确保请求能被 SpringMVC 接管并按流程处理;CharacterEncodingFilter配置解决了中文乱码这一常见问题,保证请求参数和响应内容的中文正常显示。
<!--配置前端控制器-->
<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>
<!-- 配置过滤器,解决中文乱码的问题 -->
<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>
</web-app>
编写springmvc.xml的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.qcby"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
SpringMVC 核心组件:
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器(Handler)
处理器适配器(HandlAdapter)
视图解析器(View Resolver)
视图(View)
过程图解:
详细流程描述:
用户在浏览器输入http://localhost:8080/hello.do,HTTP 请求被发送到服务器,服务器根据 web.xml中DispatcherServlet的映射规则,将请求转发给DispatcherServlet。DispatcherServlet接收到请求后,首先调用HandlerMapping(@Controller类中被@RequestMapping标注的方法),根据请求的URL和请求方式匹配对应的Handler(即@Controller类中被@RequestMapping(“/hello.do”)标注的方法),DispatcherServlet调用HandlerAdapter适配Handler,HandlerAdapter根据请求参数名与方法参数名的对应关系,自动完成参数的类型转换和赋值,最终将转换后的数据传递给控制器方法执行核心业务逻辑。HandlerAdapter将返回值ModelAndView返回给DispatcherServlet,DispatcherServlet此时需要确定用哪个视图渲染数据,因此调用ViewResolver视图解析器。ViewResolver根据预设的视图解析规则,将ModelAndView中的逻辑视图名解析为物理视图路径,并创建对应的View对象。DispatcherServlet获取View对象后,将Model中的数据传递给View,由View执行数据渲染,View渲染完成后,生成 HTTP 响应,并将响应结果返回给DispatcherServlet。若为异步请求(如@ResponseBody),则Handler直接返回 JSON 数据,跳过ViewResolver和View渲染步骤,由HandlerAdapter直接将数据转为 JSON 响应。DispatcherServlet将最终的响应通过服务器返回给客户端,用户看到登录成功页面,流程结束。
RequestMapping注解
RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
RequestMapping注解可以作用在方法和类上
作用在类上:第一级的访问目录
作用在方法上:第二级的访问目录
细节:路径可以不编写 / 表示应用的根目录开始
RequestMapping的属性
path:指定请求路径的url,需要显式声明键不能省略
value:value属性和path属性是一样的,当只有这一个参数时可以省略value键
method:指定允许的HTTP请求方式
params:限制请求必须包含指定的参数
package com.qcby.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 角色模块的类
*/
@Controller
@RequestMapping(path = "/role")
public class RoleController {
@RequestMapping(path = "/save.do",method = {RequestMethod.GET},params = "username")
public String save(){
System.out.println("保存角色...");
return "suc";
}
@RequestMapping(value = "/delete.do")
public String delete(){
System.out.println("删除角色...");
return "suc";
}
}
请求参数的绑定
请求参数绑定是指框架自动将客户端发送的请求参数转换为控制器方法参数的过程。
这一机制简化了开发者获取请求数据的操作,无需手动解析请求参数。
- 简单类型绑定:表单提交的数据都是 k=v 格式,提交表单的name和JavaBean中的属性名称需要一致,基本类型不能接收null值,若参数可能缺失,建议使用包装类;
- 对象绑定:当请求参数较多时,可将参数封装到 Java 对象中,SpringMVC 会自动将请求参数绑定到 Java 对象的属性上,要求请求参数名与 Java 对象的属性名一致(属性需提供getter/setter方法);
- 嵌套绑定:若 Java 对象中包含其他 Java 对象属性(嵌套关系),请求参数名需以 “外层属性名.内层属性名” 的格式传递;
- 集合类型绑定:对于List、Map等集合类型,通常需要将集合封装到一个包装类中,请求参数名需包含集合的索引或键;
- 日期类型绑定:SpringMVC 默认支持 yyyy/MM/dd 格式的日期字符串转换为Date类型(英文状态时间),但对 yyyy-MM-dd 等格式不支持,需自定义类型转换器。
代码示例:
前端jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>请求参数绑定</title>
</head>
<body>
<h3>请求参数绑定(简单类型绑定)</h3>
<form action="/user/save1.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
<input type="submit" value="提交" /><br/>
</form>
<h3>请求参数绑定(封装到实体类)</h3>
<form action="/user/save2.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
<input type="submit" value="提交" /><br/>
</form>
<h3>请求参数绑定(封装到实体类,包含嵌套)</h3>
<form action="/user/save3.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
金额:<input type="text" name="account.money" /><br/>
<input type="submit" value="提交" /><br/>
</form>
<h3>请求参数绑定(封装到实体类,存在list集合)</h3>
<form action="/user/save4.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
生日:<input type="text" name="birthday" placeholder="yyyy-MM-dd"/><br/>
List集合:<br/>
金额1:<input type="text" name="list[0].money" /><br/>
金额2:<input type="text" name="list[1].money" /><br/>
<input type="submit" value="提交" /><br/>
</form>
<h3>请求参数绑定(封装到实体类,存在Map集合)</h3>
<form action="/user/save5.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
生日:<input type="text" name="birthday" placeholder="yyyy-MM-dd"/><br/>
Map集合:<br/>
性别:<input type="text" id="name" name="infoMap.gender"><br>
住址:<input type="text" id="age" name="infoMap.address"><br>
<input type="submit" value="提交" /><br/>
</form>
</body>
</html>
Javabean代码
package com.qcby.model;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class User implements Serializable{
private String username;
private Integer age;
// 2000-11-11 格式的日期
//@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
//引用对象
private Account account;
//list集合
private List<Account> list;
// 添加Map集合属性
private Map<String, Object> infoMap= new HashMap<String, Object>();
public User(String username, Integer age, Date birthday, Account account, List<Account> list, Map<String, Object> infoMap) {
this.username = username;
this.age = age;
this.birthday = birthday;
this.account = account;
this.list = list;
this.infoMap = infoMap;
}
public User() {
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
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 Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public List<Account> getList() {
return list;
}
public void setList(List<Account> list) {
this.list = list;
}
public Map<String, Object> getInfoMap() {
return infoMap;
}
public void setInfoMap(Map<String, Object> infoMap) {
this.infoMap = infoMap;
}
@Override
public String toString() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String birthdayStr = (birthday != null) ? sdf.format(birthday) : "null";
@Override
public String toString() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String birthdayStr = (birthday != null) ? sdf.format(birthday) : "null";
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", birthday=" + birthdayStr +
", account=" + account +
", list=" + list +
", infoMap=" + infoMap +
'}';
}
}
由于 birthday 是 java.util.Date 类型,当它被拼接到字符串中时会自动调用 Date.toString(),而该方法的默认格式是固定的(如Wed Apr 30 00:00:00 CST 2025),要让birthday以 2025-04-30 的格式输出,需要在User类的 toString() 方法中对Date对象进行手动格式化,Java 8 + 的 DateTimeFormatter 或传统的 SimpleDateFormat。
package com.qcby.model;
import java.io.Serializable;
public class Account implements Serializable {
// 金额
private Double money;
public Account(Double money) {
this.money = money;
}
public Account() {
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"money=" + money +
'}';
}
}
controller代码
package com.qcby.controller;
import com.qcby.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Enumeration;
import java.util.Map;
/**
* 用户的模块
*/
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 请求参数的绑定
*/
@RequestMapping("/save1.do")
public String save(String username,Integer age){
System.out.println("姓名:"+username);
System.out.println("年龄:"+age);
return "suc";
}
/**
* 请求参数的绑定
*/
@RequestMapping("/save2.do")
public String save2(User user){
System.out.println("user对象:"+user);
return "suc";
}
/**
* 请求参数的绑定
*/
@RequestMapping("/save3.do")
public String save3(User user){
System.out.println("user对象:"+user);
return "suc";
}
/**
* 请求参数的绑定
*/
@RequestMapping("/save4.do")
public String save4(User user){
System.out.println("user对象:"+user);
return "suc";
}
/**
* 将请求参数封装到Map,使用@RequestParam注解
* 将请求参数转换为Map类型
*/
@PostMapping("/save5.do")
public String processMap(@RequestParam Map<String, String> map) {
// 接收到的Map参数通过entrySet方法遍历并获取键值对
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
return "suc";
}
}
@RequestParam Map<String, String> map
的作用是将前端提交的所有请求参数直接以字符串键值对的形式存入 Map 中,前端提交的日期参数本身就是字符串类型,@RequestParam 会原封不动地将这个字符串存入 map 的 value 中,当打印 map 中的值时,本质上是打印这个原始字符串,因此格式就是前端提交时的格式。
自定义类型转换器
第一种方式,使用DateTimeFormat注解的方式 @DateTimeFormat(pattern = "yyyy-MM-dd")
第二种方式,自定义类型转换器,需要实现Converter的接口
package com.qcby.util;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
// 使用Spring提供的Converter接口
public class StringToDate implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
try {
// 支持"yyyy-MM-dd"格式的日期转换
return new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (ParseException e) {
throw new IllegalArgumentException("无效的日期格式!请使用yyyy-MM-dd格式", e);
}
}
}
这段代码是一个自定义类型转换器,用于将字符串(String)类型转换为日期(Date)类型,实现数据绑定过程中的自动类型转换。
类定义 public class StringToDate implements Converter<String, Date>
实现了 Spring 框架提供的 Converter<S, T> 接口(泛型接口),其中 S(源类型)为 String 表示需要转换的原始数据类型是字符串,T(目标类型)为 Date 表示转换后的目标类型是 java.util.Date(日期对象),实现该接口后,Spring 会将其识别为一个类型转换器,在需要将字符串转为日期时自动调用。该类的核心方法 convert(String source) 是转换逻辑的核心,使用 SimpleDateFormat 以固定格式 “yyyy-MM-dd” 解析字符串,将其转换为Date对象。
前端提交的时间字符串内容会被 Spring 通过配置的 StringToDate 转换器转换为 Date 对象(java.util.Date 类型),Date 对象本身不存储格式只存储一个时间戳,当打印 Date 对象时,会默认调用其 toString() 方法。
注册自定义类型转换器,在 springmvc.xml 配置文件中编写配置
<!--配置日期类型转换器,类型转换器的组件,把日期类型转换注入到组件对象中-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.qcby.util.StringToDate" />
</set>
</property>
</bean>
<!-- 配置spring开启注解mvc的支持,让映射器、适配器和处理器生效-->
<mvc:annotation-driven conversion-service="conversionService"/>
定义了一个 Spring Bean,id 为 conversionService,其类型是 ConversionServiceFactoryBean,是 Spring 提供的工厂类,这个工厂类的核心功能是创建并初始化 Spring 的类型转换服务核心接口 ConversionService 实例。ConversionService 负责管理所有注册的转换器,实现不同类型之间的自动转换。<property name="converters">
为 ConversionServiceFactoryBean 设置 converters 属性,用于指定需要注册的转换器集合。
补充在控制器中使用原生的ServletAPI对象
<h3>请求参数绑定(Servlet原生API)</h3>
<form action="/user/save6.do" method="post">
姓名:<input type="text" name="username" /><br/>
年龄:<input type="text" name="age" /><br/>
生日:<input type="text" name="birthday" placeholder="yyyy-MM-dd" /><br/>
<!-- 示例输入:2000-01-01 -->
<input type="submit" value="提交" /><br/>
</form>
/**
* Servlet原生API的使用
* @param request 请求对象
* @param response 响应对象
*/
@RequestMapping("/save6.do")
public String save6(HttpServletRequest request, HttpServletResponse response,User user) {
System.out.println("请求对象: " + request);
HttpSession session = request.getSession();
System.out.println("会话对象: " + session);
System.out.println("响应对象: " + response);
System.out.println("绑定后的User: " + user);
return "suc";
}
HttpServletRequest request
是 Java EE 中的请求对象,封装了客户端发送的所有请求信息,通过 request.getParameter(“uname”) 获取名为uname的请求参数,通过request.getSession()获取当前会话对象HttpSession;HttpServletResponse response
是 Java EE 中的响应对象,用于向客户端发送响应数据,response.getWriter().print(“xxx”) 向客户端返回数据。
实现功能的后端打印结果: