导航:
【黑马Java笔记+踩坑汇总】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城
目录
1,SpringMVC简介
1.1 三层架构、MVC、异步回顾
1.2 SpringMVC概述
1.3 所有SpringMvc注解整理
2,SpringMVC入门案例
2.1 需求分析
2.2 代码实现
知识点:@Controller@RequestMapping@ResponseBody
2.3 案例总结
2.4 工作流程解析
2.4.1 启动服务器初始化过程
2.4.2 单次请求过程
2.5 bean加载控制,spring排除加载表现层的bean
2.5.1 问题分析
2.5.2 思路分析
2.5.3 环境准备
2.5.5 设置bean加载控制
知识点@ComponentScan的excludeFilters
3,PostMan工具的使用
3.1 PostMan简介
3.2 PostMan安装
3.3 PostMan使用
3.3.1 创建WorkSpace工作空间
3.3.2 发送请求
3.3.3 保存当前请求
4,请求与响应
4.1 设置请求映射路径
4.1.1 环境准备
4.1.2 请求冲突问题分析
4.1.3 设置映射路径
4.2 普通请求参数传递入门
4.2.1 环境准备
4.2.2 请求参数传递和中文乱码解决
4.3 请求头的五种类型参数传递
4.3.1 普通请求参数,起别名,@RequestParam
4.3.2 POJO数据类型
4.3.3 嵌套POJO类型参数
4.3.4 数组类型参数
4.3.5 集合类型参数,@RequestParam
知识点@RequestParam
4.4 请求体的JSON数据传输参数,@RequestBody
4.4.1 概述
4.4.2 JSON普通数组,@EnableWebMvc
4.4.3 JSON对象数据
4.4.4 JSON对象数组
知识点 @EnableWebMvc@RequestBody
4.4.5 @RequestBody与@RequestParam区别
4.4.5 @RequestParam和@RequestPart的区别
4.5 日期类型参数传递@DateTimeFormat
4.5.1 具体代码
知识点@DateTimeFormat
4.5.2 类型转换内部实现原理
4.6 响应
4.6.1 环境准备,jackson-databind依赖
4.6.2 响应jsp页面[了解]
4.6.3 返回文本数据[了解]
4.6.4 响应JSON数据
知识点@ResponseBody
5,Rest风格
5.1 REST简介
5.2 RESTful入门案例
5.2.1 环境准备
5.2.2 思路分析
5.2.3 代码实现,@RequestMapping的method属性,@PathVariable
5.2.4 目前学的参数占位符汇总
知识点@PathVariable
5.2.5 区别:@RequestBody、@RequestParam、@PathVariable
5.3 RESTful优化,快速开发
5.3.1 概述,@RestController,@GetMapping
知识点@RestController@GetMapping @PostMapping @PutMapping @DeleteMapping
5.4 RESTful案例
5.4.1 需求分析
5.4.2 环境准备
5.4.2 后台接口开发
5.4.3 放行静态页面,WebMvcConfigurer配置类添加资源处理器
三层架构
浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据
如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利
将后端服务器Servlet拆分成三层,分别是web
、service
和dao
MVC模式是对三层架构中的web层的优化。
MVC模式
controller
、视图view、
业务模型Model
service根据需要会调用dao对数据进行增删改查
dao把数据处理完后将结果交给service,service再交给controller
控制器controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器。这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作。
异步取代同步
随着互联网的发展,MVC模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。
vue异步调用的特点:
SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
springmvc功能和Servlet是一样的,只是更简洁。
SpringMVC主要负责的是:
SpringMVC是处于Web层的框架,主要作用:接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端。
REST是一种软件架构风格,可以降低开发的复杂性,提高系统的可伸缩性,后期的应用也是非常广泛。
SSM整合是把咱们所学习的SpringMVC+Spring+Mybatis整合在一起来完成业务开发,是对我们所学习这三个框架的一个综合应用。
学习目标:
介绍了这么多,对SpringMVC进行一个定义
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
优点
这里所说的优点,就需要我们在使用的过程中慢慢体会。
Controller 相关注解:
因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(Servlet)
4.定义处理请求的功能类(UserServlet)
5.设置请求映射(配置映射关系)
SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(SpringMVC+Servlet)
4.定义处理请求的功能类(UserController)
5.设置请求映射(配置映射关系)
6.将SpringMVC设定加载到Tomcat容器中
步骤1:创建Maven项目
打开IDEA,创建一个新的web项目
步骤2:补全目录结构
因为使用骨架创建的项目结构不完整,需要手动补全
步骤3:导入jar包
将pom.xml中多余的内容删除掉,再添加依赖spring-webmvc和javax.servlet-api
无需再导入spring-context,因为依赖spring-webmvc里包括了spring-context.
4.0.0
com.itheima
springmvc_01_quickstart
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
回顾:servlet的坐标为什么需要添加
?
provided
- scope是maven中jar包依赖作用范围的描述,
- 如果不设置默认是
compile
在在编译、运行、测试时均有效如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错
provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就避免冲突
步骤4:创建配置类
@Configuration
//扫描范围设为controller即可,因为springmvc容器只用管理表现层的bean即可,业务、数据层由spring管理
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
步骤5:创建Controller类
//设置springmvc的核心控制器bean
@Controller
public class UserController {
//设置当前控制器方法请求访问路径
@RequestMapping("/save")
//@ResponseBody设置当前控制器方法响应内容为当前返回值,无需解析
@ResponseBody
public String save(){
System.out.println("user save ...");
//如果不加@ResponseBody,系统会根据返回值字符串寻找路径,例如return "index.jsp";会跳转到jsp页面
//返回真正json只需要返回对象,在pom引入jackson,springmvcconfig配置类注解@@EnableWebMvc,下面注释的语句就是返回JSON数据
//return new User("zhangsan",23);
return "{'info':'springmvc'}";
}
}
- 当方法上有@ReponseBody注解后
- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成JSON响应给前端
步骤6:使用Servlet容器配置类替换web.xml
方法一:复杂底层
将web.xml删除,换成ServletContainersInitConfig,继承抽象转发Servlet初始化器类AbstractDispatcherServletInitializer,alt+insert实现三个方法。
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//创建Servlet容器时,加载springmvc的配置。将springmvc对应的bean并放入WebApplicationContext对象范围中。
//最终返回web容器WebApplicationContext ,web容器就是springmvc最终体现的容器。ApplicationContext应用上下文,容器
protected WebApplicationContext createServletApplicationContext() {
//创建注解配置的web容器。使用AnnotationConfigWebApplicationContext,比前面Spring创建注解容器的类AnnotationConfigApplicationContext中间多了Web
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载springmvc的配置类字节码到web容器中,从而可以让web容器管理加载配置类管理的bean
//因为AnnotationConfigWebApplicationContext类没有带参构造方法,所以要手动为容器加载指定配置类字节码文件
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置由springmvc拦截并处理的请求路径,即springmvc拦截哪些请求。
//返回值String数组只有一个"/",代表所有路径都由springmvc处理
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类,方法和加载springmvc方法相同,只是加载的配置类改成spring的配置文件
//如果创建Servlet容器时需要加载非表现层的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
方法二:简单快速
继承AbstractAnnotationConfigDispatcherServletInitializer ,跟之前抽象类外表区别是中间多了AnnotationConfig
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
步骤7:配置Tomcat环境
我这里用的是Tomcat9,因为我Servlet和springmvc依赖都导入了最新的,和Tomcat7不兼容。
步骤8:启动运行项目
步骤9:浏览器访问
浏览器输入http://localhost/save
进行访问
至此SpringMVC的入门案例就已经完成。
注意事项
- SpringMVC是基于Spring的,在pom.xml只导入了
spring-webmvc
jar包的原因是它会自动依赖spring相关坐标- AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
- AbstractDispatcherServletInitializer提供了三个接口方法供用户实现
- createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
- getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
- createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
- createServletApplicationContext用来加载SpringMVC环境,createRootApplicationContext用来加载Spring环境
知识点1:@Controller
名称 | @Controller |
---|---|
类型 | 类注解 |
位置 | SpringMVC控制器类定义上方 |
作用 | 设定SpringMVC的核心控制器bean |
知识点2:@RequestMapping
名称 | @RequestMapping |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
知识点3:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
为了更好的使用SpringMVC,我们将SpringMVC的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程
和单次请求过程
web容器、Servletcontext、webApplicationContext、UserController关系:
服务器启动,执行ServletContainersInitConfig类,初始化web容器。功能类似于以前的web.xml
执行createServletApplicationContext方法,创建了WebApplicationContext对象。该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
加载SpringMvcConfig配置类
执行@ComponentScan加载对应的bean。扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
加载UserController,每个@RequestMapping的名称对应一个具体的方法。此时就建立了 /save
和 save方法的对应关系
6.执行getServletMappings方法,设定SpringMVC拦截请求的路径规则。/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
http://localhost/save
项目目录结构:
各目录存放内容:
config目录存入的是配置类,写过的配置类有:
问题:controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?
答案:
分析清楚谁该管哪些bean以后,接下来要解决的问题是如何让Spring和SpringMVC分开加载各自的内容。
在SpringMVC的配置类SpringMvcConfig
中使用注解@ComponentScan
,我们只需要将其扫描范围设置到controller即可,如
在Spring的配置类SpringConfig
中使用注解@ComponentScan
,当时扫描的范围中其实是已经包含了controller,如:
现在的问题:
因为功能不同,如何避免Spring错误加载到SpringMVC的bean?
答案:
加载Spring控制的bean的时候排除掉SpringMVC控制的bean。Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
针对上面的问题,解决方案也比较简单,就是:
具体该如何排除:
创建一个Web的Maven项目
pom.xml添加Spring依赖springmvc相关javax.servlet-api,spring-webmvc(里面包括了spring-context),数据库相关mysql-java-connector,spring-jdbc,druid,mybatis,mybatis-spring
4.0.0
com.itheima
springmvc_02_bean_load
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
com.alibaba
druid
1.1.16
org.mybatis
mybatis
3.5.6
mysql
mysql-connector-java
5.1.47
org.springframework
spring-jdbc
5.2.10.RELEASE
org.mybatis
mybatis-spring
1.3.0
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建servlet配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
编写Controller,Service,Dao,Domain类
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}
public interface UserService {
public void save(User user);
}
@Service
public class UserServiceImpl implements UserService {
public void save(User user) {
System.out.println("user service ...");
}
}
public interface UserDao {
@Insert("insert into tbl_user(name,age)values(#{name},#{age})")
public void save(User user);
}
public class User {
private Integer id;
private String name;
private Integer age;
//setter..getter..toString略
}
最终创建好的项目结构如下:
方式一:修改Spring配置类,设定扫描范围为精准范围。
@Configuration
@ComponentScan({"com.itheima.service","comitheima.dao"})
public class SpringConfig {
}
说明:
上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer
对象来进行扫描处理的,我们只需要将其扫描到service包即可。
方式二:修改Spring配置类,设定扫描范围为com.itheima,排除掉controller包中的bean
@ComponentScan的 excludeFilters属性。
@Configuration
@ComponentScan(value="com.itheima",
[email protected](
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig {
}
excludeFilters属性:设置扫描加载bean时,排除的过滤规则
type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
大家只需要知道第一种ANNOTATION即可
classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
测试controller类是否被排除掉了:创建spring容器获取表现层bean
public class App{
public static void main (String[] args){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(ctx.getBean(UserController.class));
}
}
如果被排除了,该方法执行就会报bean未被定义的错误
注意:测试的时候,需要把SpringMvcConfig配置类上的@ComponentScan注解注释掉,否则不会报错
出现问题的原因是,
- Spring配置类扫描的包是
com.itheima
- SpringMVC的配置类,
SpringMvcConfig
上有一个@Configuration注解,也会被Spring扫描到- SpringMvcConfig上又有一个@ComponentScan,把controller类又给扫描进来了
- 所以如果不把@ComponentScan注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来,演示的效果就出不来
- 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。
Servlet容器配置类加载Spring配置类:
最后一个问题,有了Spring的配置类,要想在tomcat服务器启动将其加载,我们需要修改ServletContainersInitConfig的createRootApplicationContext方法
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
Servlet容器配置类简单方法:
对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类。
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
相关属性 | excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes) |
代码编写完后,我们要想测试,只需要打开浏览器直接输入地址发送请求即可。发送的是GET
请求可以直接使用浏览器,但是如果要发送的是POST
请求呢?
如果要求发送的是post请求,我们就得准备页面在页面上准备form表单,测试起来比较麻烦。所以我们就需要借助一些第三方工具,如PostMan.
下载地址:
Download Postman | Get Started for Free
9.1.2版本下载地址:
https://dl.pstmn.io/download/version/9.12.2/win64
9.1.2汉化包地址:
Releases · hlmd/Postman-cn · GitHub
双击Postman-win64-8.3.1-Setup.exe
即可自动安装,
汉化包:Releases · hlmd/Postman-cn · GitHub
安装汉化包后解压到postman的app-resources目录下,再次打开就已经汉化成功。
安装完成后,如果需要注册,可以按照提示进行注册,如果底部有跳过测试的链接也可以点击跳过注册
看到如下界面,就说明已经安装成功。
工作空间是可以云备份的。
点击+号新建请求:
注意:第一次请求需要创建一个新的集合目录,后面就不需要创建新目录,直接保存到已经创建好的目录即可。
之前我们提到过,SpringMVC是web层的框架,主要的作用是接收请求、接收数据、响应结果,所以这一章节是学习SpringMVC的重点内容,我们主要会讲解四部分内容:
创建一个Web的Maven项目
pom.xml添加Spring依赖javax.servlet,spring-webmvc
4.0.0
com.itheima
springmvc_03_request_mapping
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建对应的配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Class[] getRootConfigClasses() {
return new Class[0];
}
}
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
编写BookController和UserController,都有一个/save路径
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String save(){
System.out.println("user delete ...");
return "{'module':'user delete'}";
}
}
@Controller
public class BookController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("book save ...");
return "{'module':'book save'}";
}
}
项目结构如下:
因为BookController和UserController,都有一个/save路径,启动Tomcat服务器,后台会报错:
从错误信息可以看出:
http://localhost/save
http://localhost/save
http://localhost/saved
的时候,到底是访问UserController还是BookController?问题:团队多人开发,每人设置不同的请求路径,冲突问题该如何解决?
解决思路:为不同模块设置模块名作为请求路径前置
对于Book模块的save,将其访问路径设置http://localhost/book/save
对于User模块的save,将其访问路径设置http://localhost/user/save
这样在同一个模块中出现命名冲突的情况就比较少了。
方法一:笨方法,直接修改Controller方法上的@RequestMapping值
@Controller
public class UserController {
@RequestMapping("/user/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
@RequestMapping("/user/delete")
@ResponseBody
public String save(){
System.out.println("user delete ...");
return "{'module':'user delete'}";
}
}
@Controller
public class BookController {
@RequestMapping("/book/save")
@ResponseBody
public String save(){
System.out.println("book save ...");
return "{'module':'book save'}";
}
}
问题是解决了,但是每个方法前面都需要进行修改,写起来比较麻烦而且还有很多重复代码,如果/user后期发生变化,所有的方法都需要改,耦合度太高。
方法二:优化版,给controller类另外配置@RequestMapping,值为模块名
优化方案:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String save(){
System.out.println("user delete ...");
return "{'module':'user delete'}";
}
}
@Controller
@RequestMapping("/book")
public class BookController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("book save ...");
return "{'module':'book save'}";
}
}
注意:
- 当类上和方法上都添加了
@RequestMapping
注解,前端发送请求的时候,要和两个注解的value值相加匹配才能访问到。- @RequestMapping注解value属性前面加不加
/
都可以
扩展小知识:对于PostMan如何觉得字小不好看,可以使用ctrl+=
调大,ctrl+-
调小。
了解即可,更多用JSON传数据。
请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数?
关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:
创建一个Web的Maven项目
pom.xml添加Spring依赖javax.servlet,spring-webmvc
4.0.0
com.itheima
springmvc_03_request_mapping
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建对应的配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Class[] getRootConfigClasses() {
return new Class[0];
}
}
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
编写UserController
@Controller
public class UserController {
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(){
return "{'module':'commonParam'}";
}
}
编写模型类,User和Address
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
//setter...getter...略
}
最终创建好的项目结构如下:
中文乱码问题:
- 不管是GET还是POST,controller代码都是一样的,形参传递,不作区分。只是对于中文乱码解决不同,get是在Tomcat插件里配置编码utf-8,post是Servlet容器配置类使用过滤器设置编码。
- json格式传递无中文乱码问题,注意参数要加@RequestBody,返回值类型是对象和List<类>。return "{'save':'success'}" ;返回的是普通类型String。
GET发送单个参数
http://localhost/commonParam?name=itcast
GET接收单个参数:
controller里方法形参名必须和请求参数名一致:
@Controller
public class UserController {
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name){
System.out.println("普通参数传递 name ==> "+name);
return "{'module':'commonParam'}";
}
}
GET发送多个参数
http://localhost/commonParam?name=itcast&age=15
GET接收多个参数:
@Controller
public class UserController {
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'commonParam'}";
}
}
GET请求中文乱码
如果Tomcat8.5之前版本,我们传递的参数中有中文,你会发现接收到的参数会出现中文乱码问题。
发送请求:http://localhost/commonParam?name=张三&age=18
控制台:
解决乱码:
Tomcat8.5以后的版本已经处理了中文乱码的问题,但是IDEA中的Tomcat插件目前只到Tomcat7,所以需要修改pom.xml来解决GET请求中文乱码问题
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
UTF-8
POST发送参数
- 一定别忘了地址栏左边选择POST请求,不然会500报错。
- 选择x-www-form-urlencoded,对比之下,form-data表单是既能发表单,又能发文件
POST接收参数:
和GET一致,不用做任何修改
@Controller
public class UserController {
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'commonParam'}";
}
}
POST请求中文乱码:所有Tomcat版本都中文乱码
发送请求与参数:
接收参数:
控制台打印,会发现有中文乱码问题。
解决方案:配置过滤器
在Servlet容器配置类里,alt+insert重写getServletFilters过滤器方法:
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[0];
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//重写getServletFilters过滤器方法,返回数组传入CharacterEncodingFilter过滤器对象,乱码处理
@Override
protected Filter[] getServletFilters() {
//创建字符编码过滤器CharacterEncodingFilter对象
CharacterEncodingFilter filter = new CharacterEncodingFilter();
//设置编码
filter.setEncoding("UTF-8");
//返回过滤器数组,元素为CharacterEncodingFilter对象
return new Filter[]{filter};
}
}
注意:
- 过滤器方法只适用于post请求方式的中文乱码,get中文乱码还是要Tomcat8以上,或Tomcat7的pom里设置Tomcat插件编码
- CharacterEncodingFilter是在spring-webmvc包中,所以用之前要确保导入对应的jar包。
前面我们已经能够使用GET或POST来发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有:
问题:如果形参与地址参数名不一致该如何解决?
解决方案:使用@RequestParam注解,用法跟dao层传参@Param类似
发送请求与参数:
http://localhost/commonParamDifferentName?name=张三&age=18
后台接收参数:
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
因为前端给的是name
,后台接收使用的是userName
,两个名称对不上,导致接收数据失败:
解决方案:使用@RequestParam注解,用法跟dao层传参@Param类似
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
注意:写上@RequestParam注解框架就不需要自己去解析注入,能提升框架处理性能
适用于参数多的情况,将参数封装成一个实体类中。
简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用POJO实体类数据类型。
此时需要使用前面准备好的POJO类,先来看下User
public class User {
private String name;
private int age;
//setter...getter...略
}
发送请求和参数:
后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
注意:
- POJO参数接收,前端GET和POST发送请求数据的方式不变。
- 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
如果POJO对象中嵌套了其他的POJO类,如
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
private Address address;
//setter...getter...略
}
发送请求和参数:
后台接收参数:
//POJO参数嵌套:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
注意:
请求参数key的名称要和POJO中属性的名称一致,否则无法封装
举个简单的例子,如果前端需要获取用户的爱好,爱好绝大多数情况下都是多个,如何发送请求数据和接收数据呢?
发送请求和参数:
后台接收参数:
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
@RequestParam
结论:请求参数跟上面数组一样,controller形参为@RequestParam
注解的同名集合对象。
集合直接做形参会报错,报错演示:
发送请求和参数:
后台接收参数:
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据 @RequestMapping("/listParam") @ResponseBody public String listParam(List
likes){ System.out.println("集合参数传递 likes ==> "+ likes); return "{'module':'list param'}"; } 运行会报错,
错误的原因是:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
解决方案是:使用
@RequestParam
注解绑定参数关系
使用@RequestParam
注解绑定参数关系
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
注意:
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
- 对于简单数据类型使用数组会比集合更简单些。
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
@RequestBody接收请求体JSON数据,并转为对象或对象数组。
前面我们说过,现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是JSON,所以前端如果发送的是JSON数据,后端该如何接收?
对于JSON数据类型,我们常见的有三种:
使用josn数据传输参数要先导入Jackson依赖和 @EnableWebMvc注解
pom.xml添加依赖jackson-databind
SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖
com.fasterxml.jackson.core
jackson-databind
2.9.0
开启SpringMVC注解支持@EnableWebMvc
在SpringMVC的配置类中开启SpringMVC的注解支持@EnableWebMvc,这个注解功能很强大,里面包含了将JSON转换成对象的功能。
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
剧透,后面使用springboot和Rest风格后,给controller注解@RestController后就不用导入Jackson、@EnableWebMvc,直接参数前@RequestBody即可
controller接收前端请求头的JSON数组并转为List:
步骤1:pom.xml添加依赖jackson-databind
SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖
com.fasterxml.jackson.core
jackson-databind
2.9.0
步骤2:PostMan发送JSON数据
步骤3:开启SpringMVC注解支持@EnableWebMvc
在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
步骤4:controller形参前添加@RequestBody
@RequestMapping("/listParamForJson")
@ResponseBody
//使用@RequestBody注解将前端请求体传递的json数组数据,传递并转换到形参的集合对象中作为数据
public String listParamForJson(@RequestBody List likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
步骤5:启动运行程序
剧透,后面使用springboot和Rest风格后,给controller注解@RestController后就不用导入Jackson、@EnableWebMvc,直接参数前@RequestBody即可
回顾:
Servlet接收JSON对象:
1.Servlet的getParameter获取String形式的json对象
2.fastjson的JSON.parseObject方法将json对象转为实体类对象
Servlet响应JSON对象到前端:
1.将实体类对象通过fastjson的JSON.toJSONString方法转为String类型
2.response.getWriter().write(jsonStr)
controller接收前端JSON对象并转为实体类对象:
前端发送JSON对象数据:
{
"name":"itcast",
"age":15
}
controller接收JSON对象数据:
带@RequestBody实体类对象的形参直接转换JSON对象
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}
记得开启SpringMVC注解@EnableWebMvc
在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。
@Configuration @ComponentScan("com.itheima.controller") //开启json数据类型自动转换 @EnableWebMvc public class SpringMvcConfig { }
启动程序访问测试
说明:
address为null的原因是前端没有传递数据给后端。
如果想要address也有数据,我们需求修改前端传递的数据内容:
{ "name":"itcast", "age":15, "address":{ "province":"beijing", "city":"beijing" } }
再次发送请求,就能看到address中的数据
剧透,后面使用springboot和Rest风格后,给controller注解@RestController后就不用导入Jackson、@EnableWebMvc,直接参数前@RequestBody即可
controller接收前端JSON数组并转为List:
前端发送JSON对象数组:
[
{"name":"itcast","age":15},
{"name":"itheima","age":12}
]
后端接收数据:
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List list){
System.out.println("list pojo(json)参数传递 list ==> "+list);
return "{'module':'list pojo for json param'}";
}
启动程序访问测试
小结
SpringMVC接收JSON数据的实现步骤为:
(1)导入jackson包
(2)使用PostMan发送JSON数据
(3)开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
(4)Controller方法的参数前添加@RequestBody注解
知识点1:@EnableWebMvc
名称 | @EnableWebMvc |
---|---|
类型 | 配置类注解 |
位置 | SpringMVC配置类定义上方 |
作用 | 开启SpringMVC多项辅助功能 |
知识点2:@RequestBody
名称 | @RequestBody |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
区别
应用
当请求头中指定Content-Type:multipart/form-data时,传递的json参数,@RequestPart注解可以用对象来接收,@RequestParam只能用字符串接收
@RequestPart
这个注解用在multipart/form-data
表单提交请求的方法上。MultipartFile
,属于Spring的MultipartResolver
类。这个请求是通过http协议
传输的前面我们处理过简单数据类型、POJO数据类型、数组和集合数据类型以及JSON数据类型,接下来我们还得处理一种开发中比较常见的一种数据类型,日期类型
日期类型比较特殊,因为对于日期的格式有N多中输入方式,比如:
20xx/xx/xx格式日期,controller可以自动转换成Date对象,其他格式日期要注解@DateTimeFormat(pattern="yyyy-MM-dd") ,pattern译为模式,图案
步骤1:编写方法接收日期数据
在UserController类中添加方法,把参数设置为日期类型
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date)
System.out.println("参数传递 date ==> "+date);
return "{'module':'data param'}";
}
步骤2:启动Tomcat服务器
查看控制台是否报错,如果有错误,先解决错误。
步骤3:使用PostMan发送请求
使用PostMan发送GET请求,并设置date参数
http://localhost/dataParam?date=2088/08/08
步骤4:查看控制台
通过打印,我们发现SpringMVC可以接收日期数据类型,并将其打印在控制台。
这个时候,我们就想如果把日期参数的格式改成其他的,SpringMVC还能处理么?
步骤5:更换日期格式
为了能更好的看到程序运行的结果,我们在方法中多添加一个日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,Date date1)
System.out.println("参数传递 date ==> "+date);
return "{'module':'data param'}";
}
使用PostMan发送请求,携带两个不同的日期格式,
http://localhost/dataParam?date=2088/08/08&date1=2088-08-08
发送请求和数据后,页面会报400,控制台会报出一个错误
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2088-08-08'; nested exception is java.lang.IllegalArgumentException]
从错误信息可以看出,错误的原因是在将2088-08-08
转换成日期类型的时候失败了,原因是SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd
,而我们现在传递的不符合其默认格式,SpringMVC就无法进行格式转换,所以报错。
解决方案也比较简单,需要使用@DateTimeFormat
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1)
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
return "{'module':'data param'}";
}
重新启动服务器,重新发送请求测试,SpringMVC就可以正确的进行日期转换了
步骤6:携带时间的日期
接下来我们再来发送一个携带时间的日期,看下SpringMVC该如何处理?
先修改UserController类,添加第三个参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2)
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
return "{'module':'data param'}";
}
使用PostMan发送请求,携带两个不同的日期格式,
http://localhost/dataParam?date=2088/08/08&date1=2088-08-08&date2=2088/08/08 8:08:08
重新启动服务器,重新发送请求测试,SpringMVC就可以将日期时间的数据进行转换
名称 | @DateTimeFormat |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参前面 |
作用 | 设定日期时间型数据格式 |
相关属性 | pattern:指定日期时间格式字符串 |
讲解内部原理之前,我们需要先思考个问题:
- 前端传递字符串,后端使用日期Date接收
- 前端传递JSON数据,后端使用对象接收
- 前端传递字符串,后端使用Integer接收
- 后台需要的数据类型有很多中
- 在数据的传递过程中存在很多类型的转换
问:谁来做这个类型转换?
答:SpringMVC
问:SpringMVC是如何实现类型转换的?
答:SpringMVC中提供了很多类型转换接口和实现类
在框架中,有一些类型转换接口,其中有:
/**
* S: the source type
* T: the target type
*/
public interface Converter {
@Nullable
//该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
T convert(S source);
}
注意:Converter所属的包为org.springframework.core.convert.converter
Converter接口的实现类
框架中有提供很多对应Converter接口的实现类,用来实现不同数据类型之间的转换,如:
请求参数年龄数据(String→Integer)
日期格式转换(String → Date)
该接口是实现对象与JSON之间的转换工作
注意:SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略
SpringMVC接收到请求和数据后,进行一些了的处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户。
比如:根据用户ID查询用户信息、查询用户列表、新增用户等。
对于响应,主要就包含两部分内容:
因为异步调用是目前常用的主流方式,所以我们需要更关注的就是如何返回JSON数据,对于其他只需要认识了解即可。
创建一个Web的Maven项目
pom.xml添加Spring依赖javax.servlet-api,spring-webmvc,jackson-databind
4.0.0
com.itheima
springmvc_05_response
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
com.fasterxml.jackson.core
jackson-databind
2.9.0
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建servlet容器的配置类和springmvc配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[0];
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
编写模型类User
public class User {
private String name;
private int age;
//getter...setter...toString省略
}
webapp下创建page.jsp
Hello Spring MVC!
最终创建好的项目结构如下:
步骤1:设置返回页面
@Controller
public class UserController {
@RequestMapping("/toJumpPage")
//注意
//1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
//2.方法需要返回String
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
}
注意:
- 方法不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端。
- 方法需要返回String
步骤2:启动程序测试
此处涉及到页面跳转,所以不适合采用PostMan进行测试,直接打开浏览器,输入
http://localhost/toJumpPage
步骤1:设置返回文本内容
@Controller
public class UserController {
@RequestMapping("/toText")
//注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
}
步骤2:启动程序测试
此处不涉及到页面跳转,因为我们现在发送的是GET请求,可以使用浏览器也可以使用PostMan进行测试,输入地址http://localhost/toText
访问
响应POJO对象:直接返回对象
@Controller
public class UserController {
@RequestMapping("/toJsonPOJO")
@ResponseBody
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") //导入jackson-databind后可以格式化日期格式
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
}
返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
重新启动服务器,访问
http://localhost/toJsonPOJO
如果return字符串"{'zhang张三':32}"会存在中文乱码问题,所以返回对象自动转JSON比较好。
响应POJO集合对象:直接返回集合类型
@Controller
public class UserController {
@RequestMapping("/toJsonList")
@ResponseBody
public List toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);
User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);
List userList = new ArrayList();
userList.add(user1);
userList.add(user2);
return userList;
}
}
重新启动服务器,访问http://localhost/toJsonList
名称 | @ResponseBody |
---|---|
类型 | 方法\类注解 |
位置 | SpringMVC控制器方法定义上方和控制类上 |
作用 | 设置当前控制器返回值作为响应体, 写在类上,该类的所有方法都有该注解功能 |
相关属性 | pattern:指定日期时间格式字符串 |
说明:
此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:
SpringMVC的Rest风格指的是以RESTful API实现在Web上,对外提供一个面向资源(resource)的接口设计。该风格下的API需要遵循如下约定:
/
组成、“/”为层级关系的分隔符、名词使用复数形式等命名规则;示例:
请求类型 | REST URL | 描述 |
---|---|---|
GET | /users | 获取用户列表 |
POST | /users | 创建新用户 |
PUT | /users/{id} | 修改指定ID的用户 |
DELETE | /users/{id} | 删除指定ID的用户 |
采用Rest风格的API其好处在于:可以简化接口调用,降低了与客户端之间开发的耦合度,同时也让数据更加直观易懂。同时,有助于降低服务端的开发制约,支持微服务架构,并且能够更好地支持各种移动设备和网络应用,可以更好地满足API设计的不断迭代、演进的需求。
REST(Representational State Transfer),表现性状态转换,它是一种软件架构风格
当我们想表示一个网络资源的时候,可以使用两种方式:
http://localhost/user/getById?id=1
查询id为1的用户信息http://localhost/user/saveUser
保存用户信息http://localhost/user/1
http://localhost/user
传统方式一般是一个请求url对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求url地址,就大概知道该url实现的是一个什么样的操作。
查看REST风格的描述,你会发现请求地址变的简单了,并且光看请求URL并不是很能猜出来该URL的具体功能
所以REST的优点有:
问题:一个相同的url地址即可以是新增也可以是修改或者查询,我们该如何区分?
答案: 使用请求方式区分,例如GET
,POST
,PUT
,DELETE。
http://localhost/users
查询全部用户信息 GET(查询)http://localhost/users/1
查询指定用户信息 GET(查询)http://localhost/users
添加用户信息 POST(新增/保存)http://localhost/users
修改用户信息 PUT(修改/更新)http://localhost/users/1
删除用户信息 DELETE(删除)请求的方式比较多,但是比较常用的就4种,分别是GET
,POST
,PUT
,DELETE
。
常用的四种请求方式:
注意:
- 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范。虽然不是规范,但现在基本都用rest风格。
- REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
- REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但是我们如果非要用GET请求做删除,这点在程序上运行是可以实现的
- 但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
- 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts......
RESTful:
清楚了什么是REST风格后,我们后期会经常提到一个概念叫RESTful
,那什么又是RESTful呢?
后期我们在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说咱们以后都是基于RESTful来进行开发的。
创建一个Web的Maven项目
pom.xml添加依赖javax.servlet-api,spring-webmvc,jackson-databind
4.0.0
com.itheima
springmvc_06_rest
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
com.fasterxml.jackson.core
jackson-databind
2.9.0
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建对应的配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[0];
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
编写模型类User和Book
public class User {
private String name;
private int age;
//getter...setter...toString省略
}
public class Book {
private String name;
private double price;
//getter...setter...toString省略
}
编写UserController和BookController
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody User user) {
System.out.println("user save..."+user);
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String delete(Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
@RequestMapping("/update")
@ResponseBody
public String update(@RequestBody User user) {
System.out.println("user update..." + user);
return "{'module':'user update'}";
}
@RequestMapping("/getById")
@ResponseBody
public String getById(Integer id) {
System.out.println("user getById..." + id);
return "{'module':'user getById'}";
}
@RequestMapping("/findAll")
@ResponseBody
public String getAll() {
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}
}
@Controller
public class BookController {
@RequestMapping(value = "/books",method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
@RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
@RequestMapping(value = "/books",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
@RequestMapping(value = "/books/{id}",method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
@RequestMapping(value = "/books",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
最终创建好的项目结构如下:
需求:将之前的增删改查替换成RESTful的开发方式。
1.之前不同的请求有不同的路径,现在要将其修改为统一的请求路径
修改前: 新增: /save ,修改: /update,删除 /delete...
修改后: 增删改查: /users
2.根据GET查询、POST新增、PUT修改、DELETE删除对方法的请求方式进行限定
3.发送请求的过程中如何设置请求参数?
了解即可,主要用5.3的优化方案
新增
@Controller
public class UserController {
//设置当前请求方法为POST,表示REST风格中的添加操作
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save() {
System.out.println("user save...");
return "{'module':'user save'}";
}
}
将请求路径更改为/users
http://localhost/users
使用method属性限定该方法的访问方式为POST
删除
先不传递路径参数删除
@Controller public class UserController { //设置当前请求方法为DELETE,表示REST风格中的删除操作 @RequestMapping(value = "/users",method = RequestMethod.DELETE) @ResponseBody public String delete(Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}"; } }
- 将请求路径更改为
/users
- 访问该方法使用 DELETE:
http://localhost/users
访问成功,但是删除方法没有携带所要删除数据的id,所以针对RESTful的开发,如何携带数据参数?
传递路径参数删除,@PathVariable
@PathVariable译作可变路径,作用是声明这个参数来自于路径。
前端发送请求的时候使用:http://localhost/users/1
,路径中的1
就是我们想要传递的参数。
后端获取参数,需要做如下修改:
/users/{id}
,目的是和路径匹配@Controller
public class UserController {
//设置当前请求方法为DELETE,表示REST风格中的删除操作
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
//@PathVariable声明此参数来自于路径。使参数和@RequestMapping中路径中的参数绑定。如果名称不一样,需要起别名再绑定
public String delete(@PathVariable Integer id) {
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
}
思考:
(1)如果方法形参的名称和路径
{}
中的值不一致,该怎么办?答:起别名。
(2)如果有多个参数需要传递该如何编写?
答:路径后加斜杠新增参数。
前端发送请求的时候使用:
http://localhost/users/1/tom
,路径中的1
和tom
就是我们想要传递的两个参数。后端获取参数,需要做如下修改:
@Controller public class UserController { //设置当前请求方法为DELETE,表示REST风格中的删除操作 @RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id,@PathVariable String name) { System.out.println("user delete..." + id+","+name); return "{'module':'user delete'}"; } }
修改
@Controller
public class UserController {
//设置当前请求方法为PUT,表示REST风格中的修改操作
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user) {
System.out.println("user update..." + user);
return "{'module':'user update'}";
}
}
根据ID查询
@Controller
public class UserController {
//设置当前请求方法为GET,表示REST风格中的查询操作
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'module':'user getById'}";
}
}
将请求路径更改为/users
http://localhost/users/666
查询所有
@Controller
public class UserController {
//设置当前请求方法为GET,表示REST风格中的查询操作
@RequestMapping(value = "/users" ,method = RequestMethod.GET)
@ResponseBody
public String getAll() {
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}
}
将请求路径更改为/users
http://localhost/users
小结
RESTful入门案例,我们需要学习的内容如下:
(1)设定Http请求动作(动词)
@RequestMapping(value="",method = RequestMethod.POST|GET|PUT|DELETE)
(2)设定请求参数(路径变量)
@RequestMapping(value="/users/{id}",method = RequestMethod.DELETE)
@ReponseBody
public String delete(@PathVariable Integer id){
}
@Insert("insert into tbl_book values (null,#{type},#{name},#{description})")
public void save(Book book);
jsp中EL表达式:参数占位符${}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
${brands}
Title
input里的值是:{{username}}
@Value("${jdbc.driver}")
private String driver;
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
名称 | @PathVariable |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应 |
@RequestBody
、@RequestParam
、@PathVariable
关于接收参数,我们学过三个注解@RequestBody
、@RequestParam
、@PathVariable
,这三个注解之间的区别和应用分别是什么?
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}
@Controller
public class UserController {
//设置当前请求方法为GET,表示REST风格中的查询操作
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'module':'user getById'}";
}
}
结论:
@RestController合并@Controller和@ResponseBody
@GetMapping @PostMapping @PutMapping @DeleteMapping代替@RequestMapping
RESTful开发麻烦的地方:
问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。
问题解决:
@RestController //@Controller + ReponseBody
@RequestMapping("/books")
public class BookController {
//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//@RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//@RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
对于刚才的问题,我们都有对应的解决方案:
问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。
将@RequestMapping提到类上面,将@Controller和@RequestMapping合并为@RestController,用来定义所有方法共同的访问路径。
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
使用@GetMapping @PostMapping @PutMapping @DeleteMapping代替
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。
1.将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能,即返回值是响应体,而不是路径
2.使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
知识点1:@RestController
名称 | @RestController |
---|---|
类型 | 类注解 |
位置 | 基于SpringMVC的RESTful开发控制器类定义上方 |
作用 | 设置当前控制器类为RESTful风格, 等同于@Controller与@ResponseBody两个注解组合功能 |
知识点2:@GetMapping @PostMapping @PutMapping @DeleteMapping
名称 | @GetMapping @PostMapping @PutMapping @DeleteMapping |
---|---|
类型 | 方法注解 |
位置 | 基于SpringMVC的RESTful开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作, 例如@GetMapping对应GET请求 |
相关属性 | value(默认):请求访问路径 |
需求一:假数据图片列表查询,从后台返回数据,将数据展示在页面上
需求二:新增图片,将新增图书的数据传递到后台,并在控制台打印
说明:此次案例的重点是在SpringMVC中如何使用RESTful实现前后台交互,所以本案例并没有和数据库进行交互,所有数据使用假
数据来完成开发。
步骤分析:
1.搭建项目导入jar包
2.编写Controller类,提供两个方法,一个用来做列表查询,一个用来做新增
3.在方法上使用RESTful进行路径设置
4.完成请求、参数的接收和结果的响应
5.使用PostMan进行测试
6.将前端页面拷贝到项目中
7.页面发送ajax请求
8.完成页面数据的展示
创建一个Web的Maven项目
pom.xml添加SpringMVC三大依赖javax.servlet-api,spring-webmvc,jackson-databind
4.0.0
com.itheima
springmvc_07_rest_case
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-webmvc
5.2.10.RELEASE
com.fasterxml.jackson.core
jackson-databind
2.9.0
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
80
/
创建对应的配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class[] getRootConfigClasses() {
return new Class[0];
}
protected Class[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
编写模型类Book
public class Book {
private Integer id;
private String type;
private String name;
private String description;
//setter...getter...toString略
}
编写BookController
@Controller
public class BookController {
}
最终创建好的项目结构如下:
步骤1:编写Controller类并使用RESTful进行配置
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ==> "+ book);
return "{'module':'book save success'}";
}
@GetMapping
public List getAll(){
System.out.println("book getAll is running ...");
List bookList = new ArrayList();
Book book1 = new Book();
book1.setType("计算机");
book1.setName("SpringMVC入门教程");
book1.setDescription("小试牛刀");
bookList.add(book1);
Book book2 = new Book();
book2.setType("计算机");
book2.setName("SpringMVC实战教程");
book2.setDescription("一代宗师");
bookList.add(book2);
Book book3 = new Book();
book3.setType("计算机丛书");
book3.setName("SpringMVC实战教程进阶");
book3.setDescription("一代宗师呕心创作");
bookList.add(book3);
return bookList;
}
}
步骤2:先测试后端,再开发前端。PostMan测试
测试新增
{
"type":"计算机丛书",
"name":"SpringMVC终极开发",
"description":"这是一本好书"
}
测试查询
步骤1:拷贝静态页面
将资料\功能页面 拷贝到项目的webapp
目录下
步骤2:访问pages目录下的books.html,springmvc对静态资源放行,SpringMvcSupport配置类
打开浏览器输入http://localhost/pages/books.html
(1)出现错误的原因?
报错原因:SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错误。静态资源应该交给Tomcat处理,而不是springmvc处理。
(2)SpringMVC为什么会拦截静态资源呢?
(3)解决方案:SpringMVC将静态资源进行放行。
使用资源处理器,当访问请求路径/pages/????时候,从文件/pages目录下查找内容。
两种方法,主要用方法二。
方法一:麻烦底层(了解)
①创建 SpringMvcSupport 配置类,设置静态资源访问过滤。
继承WebMvcConfigurationSupport 类,重写addResourceHandlers方法。
//注意有@Configuration,注解设为配置类。现在学到的配置类就它和springmvc和spring配置类有@Configuration @Configuration //继承webmvc配置支持类WebMvcConfigurationSupport public class SpringMvcSupport extends WebMvcConfigurationSupport { //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载 @Override //addResourceHandlers译为添加资源处理器 protected void addResourceHandlers(ResourceHandlerRegistry registry) { //addResourceHandler("/pages/**")是拦截请求路径,addResourceLocations("/pages/")是映射到文件真实地址。这样就可以让别人访问服务器的本地文件了 //当访问请求路径/pages/????时候,从文件/pages目录下查找内容。注意addResourceHandler里两个通配符,addResourceLocations后面是斜杠没通配符 registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); //当访问/js/????时候,从/js目录下查找内容 registry.addResourceHandler("/js/**").addResourceLocations("/js/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); } }
②SpringMvcConfig扫描SpringMvcSupport配置类
注意:SpringMvcSupport 配置类是在config目录下,SpringMVC扫描的是controller包,所以该配置类还未生效,要想生效需要将SpringMvcConfig配置类进行修改
@Configuration @ComponentScan({"com.itheima.controller","com.itheima.config"}) @EnableWebMvc public class SpringMvcConfig { } 或者 @Configuration @ComponentScan("com.itheima") @EnableWebMvc public class SpringMvcConfig { }
方法二:简单快速(推荐)
SpringMvcConfig 实现WebMvcConfigurer 接口,既可以添加资源处理器,也可以添加拦截器,拦截器在下一章ssm整合中具体讲。既然不用config的SpringMvcSupport了,springmvc配置类也就不用扫描config目录了
@Configuration
@ComponentScan({"package1.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterception projectInterception;
//添加拦截器,配置本地资源映射路径,在访问A(虚拟的)的时候,需要到B(实际的)的位置去访问。
// @Override
// public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(projectInterception).addPathPatterns("/books","/books/*");
//如果只拦截/books,发送http://localhost/books/100后会发现拦截器没有被执行
//registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
// }
//添加资源处理器
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
使用资源处理器,当访问请求路径/pages/????时候,从文件/pages目录下查找内容。
步骤3:修改books.html页面
SpringMVC案例
图书管理
查询
新建
编辑
删除