1. springmvc介绍
Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
Spring MVC 本身就是 Spring 框架的一部分,可以说和 Spring 框架是无缝集成。性能方面具有先天的优越性,是当今业界最主流的 Web 开发框架。
1.1 MVC
- 控制层(Controller):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。
- 视图层(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、界面设计等功能。
- 数据模型层(Model):模型对象拥有最多的处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理和实现数据操作(即在数据库中存取数据)。
1.2 SpringMVC的执行流程
2. 入门案例
需求:通过用户id,查询用户信息
步骤:
新建maven项目,pom文件中package设为war
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mszlu</groupId> <artifactId>springmvc</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.16.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> </dependencies> </project>
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在src->main下新建webapp目录,会发现有个小蓝点标识
在webapp下新建WEB-INF目录,WEB-INF下新建web.xml文件
<?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_3_1.xsd" version="3.1"> <!--编码过滤器,解决post乱码问题--> <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> <servlet> <!--中央控制器,用于处理请求--> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
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上方的配置文件,需要加载spring的配置文件,在resources下新建spring-mvc.xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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可以识别此包以及子包下的类中的spring相关注解,比如Service,Controller,Component等--> <context:component-scan base-package="com.mszlu" /> <!--开启mvc相关的注解支持--> <mvc:annotation-driven /> <!--对webapp下的静态文件放行,不去走中央控制器--> <mvc:default-servlet-handler /> </beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15新建UserController和User
package com.mszlu.pojo; import lombok.Data; @Data public class User { private Long id; private String name; private Integer age; private String email; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.mszlu.controller; import com.mszlu.pojo.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class UserController { //代码的意义为,获取到用户信息,将用户信息显示到index.jsp中 @RequestMapping("getUser") public ModelAndView getUser(Long id){ ModelAndView modelAndView = new ModelAndView(); User user = new User(); user.setName("mvc"); user.setId(1L); user.setEmail("email"); user.setAge(20); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); 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在webapp下新建index.jsp
在pom.xml中加入tomcat插件,在右侧的maven->plugins中选择tomcat7,启动测试
<build> <!--设置插件--> <plugins> <!--具体的插件配置--> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15访问http://localhost/getUser 得到结果
2.1 入门案例分析
- 首先web.xml中有一个DispatcherServlet的中央控制器,其中
<url-pattern>/</url-pattern>
定义了,所有的请求都需要通过DispatcherServlet - DispatcherServlet如何匹配到controller中的方法呢?
- 访问的url路径(http://localhost/getUser)这个不是随便写的,其中http://localhost可以理解为域名(ip+端口),一般是固定的,/getUser 就定义了我们要访问方法的路径
- 在方法上有一个注解@RequestMapping("getUser") ,就对应了url上
/getUser
,这个就是执行流程的中HandlerMapping,处理器映射器 - @RequestMapping("getUser") 是加在controller方法上的,最终执行的时候,就是执行对应的方法,这个方法在mvc中称为Handler,@RequestMapping如何和方法映射起来,是通过HandlerAdapter来进行的适配。
- handler(
getUser(Long id)
),处理器执行完后,会直接返回数据或者跳转显示页面,将数据显示在页面上,这个过程有个视图解析器
来取做这个事情。 - ModelAndView,就是mvc模式中的模型和视图,
modelAndView.setViewName("/index.jsp");
代表设置视图,modelAndView.addObject("user",user)
代表设置模型数据(一般是说和视图相关的,在很多地方这个数据模型 称为vo(view object))
3. 参数接收
不管做什么业务,一个网络请求,往往分为两步,一步是request(参数传递),一步是response(请求响应)
3.1 普通传参
分为两种,一种是?传参,一种是 form表单传参,两种方式是一致的,测试一种即可,form表单中有个特例是文件传输,必须使用post接收
测试的时候,使用postman进行测试
,当天笔记的资料中有,安装即可
3.1.1 基本类型+String传参
request url: http://localhost/user/find?id=1&name=zhangsan
上方的url,有两个参数,id和name,接收有两种方式
直接接收
@RequestMapping("/user/find") public ModelAndView find(Long id,String name){ ModelAndView modelAndView = new ModelAndView(); User user = new User(); user.setName(name); user.setId(id); user.setEmail("email"); user.setAge(20); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); return modelAndView; } //第二种 写法,可以在参数前面加上@RequestParam("id")注解,注解中的value和url中的参数对应 //如果用了这种写法,参数的名称可以随意定义 @RequestMapping("/user/find") public ModelAndView find(@RequestParam("id") Long userId, @RequestParam("name") String username){ ModelAndView modelAndView = new ModelAndView(); User user = new User(); user.setName(username); user.setId(userId); user.setEmail("email"); user.setAge(20); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); 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实体类接收
//如果使用实体类接收,不能使用@RequestParam注解 @RequestMapping("/user/find") public ModelAndView find(User user){ ModelAndView modelAndView = new ModelAndView(); user.setEmail("email"); user.setAge(20); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); return modelAndView; }
1
2
3
4
5
6
7
8
9
10
3.1.2 @RequestParam属性
//required代表参数不是必须的,defaultValue如果参数没传,给一个默认值 @RequestParam(value = "id",required = false,defaultValue = "2")
1
2
required默认是true,如果不传,会报错
HTTP Status 400 - Required Long parameter 'id' is not present
3.1.3 数组传参
引入jstl依赖,便于使用c:foreach:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
2
3
4
5
6
7
8
9
10
list.jsp:
<%--
Created by IntelliJ IDEA.
User: lenovo
Date: 2021/9/9
Time: 20:55
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>mvc</title>
</head>
<body>
<c:forEach items="${names}" var="name">
用户名:${name} <br />
</c:forEach>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
request uri: http://localhost/user/list?name=lisi&name=zhangsan
接收方式:
数组
@RequestMapping("/user/list") public ModelAndView list(String[] name){ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("names",name); modelAndView.setViewName("/list.jsp"); return modelAndView; }
1
2
3
4
5
6
7List
//用List 接收,就必须加 @RequestParam("name"),从原理上来讲,传参的过程中 第一步就是要实例化对象,很明显 List是不能new的,所以需要springmvc自带的注解来帮忙做这个解析的过程 @RequestMapping("/user/list") public ModelAndView list(@RequestParam("name") List<String> name){ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("names",name); modelAndView.setViewName("/list.jsp"); return modelAndView; }
1
2
3
4
5
6
7
8
3.1.4 复杂类型传参
package com.mszlu.pojo;
import lombok.Data;
@Data
public class Address {
private String city;
}
2
3
4
5
6
7
8
9
10
package com.mszlu.pojo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
private Address address;
private List<Address> addressList;
private Map<String,Address> addressMap;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
User对象中的Address对象传参
request uri: http://localhost/user/address?address.city=beijing
@RequestMapping("/user/address") public ModelAndView list(User user){ System.out.println(user); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); return modelAndView; }
1
2
3
4
5
6
7
8User对象中的List
<Address>
传参request uri: http://localhost/user/address?addressList[0].city=beijing&addressList[1].city=shanghai
@RequestMapping("/user/address") public ModelAndView list(User user){ System.out.println(user); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("user",user); modelAndView.setViewName("/index.jsp"); return modelAndView; }
1
2
3
4
5
6
7
8User对象中的Map<String,Address>传参
request uri: http://localhost/user/address?addressMap['home'].city=beijing&addressMap['compony'].city=shanghai
3.2 JSON传参
普通传参,适用于?传参和表单传参,适合传递一些简单参数,复杂参数传递起来较为复杂,不便于使用,现在广为使用的是JSON传参,特别是前后分离项目
json传参的关键有两点
- 前端传递的参数形式为json字符串,content-type为
application/json
- 后端接收使用
@RequestBody
注解接收,POST方式
以上面User对象的传参为例:
需要导包:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.5</version>
</dependency>
2
3
4
5
@RequestMapping("/user/jsonAddress")
public ModelAndView address(@RequestBody User user){
System.out.println(user);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
postman请求参数示例:
{
"id":1,
"name":"zhangsan",
"address":{"city":"beijing"},
"addressList":[{"city":"beijing"},{"city":"shanghai"}],
"addressMap":{"home":{"city":"beijing"},"compony":{"city":"shanghai"}}
}
2
3
4
5
6
7
3.3 路径传参
request uri: http://localhost/user/find/1
//{id} 代表路径中的1,使用@PathVariable来接收,参数的名称和{id}要匹配
@RequestMapping("/user/find/{id}")
public ModelAndView address(@PathVariable Long id){
User user = new User();
user.setId(id);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
3.3.1 Restful风格
REST(英文:Representational State Transfer,简称REST,意思:表述性状态转换,描述了一个架构样式的网络系统,比如web应用)。
它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简介,更有层次,更易于实现缓存等机制。
满足这些约束条件和原则的应用程序或设计就是RESTful。
3.3.2 RESTful的特性
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。
状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,所以就是“表现层状态转换”。具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建/更新资源,PUT用来新建/更新资源,DELETE用来删除资源。
简单表述:http://localhost/user/1,使用这样的一个uri,就可以实现增删改查
3.3.3 示例
代码:
//查询
@GetMapping("/user/{id}")
public ModelAndView findUser(@PathVariable Long id){
User user = new User();
user.setId(id);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
//新增
@PostMapping("/user/{id}")
public ModelAndView saveUser(@PathVariable Long id,@RequestBody User user){
System.out.println(user);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
//更新
@PutMapping("/user/{id}")
public ModelAndView updateUser(@PathVariable Long id,String name){
User user = new User();
user.setName(name);
user.setId(id);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
11
//删除
@DeleteMapping("/user/{id}")
public ModelAndView deleteUser(@PathVariable Long id){
User user = new User();
user.setId(id);
System.out.println("删除用户");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
11
4. 响应
请求响应也是分两种:一种是跳转页面,一种是返回json数据(以前流行过返回xml数据,现在很少有用的了,但一些老项目还在使用)
4.1 跳转页面
跳转页面有两种方式:一种是直接返回String,一种是返回ModelAndView(建议使用)
4.1.1 string
//string跳转页面
@GetMapping("/user/page/{id}")
public String find(@PathVariable Long id){
//将需要跳转的页面 写在这个位置,/代表从根目录开始,forward跳转
//forward 地址栏 地址不变
return "/index.jsp";
}
2
3
4
5
6
7
//string跳转页面 redirect
@GetMapping("/user/page/{id}")
public String find(@PathVariable Long id){
//将需要跳转的页面 写在这个位置,/代表从根目录开始,redirect跳转
//redirect 地址栏变为跳转的页面
return "redirect:/index.jsp";
}
2
3
4
5
6
7
页面传参
//string跳转页面
//添加一个Model,代表springmvc中的数据模型,用于给页面传参,只有forward才能进行传参
@GetMapping("/user/page/{id}")
public String find(@PathVariable Long id, Model model){
//将需要跳转的页面 写在这个位置,/代表从根目录开始
User user = new User();
user.setId(id);
user.setName("zhangsan");
model.addAttribute("user", user);
return "/index.jsp";
}
2
3
4
5
6
7
8
9
10
11
将页面放入WEB-INF下
//如果用forward,可以将页面放在WEB-INF下,这样用户不会直接访问到页面,较为安全
//可以将WEB-INF 和 后缀jsp 做成固定的配置,不用每次跳转页面的时候 都去拼接
2
在springmvc的配置文件中加入:
<!--代表返回页面的时候 前面已经加了/WEB-INF/pages/ ,后面加了.jsp-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
2
3
4
5
在WEB-INF下新建pages,然后创建index.jsp
java代码编写:
//string跳转页面
@GetMapping("/user/page/{id}")
public String find(@PathVariable Long id, Model model){
//将需要跳转的页面 写在这个位置,/代表从根目录开始
User user = new User();
user.setId(id);
user.setName("zhangsan");
model.addAttribute("user", user);
//这返回index,但由于前后缀的添加 实际返回的页面是 /WEB-INF/pages/index.jsp
//只能forward使用,redirect方式 无法这样做
return "index";
}
2
3
4
5
6
7
8
9
10
11
12
4.1.2 ModelAndView
ModelAndView,就是将view和model做了一个集合,是springmvc特有的方式
//ModelAndView跳转页面
@GetMapping("/user/page/{id}")
public ModelAndView find(@PathVariable Long id,ModelAndView modelAndView){
//将需要跳转的页面 写在这个位置,/代表从根目录开始,forward跳转
User user = new User();
user.setId(id);
user.setName("zhangsan");
modelAndView.addObject("user", user);
modelAndView.setViewName("index");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
11
12
//ModelAndView跳转页面
@GetMapping("/user/page/{id}")
public ModelAndView find(@PathVariable Long id){
//将需要跳转的页面 写在这个位置,/代表从根目录开始,redirect跳转
ModelAndView modelAndView = new ModelAndView();
User user = new User();
user.setId(id);
user.setName("zhangsan");
modelAndView.addObject("user", user);
//注意加了redirect 属于重定向,不能重定向到WEB-INF下的页面,只能是webapp下的
//具体原因 可以打个war包 解压出来,看看页面所在的位置的区别
modelAndView.setViewName("redirect:/index.jsp");
return modelAndView;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
4.2 返回JSON数据
前后端分离项目一般都是用json进行交互,传参使用json,返回数据也接收为json数据,这种方式广泛应用于第三方接口,前后端分离开发,微服务等场景中
如何做?
导包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.5</version> </dependency>
1
2
3
4
5在需要返回json数据的方法上,添加注解
@ResponseBody
开启mvc注解支持
<!--开启mvc相关的注解支持--> <mvc:annotation-driven />
1
2示例
@GetMapping("/user/findUserById/{id}") @ResponseBody public User findUserById(@PathVariable Long id){ User user = new User(); user.setId(id); user.setName("zhangsan"); return user; }
1
2
3
4
5
6
7
8
9如果controller类中,所有的方法都需要返回json数据,可以在类上面加@RestController来代替@Controller,方法上就不用写了
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController {}
1
2
3
4
5
6
5. 路径映射
@GetMapping("/user/findUserById/{id}")
@PostMapping("/user/updateUser/{id}")
2
之前我们写路径映射的时候,都是这样的写法,很明显user是路径上的共同点,那么能不能抽离出来呢?
@RequestMapping是可以加在类上面的
@RestController
@RequestMapping("user")
public class UserController {
//类上面加上user后,那么访问所有的方法,都先加上user
//http://localhost/user/findUserById/1
@GetMapping("findUserById/{id}")
public User findUserById(@PathVariable Long id){}
//http://localhost/user/updateUser/1
@PostMapping("updateUser/{id}")
public User updateUser(@PathVariable Long id){
}
2
3
4
5
6
7
8
9
10
11
这种写法是建议的写法,UserController上
@RequestMapping("user"
)代表了user模块,从路径上就可以进行模块的区分,代码结构清晰,后期进行更细致模块拆分的时候,也更加容易(便于扩展,维护)