Spring MVC

Spring MVC 是Spring Framework提供的Web组件,全程是Spring Web MVC,是目前主流的实现MVC设计模式的框架,提供前端路由映射、视图解析等功能。

Java Web开发者必须要掌握的技术框架。

Spring MVC功能

MVC:Controller(控制层)、Model(模型层)、View(视图层)

流程:Controller接收客户端请求,调用相关业务层组件,产出一个Model,获取业务数据并且返回给Controlelr,Controller再结合View,完成业务数据的视图层渲染,并将结果响应给客户端。

image-20240727235543920

Spring MVC对这套MVC流程进行封住哪个,帮助开发者屏蔽底层代码,并且开放出相关接口供开发者调用,让MVC开发变得更加简单方便。

SpringMVC实现原理

核心组件

  • DispatcherServlet: 前置控制器,负责 调度其他组件的执行,可以降低不同组件之间的耦合性,是整个SpringMVC的核心模块
  • Handler:处理器,完成具体的业务逻辑,相当于Servlet。
  • HandlerMapping:DispatcherServlet是通过HandlerMapping将请求映射到不同的Handler
  • HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口来完成。
  • HandlerExecuionChain:处理器执行链,包括两部分内容,Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器进行设置)。
  • HandlerAdpter:处理器适配器,Handler执行业务方法之前需要进行一些列的曹组哦,包括表单的数据验证,数据类型的转化,将表单数据封装到POJO等,这一些列操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler
  • ModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet.
  • ViewResolver:视图解析器,DispatcherServlet通过他将逻辑视图解析为物理视图,最终将渲染的结果响应给客户端。

工作流程

1、客户端请求被DispatcherServlet接收。

2、根据HandlerMapping映射到Handler.

3、生成Handler和HandlerInterceptor。

4、Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet。

5、DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理。

6、返回一个ModelAndView对象给DispatcherServlet。

7、DispatcherServlet将获取的ModelAndView对象传给ViewResolver视图解析器,将逻辑视图解析成物理视图

8、ViewResolver返回一个View给DispatcherServlet。

9、DispatcherServlet根据View进行视图渲染(将模型数据填充到视图中)。

10、DispatcherServlet将渲染之后的视图响应给客户端。

image-20240728171555544

Spring MVC 具体使用

1、创建Maven工程,在pom.xml中添加相关依赖

1
2
3
4
5
6
<!--Spring mvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.4</version>
</dependency>

2、在web.xml中配置springmvc的DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

3、在springmvc中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 配置自动扫描 -->
<context:component-scan base-package="com.bitzh"></context:component-scan>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/"></property>
<!--后缀-->
<property name="suffix" value=".jsp"></property>
</bean>

</beans>

4、创建Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bitzh.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @Auther: oyy0v0
* @Date: 2024/7/28 - 07 - 28 - 17:30
* @Description: com.bitzh.controller
* @version: 1.0
*/
@Controller
public class HelloHandler {

@RequestMapping("/index")
public String index(){
System.out.println("接收到了请求");
//返回逻辑视图
return "index";
}
}

流程梳理

1、DispatcherServlet接收到URL请求index,结合@RequestMapping(“/index”)注释将请求交给index业务方法进行处理。

2、执行index业务方法,控制台打印日志,并且返回index字符串(逻辑视图)

3、结合spingmvc.xml中的视图解析器配置,找到目标资源:/index.jsp,即根目录下的index.jsp文件,将该jsp资源返回给客户端完成响应。

SpringMVC环境搭建成功。

Spring MVC常用注解

Spring MVC 通过@RequestMapping注解将URL请求与业务方法进行映射,在控制器的类定义处和方法定义处都可以添加@RequestMapping,在类定义处添加相当于多了一层访问路径。

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/hello")
public class HelloHandler {

@RequestMapping("/index")
public String index(){
System.out.println("接收到了请求");
//返回逻辑视图
return "index";
}
}

http://localhost:8080/hello/index

@RequestMapping 常用参数

  • value:指定URL请求的实际地址,是@RequestMapping的默认值
  • method:指定请求的method的类型,包括GET、POST、PUT、DELETE等。
1
2
3
4
5
6
@RequestMapping(value = "/index" ,method = RequestMethod.POST)
public String index(){
System.out.println("接收到了请求");
//返回逻辑视图
return "index";
}

上述代码表示只有POST请求可以访问该方法,若使用其他请求访问,直接抛出异常405.

  • params:指定request请求中必须包含的参数值,若不包含,无法调用该方法。
1
2
3
4
5
6
@RequestMapping(value = "/index" ,method = RequestMethod.POST,params = {"id=1","name=tom"})
public String index(){
System.out.println("接收到了请求");
//返回逻辑视图
return "index";
}

上述代码表示request请求中必须包含,name和id两个参数,并且id的值必须为1,name的值必须为tom才可以调用,否则抛出400异常。

参数绑定

params是对URL请求参数进行限制,不满足条件的URL无法访问该方法,需要在业务方法中获取URL的参数值 。

1、在业务方法定义时,声明参数列表 。

2、给参数列表添加@RequestParam注解进行绑定

1
2
3
4
5
6
@RequestMapping(value = "/index" ,method = RequestMethod.POST)
public String index(@RequestParam("num") Integer id,@RequestParam("str") String name){
System.out.println("接收到了请求,参数是:id="+id+","+"name="+name);
//返回逻辑视图
return "index";
}

SpringMVC 可以自动完成数据类型转换,该工作是由HandlerAdaapter来完成的,将路径发送的num和str绑定到id和name上,并且将String类型转换为Integer类型,这都是自动完成的。

Spring MVC 也支持RESTful 风格的URL参数获取

传 统的URL:localhost:8080/hello/index?id=18&name=tom

RESTful URL: localhost:8080/hello/index/1/tom

1
2
3
4
5
@RequestMapping("/index/{id}/{name}")
public String restful(@PathVariable("id") Integer id, @PathVariable("name")String name){
return "index";

}

将参数列表的注解改为@PathVariable(“id”)即可

映射Cookie

1
2
3
4
5
6
@RequestMapping("/cookie")
public String getCookie(@CookieValue("JSESSIONID") String sessionId){
System.out.println(sessionId);
return "index";

}

使用POJO绑定参数

Spring MVC 会根据请求的参数名和POJO的属性名进行匹配,自动为该对象填充属性值,并且支持属性级联。

如果出现中文乱码,可以通过配置过滤器来解决,在web.xml中添加配置即可。

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Address

1
2
3
4
5
@Data
public class Address {
private Integer code;
private String value;
}

User

1
2
3
4
5
6
@Data
public class User {
private Integer id;
private String name;
private Address address;
}

addUser.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/29
Time: 15:17
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="/hello/add" method="post">
<table>
<tr>
<td>编号:</td>
<td>
<input type="text" name="id"/>
</td>
</tr>
<tr>
<td>姓名:</td>
<td>
<input type="text" name="name">
</td>
</tr>
<tr>
<td>地址编号:</td>
<td>
<input type="text" name="address.code">
</td>
</tr>
<tr>
<td>地址信息:</td>
<td>
<input type="text" name="address.value">
</td>
</tr>
<tr>
<td>
<input type="submit" value="提交">
</td>
</tr>
</table>

</form>

</body>
</html>

Handler

1
2
3
4
5
@RequestMapping(value = "/add" ,method = RequestMethod.POST)
public String add(User user){
System.out.println(user);
return "index";
}

主体对象可以没有无参构造但是级联对象必须有无参构造,因为主体对象可以用其他构造器构造。

JSP页面的转发和重定向

Spring MVC默认以转发的形式响应JSP,可以手动修改

1
2
3
4
5
@RequestMapping("/hello/{id}/{name}")
public String restful(@PathVariable("id") Integer id, @PathVariable("name")String name){
return "redirect:/index.jsp";

}

设置重定向的时候不能写逻辑视图,必须写明资源的物理路径。

转发

1
2
3
4
5
@RequestMapping("/hello/{id}/{name}")
public String restful(@PathVariable("id") Integer id, @PathVariable("name")String name){
return "forward:/index.jsp";

}

等同于

1
2
3
4
@RequestMapping("/hello/{id}/{name}")
public String restful(@PathVariable("id") Integer id, @PathVariable("name")String name){
return "index";
}

Spring MVC 数据绑定

数据绑定:在后台业务方法中,直接获取前端HTTP请求中的参数。

HTTP请求传输的参数都是String类型的,Handler业务方法中的参数是开发者指定的数据类型,Integer、Object类型等等,因此需要进行数据类型的转换。

Spring MVC的HandlerAdapter组件会在执行Handler业务方法之前,完成参数的绑定,开发者直接使用即可。

  • 基本数据类型
1
2
3
4
5
@RequestMapping("/baseType")
@ResponseBody
public String basetype(int id){
return "id:"+id;
}

客户端的HTTP请求中必须包含id参数,否则抛出500异常,因为id不能为null。

同时id的值必须为数值且必须为整数否则抛出400异常。

  • 包装类
1
2
3
4
5
@RequestMapping("/packageType")
@ResponseBody
public String packageType(Integer id){
return "id:"+id;
}

如果HTTP请求中没有包含id参数,不会报错,id的值就是Null,会直接返回id:null给客户端。

但是如果id=a或者id=1.5,同样会抛出400异常,因为数据类型无法匹配。

可以给参数列表添加@RequestParam注解,可以对参数进行相关设置。

1
2
3
4
5
@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "id",required = true,defaultValue = "0") Integer id){
return "id:"+id;
}
  • value=“id” 将HTTP请求中名为id的参数与Handler业务方法中的形参进行映射。
  • require:true 表示id参数为必填,false表示非必填
  • defaultValue=“0” 表示没有id参数的话默认值为0

数组绑定

1
2
3
4
5
6
7
8
9
@RequestMapping("/arrayType")
@ResponseBody
public String arrayType(@RequestParam("names") String[] names){
StringBuffer stringBuffer = new StringBuffer();
for(String str : names){
stringBuffer.append(str).append(" ");
}
return "names:"+stringBuffer.toString();
}

POJO

跟之前的一样但是如果直接返回给前端的不是一个页面而是一个对象的话,需要解决乱码的话就需要在rescouces下的springmvc.xml中配置消息转换器,

1
2
3
4
5
6
7
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

List集合

Spring MVC不支持List类型的直接转换,需要包装成Object。

List的自定义包装类

1
2
3
4
5
6
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserList {
private List<User> users;
}

业务方法

1
2
3
4
5
6
7
8
9
@RequestMapping("/listType")
@ResponseBody
public String listType(UserList users){
StringBuffer stringBuffer = new StringBuffer();
for(User user: users.getUsers()){
stringBuffer.append(user);
}
return "用户:"+stringBuffer.toString();
}

jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/30
Time: 17: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="/hello/listType" method="post">
用户1ID:<input type="text" name="users[0].id"><br/>
用户1姓名:<input type="text" name="users[0].name"><br/>
用户2ID:<input type="text" name="users[1].id"><br/>
用户2姓名:<input type="text" name="users[1].name"><br/>
用户3ID:<input type="text" name="users[2].id"><br/>
用户3姓名:<input type="text" name="users[2].name"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>

需要注意的是User类中一定要有无参构造,否则抛出异常

json格式的封装

前端给我们传来json格式的对象然后我们后端封装成对象。

JSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/30
Time: 17:39
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-2.1.1.min.js"></script>
<script type="text/javascript">
$(function (){
var user = {
"id":1 ,
"name":"zhangsan"
};
$.ajax({
url:"/hello/jsonType",
data:JSON.stringify(user),
type:"POST",
contentType:"application/json;charset=UTF-8",
dataType:"JSON",
success:function (data){
var obj = eval("("+data+")")
alert(obj.id)
alert(obj.name)
}
})
})
</script>
</head>
<body>

</body>
</html>

注意:

  • JSON数据必须用JSON.stringstringify()方法转换成字符串
  • contentType:”application/json;charset=UTF-8”不能省略,告诉后端传回来的是json

业务方法

1
2
3
4
5
6
7
@RequestMapping("/jsonType")
@ResponseBody
public String jsonType(@RequestBody User user){
System.out.println(user);
return user.toString();

}

@RequestBody注解

读取HTTP请求的参数,通过SpringMVC提供的HttpMessageConverter接口将读取的参数转换为JSON,XML格式的数据,绑定到业务方法的形参中

@ResponseBody注解

将业务方法返回的对象,通过HttpMessageConverter接口转为制定的格式数据,比如JSON,XML等,响应给客户端。

需要使用组件结合@RequestBody注解将JSON转为Java Bean,这里使用Fastjson,其优势是如果属性为空就不会将其转换为JSON

如何使用Fastjson

1、pom.xml中添加相关依赖

1
2
3
4
5
6
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>

2、springmvc.xml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?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">


<!-- 配置自动扫描 -->
<context:component-scan base-package="com.bitzh"></context:component-scan>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/"></property>
<!--后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!--fastjson-->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
</mvc:message-converters>
</mvc:annotation-driven>



</beans>

image-20240731152758796

Spring MVC 视图层解析

调用Web资源给域对象传值,域对象就是jsp的四个作用域

page

request

session

application

业务数据的绑定是指将业务数据绑定给JSP域对象,业务数据的绑定是由ViewResolver来完成的,我们先添加业务数据,再交给ViewResolver来绑定,因此学习的重点在于如何添加业务数据,Spring MVC 提供了以下几种方式添加业务数据:

  • Map
  • Moudel
  • ModelAndView
  • @SessionAttribute
  • @ModelAttribute
  • Servlet API

业务数据绑定到request域对象

Map

Spring MVC在调用业务方法之前会创建一个隐含对象作为业务数据的存储容器,设置业务方法的入参为Map类型,Spring MVC会将隐含对象的引用传递给入参。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("/view")
public class ViewHandler {

@RequestMapping("/map")
public String map(Map<String,Object> map){
User user = new User();
user.setId(1);
user.setName("张三");
map.put("user",user);
return "show";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/31
Time: 23:48
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${user}
</body>
</html>

Model

Model和Map类似,业务方法通过入参来完成业务方法的绑定

1
2
3
4
5
6
7
8
@RequestMapping("/model")
public String model(Model model){
User user = new User();
user.setId(1);
user.setName("张三");
model.addAttribute("user",user);
return "show";
}

ModelAndView

与Map或者Model不同的是,ModelAndView补单包含业务数据,同时也封装了视图信息,如果使用ModelAndeView来处理业务,业务方法的返回值必须是ModelAndView对象。

业务方法中对ModelAndView进项两个操作:

  • 填充业务数据
  • 绑定视图信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.bitzh.controller;

import com.bitzh.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.InternalResourceView;

import java.util.HashMap;
import java.util.Map;

/**
* @Auther: oyy0v0
* @Date: 2024/7/31 - 07 - 31 - 23:46
* @Description: com.bitzh.controller
* @version: 1.0
*/
@Controller
@RequestMapping("/view")
public class ViewHandler {

@RequestMapping("/map")
public String map(Map<String,Object> map){
User user = new User();
user.setId(1);
user.setName("张三");
map.put("user",user);
return "show";
}
@RequestMapping("/model")
public String model(Model model){
User user = new User();
user.setId(1);
user.setName("张三");
model.addAttribute("user",user);
return "show";
}

@RequestMapping("/mav1")
public ModelAndView modelAndView1(){
ModelAndView modelAndView = new ModelAndView();
User user = new User();
user.setId(1);
user.setName("张三");
//填充业务数据
modelAndView.addObject("user",user);
//绑定视图信息
modelAndView.setViewName("show");
return modelAndView;
}

@RequestMapping("/mav2")
public ModelAndView modelAndView2(){
ModelAndView modelAndView = new ModelAndView();
User user = new User();
user.setName("王五");
user.setId(2);
modelAndView.addObject("user",user);
View view = new InternalResourceView("/show.jsp");
modelAndView.setView(view);
return modelAndView;

}

@RequestMapping("/mav3")
public ModelAndView modelAndView3(){
ModelAndView modelAndView = new ModelAndView("show");
User user = new User();
user.setId(3);
user.setName("李四");
modelAndView.addObject("user",user);
return modelAndView;
}

@RequestMapping("/mav4")
public ModelAndView modelAndView4(){
View view = new InternalResourceView("/show.jsp");
ModelAndView modelAndView = new ModelAndView(view);
User user = new User();
user.setId(4);
user.setName("张三");
modelAndView.addObject("user",user);
return modelAndView;
}

@RequestMapping("/mav5")
public ModelAndView modelAndView5(){
Map<String,Object> map = new HashMap<>();
User user = new User();
user.setId(4);
user.setName("张三");
map.put("user",user);
ModelAndView modelAndView = new ModelAndView("show",map);
return modelAndView;

}

@RequestMapping("/mav6")
public ModelAndView modelAndView6(){
Map<String,Object> map = new HashMap<>();
User user = new User();
user.setId(4);
user.setName("张三");
map.put("user",user);
View view = new InternalResourceView("/show.jsp");
ModelAndView modelAndView = new ModelAndView(view,map);
return modelAndView;
}
@RequestMapping("/mav7")
public ModelAndView modelAndView7(){
User user = new User();
user.setId(4);
user.setName("张三");
ModelAndView modelAndView = new ModelAndView("show","user",user);
return modelAndView;
}

@RequestMapping("/mav8")
public ModelAndView modelAndView8(){
User user = new User();
user.setId(4);
user.setName("张三");
View view = new InternalResourceView("/show.jsp");
ModelAndView modelAndView = new ModelAndView(view,"user",user);
return modelAndView;
}

}

HttpServletRequest

Spring MVC 可以在业务方法中直接获取Servlet原生Web资源,只需要在方法定义的时候添加HttpServletRequest入参即可,在方法体中直接使用request对象

!!!注意在最新版的springmvc中要导入jakarta的servlet而不是javax的依赖

1
2
3
4
5
6
<!--servlet-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
1
2
3
4
5
6
7
8
@RequestMapping("/request")
public String request(HttpServletRequest request){
User user = new User();
user.setId(1);
user.setName("张三");
request.setAttribute("user",user);
return "show";
}

这种方式是直接把参数放到request中,上面是通过Map或者ModelAndView来放入

Spring MVC 还可以通过@ModelAttribute注解的方式添加业务数据,具体使用步骤:

  • 定义一个方法,该方法用来放好要填充到业务数据中的对象
  • 给该方法添加@ModelAttribute注解,不是响应请求的业务方法。
1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/modelAttribute")
public String modelAttrubute(){
return "show";
}
@ModelAttribute
public User getUser(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}

@MoudelAtttribute注解的作用是,当Handler接受到一个请求之后,无论调用哪个业务方法,都会优先调用@ModelAttribute注解修饰的方法,并将其返回值作为业务数据,在进入到业务方法,

不需要返回业务数据,即使返回业务数据,也会被@ModelAttribute注解修饰的方法返回的业务数据覆盖。

域对象中存值都是以key-value的形式存储的,那么此时的key值是什么?默认值是业务数据对应的类的类名首字母小写之后的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/31
Time: 23:48
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>show</title>
</head>
<body>
${user}
</body>
</html>

如果说getUser没有返回值,则必须手动在该方法中填充业务数据,使用Map或者Model都可以。

1
2
3
4
5
6
7
@ModelAttribute
public void getUser(Model model){
User user = new User();
user.setId(1);
user.setName("张三");
model.addAttribute("user",user);
}

如果同时存在两个@ModelAttribute注解方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ModelAttribute
public User getUser(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
@ModelAttribute
public void getUser(Model model){
User user = new User();
user.setId(1);
user.setName("张三");
model.addAttribute("user",user);
}

直接给Model对象直接进行装载的优先级更高。

业务数据绑定到session域对象

  • 直接使用HttpSession
1
2
3
4
5
6
7
8
9
@RequestMapping("/session")
public String session(HttpSession session){
User user = new User();
user.setId(1);
user.setName("张三");
session.setAttribute("user",user);
return "show";

}

jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/7/31
Time: 23:48
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>show</title>
</head>
<body>
${sessionScope.user}
</body>
</html>

使用原生API的方式

@SessionAttribute

@SessionAttribute不是给方法添加的而是给类添加的,无论类中的那个业务方法被访问,将业务数据绑定到request域对象的同时也会绑定到session与对象中,也就是说request和session同时存在,前提是request中的key和@SessionAttribute的value值一样

1
2
3
4
5
@SessionAttributes(value = "user")
//@SessionAttributes(types = User.class) 两种方法都可以
public class ViewHandler {
。。。
}

@SessionAttribute还可以同时绑定多个业务数据

1
2
3
@Controller
@SessionAttribute(value = {"user","address"})
//@SessionAttributes(types = {User.class,Address.class}) 两种方法都可以

Spring MVC的自定义数据类型转换器

Spring MVC默认的转换器只能将String的数据转换成包装类基本类型如果遇到其他数据类型就只能使用自定义类型转换器了。

1、创建一个DateConverter类,并且实现org.springframework.core.converter.converter.Converter接口,这样他就成为了一个自定义数据类型转换器,同时需要指定泛型,表示将String类型转为Date类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.bitzh.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* @Auther: oyy0v0
* @Date: 2024/8/2 - 08 - 02 - 4:20
* @Description: com.bitzh.converter
* @version: 1.0
*/
public class DateConverter implements Converter<String, Date> {

private String pattern;

public DateConverter(String pattern){
this.pattern = pattern;
}

@Override
public Date convert(String source) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
try {
simpleDateFormat.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}

2、要在springmvc.xml中配置一个conversionService bean,这个bean是org.springframework.ontext.support.ConversionServiceFactoryBean的实例化对象,同时bean中必须包含一个converters属性,在其中注册所有需要使用的自定义转换器。

1
2
3
4
5
6
7
8
9
10
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" >
<list>
<bean class="com.bitzh.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

默认是单例模式

StudentConverter

1
2
3
4
5
6
7
8
9
10
11
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" >
<list>
<bean class="com.bitzh.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
<bean class="com.bitzh.converter.StudentConverter"></bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
1
2
3
4
5
6
@RequestMapping("/student")
@ResponseBody
public String student(Student student, HttpServletResponse response){
response.setContentType("text/json;charset=UTF-8");
return student.toString();
}
1
2
3
4
5
6
7
8
9
10
11
public class StudentConverter implements Converter<String, Student> {
@Override
public Student convert(String source) {
String[] args = source.split("-");
Student student = new Student();
student.setId(Integer.parseInt(args[0]));
student.setAge(Integer.parseInt(args[2]));
student.setName(args[1]);
return student;
}
}

如果需要将业务数据转换成JSON,中文乱码需要再业务方法中通过设置respense的编码格式来解决,springmvc.xml中的bean不起作用,如果不需要将业务数据转成JSON,springmvc.xml的配置可以完成中文乱码的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" >
<list>
<bean class="com.bitzh.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
<bean class="com.bitzh.converter.StudentConverter"></bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
<!--消息转换器-->
<mvc:message-converters >
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!--fastjson-->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
</mvc:message-converters>
</mvc:annotation-driven>

Spring MVC 与RESTful的集成

  • 什么是RESTful?

RESTful是当前比较流行的一种互联网软件架构模型,通过统一的规范完成不同终端的数据访问和交互,REST全程是Representational State Transfer(资源表现层状态转换)。

RESTful优点:结构清晰、有统一标准、扩展性好。

  • Resources

资源指的是网络中的某个具体文件,类型不限可以使文本可以是图片,可以是视频,音频,数据流等,是网络中真实存在的一个实体,如何来获取?可以通过统一资源定位符找到,URl,每一个资源都有一个特定的URI,通过URI可以找到一个具体的资源。

  • Pepresentation

资源表现层,资源的具体表现形式,例如一段文字我们可以使用tet\html\xml\json等不同形式来描述他。

  • State Transfer

状态转化是指客户端和服务端之间的数据交互,因为HTTP请求不能传输数据的状态,所有的状态都保存在服务端,如果说客户端希望访问服务端的数据,就需要使其发生状态转变,同时这种状态转化是建立在表现层上的,完成转换就表示资源的表现形式发生了改变

RESTful概念比较抽象,特点有两个:

1、URL传参更加简洁

2、完成不同终端的资源共享,RESTful提供了一套规范,不同终端之间只需要遵守该规范就可以实现数据交互。

RESTful具体来讲就是四种表现形式,HTTP协议中四种请求类型(GET,PUT,POST,DELETE)分别表示四种常规操作,CRUD

  • GET 用来获取资源
  • POST 用来创建资源
  • PUT 用来修改资源
  • DELETE 用来删除资源

image-20240802141832342

两个终端要完成数据交互,基于RESTful的方式,增删改查操作分别需要使用不同的HTTP请求类型来访问。

传统的Web开发中,form表单只支持GET和POST,不支持DELETE和PUT,如何解决?通过添加HiddenHttpMethodFilter过滤器,可以将POST请求转为PUT或者DELETE。

@HiddenHttpMethodFilter的实现原理

HideenHttpMethoFilter会检测请求参数中是否包含 _method参数,如果包含则取出它的值,并且判断请求类型之后完成请求类型的转换,然后继续传递。

image-20240802142812021

实现步骤

1、在form表单中添加隐藏域标签,name=_method,value为DELETE/PUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/8/2
Time: 14:29
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="/rest/update" method="post">
<input type="hidden" name="_method" value="PUT"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>

2、在web.xml中配置HiddenHttpMethodFilter

1
2
3
4
5
6
7
8
9
<!--HiddenHttpMethodFilter-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3、Handler

1
2
3
4
5
6
@PutMapping("/update")
@ResponseBody
public String update(HttpServletResponse response){
response.setCharacterEncoding("UTF-8");
return "已经接受到PUT请求";
}

小案例:需求分析

  • 添加课程,成功则返回全部课程信息
  • 查询课程,通过id查询对应的课程信息
  • 修改课程,成功则返回修改之后的全部课程信息。
  • 删除课程,成功则返回删除之后的全部课程信息。

代码实现

1、JSP

  • 添加课程:add.jsp
  • 修改课程:edit.jsp
  • 课程展示:index.jsp

2、Course实体类

1
2
3
4
5
public class Course(){
private Integer id;
private String name;
private Double price;
}

3、CourseRepository(DAO)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.bitzh.repository;

import com.bitzh.entity.Course;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* @Auther: oyy0v0
* @Date: 2024/8/2 - 08 - 02 - 14:47
* @Description: com.bitzh.repository
* @version: 1.0
*/
@Repository
public class CourseRepository {
//这里用集合代替数据库
private Map<Integer, Course> courseMap;
public CourseRepository(){
courseMap = new HashMap<>();
courseMap.put(1,new Course(1,"java基础",Double.parseDouble("500")));
courseMap.put(2,new Course(2,"java高级",Double.parseDouble("1000")));

}

public Collection<Course> findAll(){
return courseMap.values();
}

public Course findById(Integer id){
return courseMap.get(id);
}

public void update(Course course){
courseMap.put(course.getId(),course);
}

public void deleteById(Integer id){
courseMap.remove(id);
}
}

4、CourseController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("/course")
public class CourseController {
@Autowired
private CourseRepository courseRepository;

@GetMapping("/findAll")
public ModelAndView findAll(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("index");
modelAndView.addObject("list",courseRepository.findAll());
return modelAndView;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.bitzh.controller;

import com.bitzh.entity.Course;
import com.bitzh.repository.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

/**
* @Auther: oyy0v0
* @Date: 2024/8/2 - 08 - 02 - 14:54
* @Description: com.bitzh.controller
* @version: 1.0
*/
@Controller
@RequestMapping("/course")
public class CourseController {
@Autowired
private CourseRepository courseRepository;

@PostMapping("/save")
public String save(Course course){
courseRepository.update(course);
return "redirect:/course/findAll";
}

@GetMapping("/findAll")
public ModelAndView findAll(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("index");
modelAndView.addObject("list",courseRepository.findAll());
return modelAndView;
}

@DeleteMapping("/deleteById/{id}")
public String deleteById(@PathVariable("id") Integer id){
courseRepository.deleteById(id);
return "redirect:/course/findAll";
}

@GetMapping("/findById/{id}")
public ModelAndView findById(@PathVariable("id") Integer id){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("edit");
modelAndView.addObject("course",courseRepository.findById(id));
return modelAndView;
}

@PutMapping("/update")
public String update(Course course){
courseRepository.update(course);
return "redirect:/course/findAll";
}

}

5、JSP

save.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/8/2
Time: 15:28
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="/course/save" method="post">
<table>
<tr>
<td>课程编号:</td>
<td>
<input type="text" name="id"/>
</td>
</tr>
<tr>
<td>课程名称:</td>
<td>
<input type="text" name="name"/>
</td>
</tr>
<tr>
<td>课程价格:</td>
<td>
<input type="text" name="price"/>
</td>
</tr>
<tr>
<td>
<input type="submit" value="提交">
</td>
</tr>
</table>
</form>

</body>
</html>

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/8/2
Time: 14:56
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<table>
<tr>
<th>编号</th>
<th>名称</th>
<th>价格</th>
</tr>
<c:forEach items="${list}" var="course">
<tr>
<td>${course.id}</td>
<td>${course.name}</td>
<td>${course.price}</td>
<td>
<form action="/course/deleteById/${course.id}"method="post">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="删除"/>
</form>
</td>
<a href="/course/findById/${course.id}">修改</a>
</tr>
</c:forEach>
</table>
</body>
</html>

edit.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%--
Created by IntelliJ IDEA.
User: pc
Date: 2024/8/2
Time: 15:45
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/course/update" method="post">
<input type="hidden" name="_method" value="PUT">
<table>
<tr>
<td>编号:</td>
<td>
<input type="text" name="id" readonly value="${course.id}">
</td>
</tr>
<tr>
<td>名称:</td>
<td>
<input type="text" name="name" value="${course.name}"/>
</td>
</tr>
<tr>
<td>价格:</td>
<td>
<input type="text" name="price" value="${course.price}"/>
</td>
</tr>
<tr>
<td>
<input type="submit" value="修改">
</td>
</tr>
</table>
</form>
</body>
</html>

Spring MVC 实现文件的上传下载

文件上传

单文件上传

1、底层使用的是Appche fileupload组件完成上传功能,Spring MVC只是对其进行了封装,简化开发。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
<!--apache fileupload-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta-servlet5</artifactId>
<version>2.0.0-M2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>

2、JSP页面

  • input 的type设置为file
  • form表单的method设置为Post
  • form表单的enctype设置为multipart/form-data

由于spring6有很多改动所以看这个

在SpringMVC中利用CommonsMultipartResolver与<multipart-config>分别解决浏览器文件上传问题的区别-CSDN博客

多文件上传

springMVC实现 MultipartFile 多文件上传,StandardMultipartHttpServletRequest上传文件,在请求中上传文件,比如上传图片_request中multipartfiles多文件上传-CSDN博客

文件下载

1、在JSP页面中添加超链接加载

SpringMVC的文件上传&文件下载&多文件上传—-详细介绍_使 spring mvc或其他框架实现 件上传功能,包括配 置 件上传解析器、处理-CSDN博客

底层都是通过IO流的技术。