Spring MVC
Spring MVC 是目前主流的实现 MVC 设计模式的企业级开发框架,Spring 框架的一个子模块,无需整合,开发起来更加便捷。
什么是 MVC 设计模式?
将应用程序分为 Controller、Model、View 三层,Controller 接收客户端请求,调用 Model 生成业务数据,传递给 View。
Spring MVC 就是对这套流程的封装,屏蔽了很多底层代码,开放出接口,让开发者可以更加轻松、便捷地完成基于 MVC 模式的 Web 开发。
Spring MVC 的核心组件
-
DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
-
Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
-
HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler。
-
HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
-
HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
-
HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
-
ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
-
ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。
-
Spring MVC 的工作流程
-
客户端请求被 DisptacherServlet 接收。
-
根据 HandlerMapping 映射到 Handler。
-
生成 Handler 和 HandlerInterceptor。
-
Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DisptacherServlet。
-
DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理。
-
Handler 返回一个 ModelAndView 给 DispatcherServlet。
-
DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析为物理视图 View。
-
ViewResovler 返回一个 View 给 DispatcherServlet。
-
DispatcherServlet 根据 View 进行视图渲染(将模型数据 Model 填充到视图 View 中)。
-
DispatcherServlet 将渲染后的结果响应给客户端。
Spring MVC 流程非常复杂,实际开发中很简单,因为大部分的组件不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可,真正需要开发者进行处理的只有 Handler 、View。
如何使用?
在创建maven工程时选中create from archetype(创建原型)并选中其中的maven-archetype-webapp,创建成功
在创建好的src-main目录下没有java源代码和resources资源目录,需要手动创建这两个目录,并右键->标记目录为->分别标记源码根和Resources根
- 创建 Maven 工程,pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
</dependencies>
- 在 web.xml 中配置 DispatcherServlet。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--读取spring.mvc中的信息-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!-- 自动扫描配置,ioc扫描com.southwind包中哪些类有@Compoent注解,该类扫描到IoC容器中,交由IoC管理它的对象 -->
<context:component-scan base-package="com.southwind"></context:component-scan>
<!-- 配置视图解析器,把逻辑视图解析为物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property><!--前缀-->
<property name="suffix" value=".jsp"></property><!--后缀-->
</bean>
</beans>
- 创建 Handler
package com.southwind.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//控制器@Controller注解与@Contended一样,都交给IoC管理,而且还具备控制器功能
@Controller
@RequestMapping("/hello")
public class HelloHandle {
@RequestMapping("/index")
public String index(){
System.out.println("执行了index...");
return "index";
}
}
Spring MVC 注解
- @RequestMapping
Spring MVC 通过 @RequestMapping 注解将 URL 请求与业务方法进行映射,在 Handler 的类定义处以及方法定义处都可以添加 @RequestMapping ,在类定义处添加,相当于客户端多了一层访问路径。
- @Controller
@Controller 在类定义处添加,将该类交个 IoC 容器来管理(结合 springmvc.xml 的自动扫描配置使用),同时使其成为一个控制器,可以接收客户端请求。
package com.southwind.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hello")
public class HelloHandler {
@RequestMapping("/index")
public String index(){
System.out.println("执行了index...");
return "index";
}
}
- @RequestMapping 相关参数
1、value:指定 URL 请求的实际地址,是 @RequestMapping 的默认值。
@RequestMapping("/index")
public String index(){
System.out.println("执行了index...");
return "index";
}
等于
@RequestMapping(value="/index")
public String index(){
System.out.println("执行了index...");
return "index";
}
2、method:指定请求的 method 类型,GET、POST、PUT、DELET。
@RequestMapping(value = "/index",method = RequestMethod.GET)
public String index(){
System.out.println("执行了index...");
return "index";
}
上述代码表示 index 方法只能接收 GET 请求。
3、params:指定请求中必须包含某些参数,否则无法调用该方法。
@RequestMapping(value = "/index",method = RequestMethod.GET,params = {"name","id=10"})
public String index(){
System.out.println("执行了index...");
return "index";
}
上述代码表示请求中必须包含 name 和 id 两个参数,同时 id 的值必须是 10。
关于参数绑定,在形参列表中通过添加 @RequestParam 注解完成 HTTP 请求参数与业务方法形参的映射。
@RequestMapping(value = "/index",method = RequestMethod.GET,params = {"name","id=10"})
public String index(@RequestParam("name") String str,@RequestParam("id") int age){
System.out.println(str);
System.out.println(age);
System.out.println("执行了index...");
return "index";
}
上述代码表示将请求的参数 name 和 id 分别赋给了形参 str 和 age ,同时自动完成了数据类型转换,将 “10” 转为了 int 类型的 10,再赋给 age,这些工作都是由 HandlerAdapter 来完成的。
Spring MVC 也支持 RESTful 风格的 URL。
传统类型:http://localhost:8080/hello/index?name=zhangsan&id=10
REST:http://localhost:8080/hello/index/zhangsan/10
@RequestMapping("/rest/{name}/{id}")
public String rest(@PathVariable("name") String name,@PathVariable("id") int id){
System.out.println(name);
System.out.println(id);
return "index";
}
通过 @PathVariable 注解完成请求参数与形参的映射。
- 映射 Cookie
Spring MVC 通过映射可以直接在业务方法中获取 Cookie 的值。
@RequestMapping("/cookie")
public String cookie(@CookieValue(value = "JSESSIONID") String sessionId){
System.out.println(sessionId);
return "index";
}
- 使用 JavaBean 绑定参数
Spring MVC 会根据请求参数名和 JavaBean 属性名进行自动匹配,自动为对象填充属性值,同时支持及联属性。
package com.southwind.entity;
import lombok.Data;
@Data
public class Address {
private String value;
}
package com.southwind.entity;
import lombok.Data;
@Data
public class User {
private long id;
private String name;
private Address address;
}
<%--
Created by IntelliJ IDEA.
User: southwind
Date: 2019-03-13
Time: 15:33
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--Spring MVC 会根据请求参数名和 JavaBean 属性名进行自动匹配,自动为对象填充属性值,同时支持及联属性。--%>
<form action="/hello/save" method="post">
用户id:<input type="text" name="id"/><br/>
用户名:<input type="text" name="name"/><br/>
用户地址:<input type="text" name="address.value"/><br/>
<input type="submit" value="注册"/>
</form>
</body>
</html>
@RequestMapping(value = "/save",method = RequestMethod.POST)
public String save(User user){
System.out.println(user);
return "index";
}
如果出现中文乱码问题,只需在 web.xml 添加 Spring MVC 自带的过滤器即可。
<!--过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置字符格式为UTF-8-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- 拦截所有请求-->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- JSP 页面的转发和重定向:
Spring MVC 默认是以转发的形式响应 JSP。
1、转发
@RequestMapping("/forward")
public String forward(){
return "forward:/index.jsp";
// return "index";
}
2、重定向
@RequestMapping("/redirect")
public String redirect(){
return "redirect:/index.jsp";
}
Spring MVC 数据绑定
数据绑定:在后端的业务方法中直接获取客户端 HTTP 请求中的参数,将请求参数映射到业务方法的形参中,Spring MVC 中数据绑定的工作是由 HandlerAdapter 来完成的。
- 基本数据类型
@RequestMapping("/baseType")
@ResponseBody
public String baseType(int id){
return id+"index";
}
@ResponseBody 表示 Spring MVC 会直接将业务方法的返回值响应给客户端,如果不加 @ResponseBody 注解,Spring MVC 会将业务方法的放回值传递给 DispatcherServlet,再由 DisptacherServlet 调用 ViewResolver 对返回值进行解析,映射到一个 JSP 资源。
- 包装类
@RequestMapping("/packageType")
@ResponseBody
public String PackageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
return id+"index";
}
包装类可以接收 null,当 HTTP 请求没有参数时,使用包装类定义形参的数据类型,程序不会抛出异常。
@RequestParam
value = “num”:将 HTTP 请求中名为 num 的参数赋给形参 id。
requried:设置 num 是否为必填项,true 表示必填,false 表示非必填,可省略。
defaultValue = “0”:如果 HTTP 请求中没有 num 参数,默认值为0.
- 数组
@RestController
@RequestMapping("/data")
public class DataBindHandler {
@RequestMapping("/array")
public String array(String[] name){
String str = Arrays.toString(name);
return str;
}
}
@RestController 表示该控制器会直接将业务方法的返回值响应给客户端,不进行视图解析。
@Controller 表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析,如果只需要将数据响应给客户端,而不需要进行视图解析,则需要在对应的业务方法定义处添加 @ResponseBody。
@RestController
@RequestMapping("/data")
public class DataBindHandler {
@RequestMapping("/array")
public String array(String[] name){
String str = Arrays.toString(name);
return str;
}
}
等同于
@Controller
@RequestMapping("/data")
public class DataBindHandler {
@RequestMapping("/array")
@ResponseBody
public String array(String[] name){
//把数组转为字符串形式保存
String str = Arrays.toString(name);
return str;
}
}
- List
Spring MVC 不支持 List 类型的直接转换,需要对 List 集合进行包装。
集合封装类
package com.southwind.entity;
import lombok.Data;
import java.util.List;
@Data
public class UserList {
private List<User> users;
}
JSP
<%--
Created by IntelliJ IDEA.
User: southwind
Date: 2019-03-14
Time: 09:12
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/data/list" method="post">
用户1编号:<input type="text" name="users[0].id"/><br/>
用户1名称:<input type="text" name="users[0].name"/><br/>
用户2编号:<input type="text" name="users[1].id"/><br/>
用户2名称:<input type="text" name="users[1].name"/><br/>
用户3编号:<input type="text" name="users[2].id"/><br/>
用户3名称:<input type="text" name="users[2].name"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
业务方法
@RequestMapping("/list")
@ResponseBody
public String list(UserList userList){
//使用字符流,提高字符读取输出速度
StringBuffer str = new StringBuffer();
//循环遍历,userList集合对象,所以要userList.getUsers()得到集合中的对象
for(User user:userList.getUsers()){
//把user输入到字符str中
str.append(user);
}
return str.toString();
}
处理 @ResponseBody 后台给前台传信息时中文乱码,在 springmvc.xml 中配置消息转换器。
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- Map
自定义封装类
package com.southwind.entity;
import lombok.Data;
import java.util.Map;
@Data
public class UserMap {
private Map<String,User> users;
}
业务方法
@RequestMapping("/map")
public String map(UserMap userMap){
StringBuffer str = new StringBuffer();
for(String key:userMap.getUsers().keySet()){
User user = userMap.getUsers().get(key);
str.append(user);
}
return str.toString();
}
JSP
<%--
Created by IntelliJ IDEA.
User: southwind
Date: 2019-03-14
Time: 09:12
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/data/map" method="post">
用户1编号:<input type="text" name="users['a'].id"/><br/>
用户1名称:<input type="text" name="users['a'].name"/><br/>
用户2编号:<input type="text" name="users['b'].id"/><br/>
用户2名称:<input type="text" name="users['b'].name"/><br/>
用户3编号:<input type="text" name="users['c'].id"/><br/>
用户3名称:<input type="text" name="users['c'].name"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- JSON
客户端发生 JSON 格式的数据,直接通过 Spring MVC 绑定到业务方法的形参中。
处理 Spring MVC 无法加载静态资源,在 web.xml 中添加配置即可。
<!-- 由于DispatcherServlet无法处理json类型的jsp,所以转换为默认处理-->
<!-- *.js表示只要是以js为后缀的不交给DispatcherServlet处理,交给默认的方式处理-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
JSP
<%--
Created by IntelliJ IDEA.
User: southwind
Date: 2019-03-14
Time: 10:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function(){
var user = {
"id":1,
"name":"张三"
};
$.ajax({
url:"/data/json",
data:JSON.stringify(user),
type:"POST",
contentType:"application/json;charset=UTF-8",
dataType:"JSON",
success:function(data){
alert(data.id+"---"+data.name);
}
})
});
</script>
</head>
<body>
</body>
</html>
业务方法
/**
*@RequestBody接收客户端的数据
*@ResponseBody把数据直接返回到客户端
* jsp前端把id=1,name="张三"传进user,再把user的id,name改变,返回去
*/
@RequestMapping("/json")
public User json(@RequestBody User user){
System.out.println(user);
user.setId(6);
user.setName("张六");
return user;
}
Spring MVC 中的 JSON 和 JavaBean 的转换需要借助于 fastjson,pom.xml 引入相关依赖。
<!--json所用,把json数据转换为java对象-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.32</version>
</dependency>
springmvc.xml 添加 fastjson 配置。
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!-- 配置fastjson /json用的-->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
Spring MVC 模型数据解析
JSP 四大作用域对应的内置对象:pageContext、request、session、application。
模型数据的绑定是由 ViewResolver 来完成的,实际开发中,我们需要先添加模型数据,再交给 ViewResolver 来绑定。
Spring MVC 提供了以下几种方式添加模型数据:
- Map
- Model
- ModelAndView
- @SessionAttribute
- @ModelAttribute
一.将模式数据绑定到 request 对象。
1、Map
@RequestMapping("/map")
public String map(Map<String,User> map){
User user = new User();
user.setId(1L);
user.setName("张三");
//把user加到域对象中
map.put("user",user);
return "view";
}
JSP
<%--
Created by IntelliJ IDEA.
User: southwind
Date: 2019-03-14
Time: 11:36
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!--是否解析EL表达式,false,加上这句话就会对EL中内容进行解析,不直接展示文本信息-->
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${requestScope.user}
</body>
</html>
2、Model
@RequestMapping("/model")
public String model(Model model){
User user = new User();
user.setId(1L);
user.setName("张三");
model.addAttribute("user",user);
return "view";
}
3、ModelAndView
@RequestMapping("/modelAndView")
public ModelAndView modelAndView(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("view");
return modelAndView;
}
@RequestMapping("/modelAndView2")
public ModelAndView modelAndView2(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
View view = new InternalResourceView("/view.jsp");
modelAndView.setView(view);
return modelAndView;
}
@RequestMapping("/modelAndView3")
public ModelAndView modelAndView3(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView("view");
modelAndView.addObject("user",user);
return modelAndView;
}
@RequestMapping("/modelAndView4")
public ModelAndView modelAndView4(){
User user = new User();
user.setId(1L);
user.setName("张三");
View view = new InternalResourceView("/view.jsp");
ModelAndView modelAndView = new ModelAndView(view);
modelAndView.addObject("user",user);
return modelAndView;
}
@RequestMapping("/modelAndView5")
public ModelAndView modelAndView5(){
User user = new User();
user.setId(1L);
user.setName("张三");
Map<String,User> map = new HashMap<>();
map.put("user",user);
ModelAndView modelAndView = new ModelAndView("view",map);
return modelAndView;
}
@RequestMapping("/modelAndView6")
public ModelAndView modelAndView6(){
User user = new User();
user.setId(1L);
user.setName("张三");
Map<String,User> map = new HashMap<>();
map.put("user",user);
View view = new InternalResourceView("/view.jsp");
ModelAndView modelAndView = new ModelAndView(view,map);
return modelAndView;
}
@RequestMapping("/modelAndView7")
public ModelAndView modelAndView7(){
User user = new User();
user.setId(1L);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView("view","user",user);
return modelAndView;
}
@RequestMapping("/modelAndView8")
public ModelAndView modelAndView8(){
User user = new User();
user.setId(1L);
user.setName("张三");
View view = new InternalResourceView("/view.jsp");
ModelAndView modelAndView = new ModelAndView(view,"user",user);
return modelAndView;
}
4、HttpServletRequest
@RequestMapping("/request")
public String request(HttpServletRequest request){
User user = new User();
user.setId(1L);
user.setName("张三");
request.setAttribute("user",user);
return "view";
}
5、@ModelAttribute
- 定义一个方法,该方法专门用来返回要填充到模型数据中的对象。
@ModelAttribute
public User getUser(){
User user = new User();
user.setId(1L);
user.setName("张三");
return user;
}
@ModelAttribute
public void getUser(Map<String,User> map){
User user = new User();
user.setId(1L);
user.setName("张三");
map.put("user",user);
}
@ModelAttribute
public void getUser(Model model){
User user = new User();
user.setId(1L);
user.setName("张三");
model.addAttribute("user",user);
}
- 业务方法中无需再处理模型数据,只需返回视图即可。
@RequestMapping("/modelAttribute")
public String modelAttribute(){
return "view";
}
二.将模型数据绑定到 session 对象
1、直接使用原生的 Servlet API。
@RequestMapping("/session")
public String session(HttpServletRequest request){
HttpSession session = request.getSession();
User user = new User();
user.setId(1L);
user.setName("张三");
session.setAttribute("user",user);
return "view";
}
@RequestMapping("/session2")
public String session2(HttpSession session){
User user = new User();
user.setId(1L);
user.setName("张三");
session.setAttribute("user",user);
return "view";
}
2、@SessionAttribute全局的,放在类上面
@Controller
@RequestMapping("view")
//只要有域对象名为user的对象就同时也把它存到session中
//@SessionAttributes(value = {"user","address"})
//只要有User类对应的对象被添加到域对象中,就同时把它也添加到session中
//@SessionAttributes(types = {User.class,address.class})
// 但是一般不用,因为大多数只向request中添加,向添加session时单独添加,230行
public class ViewHandle {}
对于 ViewHandler 中的所有业务方法,只要向 request 中添加了 key = “user”、key = “address” 的对象时,Spring MVC 会自动将该数据添加到 session 中,保存 key 不变。
@SessionAttributes(types = {User.class,Address.class})
public class ViewHandler {
}
对于 ViewHandler 中的所有业务方法,只要向 request 中添加了数据类型是 User 、Address 的对象时,Spring MVC 会自动将该数据添加到 session 中,保存 key 不变。
三.将模型数据绑定到 application 对象
/* application域范围对应的数据类型是ServletContext
* 必须通过request取出ServletContext对象
*/
@RequestMapping("/application")
public String application(HttpServletRequest request){
ServletContext application = request.getServletContext();
User user = new User();
user.setId(1L);
user.setName("张三");
application.setAttribute("user",user);
return "view";
}