MVC 是软件工程中的一种软件架构模式,它是一种分离业务逻辑与显示界面的开发思想。
* M(model)模型:处理业务逻辑,封装实体
* V(view) 视图:展示内容
* C(controller)控制器:负责调度分发(1.接收请求、2.调用模型、3.转发到视图)
SpringMVC 是一种基于 Java 的实现 MVC 设计模式的轻量级 Web 框架,属于Spring Framework 的后续产品,已经融合在 Spring Web Flow 中。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring 3.0 的发布,全面超越 Struts 2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。
SpringMVC 的框架就是封装了原来 Servlet 中的共有行为;例如:参数封装,视图转发等。
需求:客户端发起请求,服务器接收请求,执行逻辑并进行视图跳转。
步骤分析:
DispatcherServlet
<packaging>war</packaging>
<!-- 指定编码及版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.11</java.version>
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
</properties>
<dependencies>
<!-- springMVC 坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- servlet 坐标 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp 坐标 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
...
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- springMVC 的前端控制器:DispatcherServlet-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 加载 springMVC 相关的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--
在应用启动时,就完成 servlet 的实例化及初始化操作。
数字大于等于 0 时,服务器启动时就加载并初始化 servlet;
数字越接近 0,则优先级越高
-->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--
/ 和 /* 的区别:
/ 会匹配到所有的访问路径,除了带有 *.jsp 这样的后缀名的路径;
如可以匹配到 /login /add /update,但 /a.jsp 不能匹配。
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
UserController.java
public class UserController {
public String quick() {
// 业务处理
System.out.println("SpringMVC Quick Start...");
// 页面跳转 请求转发
return "/WEB-INF/pages/success.jsp";
}
}
/WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SUCCESS</title>
</head>
<body>
<h3>success... ${username}</h3>
<% System.out.println("Success...");%>
</body>
</html>
@Controller
public class UserController {
@RequestMapping("/quick")
public String quick() {
System.out.println("SpringMVC Quick Start...");
return "/WEB-INF/pages/success.jsp";
}
}
<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">
<!-- IOC 注解扫描 -->
<context:component-scan base-package="com.renda.controller"/>
</beans>
客户端(浏览器)发送请求到 Tomcat 服务器,Tomcat 服务器发送响应到客户端。
服务器的 Tomcat 引擎:
服务器的 Web 应用:
* SpringMVC 是对 MVC 设计模式的一种实现,属于轻量级的 WEB 框架。
* SpringMVC 的开发步骤:
1.创建 web 项目,导入 SpringMVC 相关坐标
2.配置 SpringMVC 前端控制器 DispatcherServlet
3.编写 Controller 类和视图页面
4.使用注解配置 Controller 类中业务方法的映射地址
5.配置 SpringMVC 核心文件 spring-mvc.xml
1. 用户发送请求至前端控制器 DispatcherServlet。
2. DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3. 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4. DispatcherServlet 调用 HandlerAdapter 处理器适配器。
5. HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
6. Controller 执行完成返回 ModelAndView。
7. HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9. ViewReslover 解析后返回具体 View。
10. DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
11. DispatcherServlet 将渲染后的视图响应响应用户。
1. 前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 MVC 模式中的 C;DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求;DispatcherServlet 的存在降低了组件之间的耦合性。
2. 处理器映射器:HandlerMapping
HandlerMapping 负责根据用户请求找到 Handler 即处理器;SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式、实现接口方式、注解方式等。
3. 处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4. 处理器:Handler【**开发者编写**】
它是开发中要编写的具体业务控制器;由 DispatcherServlet 把用户请求转发到 Handler;由 Handler 对具体的用户请求进行处理。
5. 视图解析器:ViewResolver
View Resolver 负责将处理结果生成 View 视图;View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6. 视图:View 【**开发者编写**】
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
7. SpringMVC 中的三大组件是什么?
处理器映射器 HandlerMapping;
处理器适配器 HandlerAdapter;
视图解析器 ViewResolver;
如果是四大组件,则加上前端控制器 DispatcherServlet。
spring-mvc.xml
<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">
<!-- IOC 注解扫描 -->
<context:component-scan base-package="com.renda.controller"/>
<!-- 处理器映射器-处理器适配器。进行了功能的增强:支持 json 的读写 -->
<mvc:annotation-driven/>
<!-- 视图解析器:ViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- success :解析逻辑视图名,解析成具体的物理文件地址 /WEB-INF/pages/success.jsp -->
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
UserController.java
@RequestMapping("/quick")
public String quick() {
System.out.println("SpringMVC Quick Start...");
// 页面跳转 请求转发 返回逻辑视图名
return "success";
}
SpringMVC 基于 Spring 容器,所以在进行 SpringMVC 操作时,需要将 Controller 存储到 Spring 容器中,如果使用 @Controller
注解标注的话,就需要使用:
<!-- 配置 IOC 注解扫描 -->
<context:component-scan base-package="com.renda.controller"/>
注意配置 SpringMVC 的 base package 只扫描 web 层的包,其它包交给 Spring 容器扫描;这样 SpringMVC 的容器就相当于是 Spring 容器的一个子容器。
* 作用:用于建立请求 URL 和处理请求方法之间的对应关系
* 位置:
1. 类上:请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录;写的话需要以 / 开头。
它出现的目的是为了使 URL 可以按照模块化管理:
用户模块
/user/add
/user/update
/user/delete
...
账户模块
/account/add
/account/update
/account/delete
2. 方法上:请求 URL 的第二级访问目录,和一级目录组成一个完整的 URL 路径。
* 属性:
1. value:用于指定请求的 URL;它和 path 属性的作用是一样的
2. method:用来限定请求的方式
3. params:用来限定请求参数的条件
例如:
params={"accountName"} 表示请求参数中必须有 accountName
params={"money!100"} 表示请求参数中 money 不能是100
@Controller
@RequestMapping("/user") //一级访问目录
public class UserController {
/**
* http://localhost:8080/springmvc_quickstart/user/quick?accountName=renda
*
* path :作用等同于 value,同样是设置方法的映射地址
* method:用来限定请求的方式 RequestMethod.POST: 只能以 post 的请求方式访问该访问,其他请求方式会报错
* params:用来限定请求参数的条件 params={"accountName"} 表示请求参数中必须有 accountName
*/
@RequestMapping(
path = "/quick", // 二级访问目录
method = {RequestMethod.POST, RequestMethod.GET},
params = {"accountName"})
public String quick() {
// 业务处理
System.out.println("SpringMVC Quick Start...");
// 页面跳转 请求转发 返回逻辑视图名
return "success";
}
}
* SpringMVC的三大组件
处理器映射器:HandlerMapping
处理器适配器:HandlerAdapter
视图解析器:View Resolver
* 开发者编写
处理器:Handler
视图:View
客户端请求参数的格式是:name=value&name=value ...
服务器要获取请求的参数的时候要进行类型转换,有时还需要进行数据的封装
SpringMVC 可以接收如下类型的参数:
Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配。并且能自动做类型转换;自动的类型转换是指从 String 向其他类型的转换。
requestParam.jsp
<%-- ${pageContext.request.contextPath} 动态的来获取当前的项目路径 - springmvc_quickstart;a 标签的请求方式:get 请求 --%>
<a href="${pageContext.request.contextPath}/user/simpleParam?id=1&username=杰克">
基本类型参数
</a>
在 UserController
中添加方法
@RequestMapping("/simpleParam")
public String simpleParam(Integer id, String username) {
System.out.println(id);
System.out.println(username);
return "success";
}
Controller 中的业务方法参数的 POJO 属性名与请求参数的 name 一致,参数值会自动映射匹配。
requestParam.jsp
<%-- form 表单;该表单提交的请求方式为 post 类型 --%>
<form action="${pageContext.request.contextPath}/user/pojoParam" method="post">
编号:<input type="text" name="id"><br>
用户名:<input type="text" name="username">
<input type="submit" value="对象类型参数">
</form>
创建 User
类
public class User {
private Integer id;
private String username;
// setter getter...
}
在 UserController
中添加方法
@RequestMapping("/pojoParam")
public String pojoParam(User user){
System.out.println(user);
return "success";
}
当 post 请求时,数据会出现乱码,可以在 web.xml 设置一个过滤器来进行编码的过滤。
<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>
Controller 中的业务方法数组名称与请求参数的 name 一致,参数值会自动映射匹配。
requestParam.jsp
<form action="${pageContext.request.contextPath}/user/arrayParam" method="post">
编号:<br>
<input type="checkbox" name="id" value="1"><br>
<input type="checkbox" name="ids" value="2"><br>
<input type="checkbox" name="ids" value="3"><br>
<input type="checkbox" name="ids" value="4"><br>
<input type="submit" value="数组类型参数">
</form>
在 UserController
中添加方法
@RequestMapping("/arrayParam")
public String arrayParam(Integer[] ids){
System.out.println(Arrays.toString(ids));
return "success";
}
获得集合参数时,要将集合参数包装到一个 POJO 中才可以。
requestParam.jsp
<form action="${pageContext.request.contextPath}/user/queryParam" method="post">
搜索关键字:
<input type="text" name="keyword"><br>
user对象:
<input type="text" name="user.id" placeholder="编号">
<input type="text" name="user.username" placeholder="姓名"><br>
list集合:
元素:
<input type="text" name="userList[0].id" placeholder="编号">
<input type="text" name="userList[0].username" placeholder="姓名">
元素:
<input type="text" name="userList[1].id" placeholder="编号">
<input type="text" name="userList[1].username" placeholder="姓名"><br>
Map集合:
元素:
<input type="text" name="userMap['u1'].id" placeholder="编号">
<input type="text" name="userMap['u1'].username" placeholder="姓名">
元素:
<input type="text" name="userMap['u2'].id" placeholder="编号">
<input type="text" name="userMap['u2'].username" placeholder="姓名"><br>
<input type="submit" value="复杂类型">
</form>
创建 QueryVo
类
public class QueryVo {
private String keyword;
private User user;
private List<User> userList;
private Map<String, User> userMap;
// setter getter...
}
在 UserController
中添加方法
@RequestMapping("/queryParam")
public String queryParam(QueryVo queryVo){
System.out.println(queryVo);
return "success";
}
SpringMVC 默认已经提供了一些常用的类型转换器。例如:客户端提交的字符串转换成 INT 型进行参数设置,日期格式类型要求为:yyyy/MM/dd
不然的话会报错;对于特有的行为,SpringMVC 提供了自定义类型转换器方便开发者自定义处理。
requestParam.jsp
<form action="${pageContext.request.contextPath}/user/converterParam" method="post">
生日:<input type="text" name="birthday">
<input type="submit" value="自定义类型转换器">
</form>
配置自定义日期转换 DateConverter
public class DateConverter implements Converter<String, Date> {
/**
* s 就是表单传递过来的请求参数,如 2012-12-12
*/
@Override
public Date convert(String s) {
// 将日期字符串转换成日期对象,进行返回
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
spring-mvc.xml
<!-- 自定义类型转换器配置 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.renda.converter.DateConverter"/>
</set>
</property>
</bean>
<!-- 处理器映射器-处理器适配器。进行了功能的增强:支持 json 的读写 -->
<mvc:annotation-driven conversion-service="conversionService"/>
在 UserController
中添加方法
@RequestMapping("/converterParam")
public String converterParam(Date birthday){
System.out.println(birthday);
return "success";
}
当请求的参数 name 名称与 Controller 的业务方法参数名称不一致时,就需要通过 @RequestParam
注解显示的绑定
requestParam.jsp
<a href="${pageContext.request.contextPath}/user/findByPage?pageNo=2">
分页查询
</a>
在 UserController
中添加方法
/**
* name:匹配页面传递参数的名称
* defaultValue:设置参数的默认值
* required:设置是否必须传递该参数,默认值为 true;如果设置了默认值,值自动改为 false
*/
@RequestMapping("findByPage")
public String findByPage(
@RequestParam(name = "pageNo", defaultValue = "1", required = false) Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize) {
System.out.println(pageNum);
System.out.println(pageSize);
return "success";
}
获取请求头的数据。
在 UserController
中添加方法
@RequestMapping("/requestHeader")
public String requestHead(@RequestHeader("cookie") String cookie){
System.out.println(cookie);
return "success";
}
获取 cookie
中的数据。
在 UserController
中添加方法
@RequestMapping("/cookieValue")
public String cookieValue(@CookieValue("JSESSIONID") String jSessionId){
System.out.println(jSessionId);
return "success";
}
SpringMVC 支持使用原始 Servlet API 对象作为控制器方法的参数进行注入,常用的对象如下:
@RequestMapping("/servletAPI")
public String servletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
return "success";
}
页面跳转:
void
原始 ServletAPI
ModelAndView
返回数据:
json
返回直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转到指定页面
@RequestMapping("/returnString")
public String returnString() {
return "success";
}
可以通过 request、response 对象实现响应
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Index</title>
</head>
<body>
Hello ${username}
</body>
</html>
在 UserController
中添加方法
@RequestMapping("/returnVoid")
public void returnVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* 直接返回数据 */
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("张人大");
request.setAttribute("username", "Renda");
// 借助 request 对象完成请求转发,一次请求
// request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
// request.getRequestDispatcher("/index.jsp").forward(request, response);
// 借助 response 对象完成重定向,两次请求。WEB-INF - 安全目录:不允许外部请求直接访问该目录资源,只可以进行服务器内部转发
// response.sendRedirect(request.getContextPath() + "/index.jsp");
}
企业开发一般使用返回字符串逻辑视图实现页面的跳转,这种方式其实就是请求转发;也可以写成:forward 转发。如果用了 forward,则路径必须写成实际视图 URL,不能写逻辑视图。它相当于:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response)
使用请求转发,既可以转发到 JSP,也可以转发到其他的控制器方法。
@RequestMapping("/forward")
public String forward(Model model){
// 在模型中设置一些值
model.addAttribute("username","RendaZhang");
// 使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法
// return "forward:/product/findAll";
return "forward:/WEB-INF/pages/success.jsp";
}
Redirect 重定向:可以不写虚拟目录,SpringMVC 框架会自动拼接,并且将 Model 中的数据拼接到 URL 地址上;重定向的目标 JSP 页面不能写在 WEB-INF 目录中,否则无法找到。
@RequestMapping("/redirect")
public String redirect(Model model) {
// 底层使用的还是 request.setAttribute("username","RENDA"),
// 域范围是一次请求,所以重定向后,请求发生了变化,请求域的值无法获取
model.addAttribute("username", "RENDA");
return "redirect:/index.jsp";
}
在 Controller 中方法创建并返回 ModelAndView
对象,并且设置视图名称
/**
* model:模型:作用封装存放数据
* View :视图:用来展示数据
*/
@RequestMapping("/returnModelAndView")
public ModelAndView returnModelAndView(){
ModelAndView modelAndView = new ModelAndView();
// 设置模型数据
modelAndView.addObject("username","modelAndView 方式一");
// 设置视图名称。视图解析器解析 modelAndView 拼接前缀和后后缀
modelAndView.setViewName("success");
return modelAndView;
}
在 Controller 中方法形参上直接声明 ModelAndView
,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面
@RequestMapping("/returnModelAndView2")
public ModelAndView returnModelAndView2(ModelAndView modelAndView){
// 设置模型数据
modelAndView.addObject("username","modelAndView 方式二");
// 设置视图名称。视图解析器解析 modelAndView 拼接前缀和后后缀
modelAndView.setViewName("success");
return modelAndView;
}
如果在多个请求之间共用数据,则可以在控制器类上标注一个 @SessionAttributes
,配置需要在 session
中存放的数据范围,SpringMVC 将存放在 model
中对应的数据暂存到 HttpSession
中。
注意:@SessionAttributes
只能定义在类上
@Controller
@RequestMapping("/user") //一级访问目录
@SessionAttributes("username") // 向 request 域中(model)中存入 key 为 username 时,会同步到 session 域中
public class UserController {
...
@RequestMapping("/forward")
public String forward(Model model) {
// 在模型中设置一些值
model.addAttribute("username", "RendaZhang");
// 使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法
// return "forward:/product/findAll";
return "forward:/WEB-INF/pages/success.jsp";
}
...
@RequestMapping("/returnString")
public String returnString(){
return "success";
}
}
* 页面跳转采用返回字符串逻辑视图
forward 转发
可以通过Model向request域中设置数据
redirect 重定向
直接写资源路径即可,虚拟目录springMVC框架自动完成拼接
* 数据存储到 request 域中
Model model
model.addAttribute("username", "子慕");
requestParam.jsp
...
<%-- 引入 jquery.js --%>
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
...
当有静态资源需要加载时,比如 jQuery 文件,通过谷歌开发者工具抓包发现,没有加载到 jQuery 文件,原因是 SpringMVC 的前端控制器 DispatcherServlet
的 url-pattern
配置的是 /
(默认配置),代表对所有的静态资源都进行处理操作,这样就不会执行 Tomcat 内置的 DefaultServlet
处理,可以通过以下两种方式在 spring-mvc.xml 指定放行静态资源。
方式一:放行指定的静态资源
<!-- mapping:放行的映射路径,location:静态资源所在的目录 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/img/**" location="/img/"/>
方式二:放行全部的静态资源
<!-- 在 springmvc 配置文件中开启 DefaultServlet 处理静态资源 -->
<mvc:default-servlet-handler/>