SpringMVC学习笔记 - 第一章 - 工作流程、Bean加载控制、请求与响应(参数接收与内容返回)、RESTful
【前置内容】Spring 学习笔记全系列传送门:
SpingMVC 学习笔记全系列传送门:
1、SpringMVC概述
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
优点
- 使用简单、开发便捷(相比于Servlet)
- 灵活性强
SpringMVC主要负责的就是
- controller如何接收请求和数据
- 如何将请求和数据转发给业务层
- 如何将响应数据转换成json发回到前端
-
三层架构与MVC模式
-
浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据
如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利
-
将后端服务器Servlet拆分成三层,分别是
web
、service
和dao
- web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
- service层主要负责业务逻辑的处理
- dao层主要负责数据的增删改查操作
servlet处理请求和数据的时候,存在的问题是一个servlet只能处理一个请求
-
针对web层进行了优化,采用了MVC设计模式,将其设计为
controller
、view
和Model
- controller负责请求和数据的接收,接收后将其转发给service进行业务处理
- service根据需要会调用dao对数据进行增删改查
- dao把数据处理完后将结果交给service,service再交给controller
- controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
- 这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作。
-
随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式
- 因为是异步调用,所以后端不需要返回view视图,将其去除
- 前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成json格式进行返回
-
2、SpringMVC入门案例
2.1 注意事项
- 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环境
2.2 案例制作
-
创建 Maven-webapp 项目,整理包结构
-
导入依赖
<?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>priv.dandelion</groupId> <artifactId>01_quickstart</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies> <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> </project>
-
创建 Controller
// 定义Controller,声明为Spring的bean @Controller public class UserController { // 设置当前操作的访问路径 @RequestMapping("/save") // 设置当前操作的返回值类型 @ResponseBody public String save() { System.out.println("user----save"); // 相应的内容直接返回 return "{'hello':'springmvc'}"; } }
-
创建配置类
// 创建SpringMVC的配置文件,加载controller对应的bean @Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
定义 Servlet 容器启动的配置类(代替 web.xml)
// 定义一个servlet容器启动的配置类,在此处加载spring配置 public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { // 加载SpringMVC容器配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); // 注册配置 ctx.register(SpringMvcConfig.class); // tomcat服务器启动时就可以加载到SpringMvcConfig.class return ctx; } // 设置那些请求归属于SpringMVC处理 @Override protected String[] getServletMappings() { // 将所有请求交给SpringMVC处理 return new String[]{"/"}; } // 加载Spring容器配置,此处暂时未用到 @Override protected WebApplicationContext createRootApplicationContext() { return null; } }
2.3 相关知识点
-
@Controller
名称 @Controller 类型 类注解 位置 SpringMVC控制器类定义上方 作用 设定SpringMVC的核心控制器bean -
@RequestMapping
名称 @RequestMapping 类型 类注解或方法注解 位置 SpringMVC控制器类或方法定义上方 作用 设置当前控制器方法请求访问路径 相关属性 value(默认),请求访问路径 -
@ResponseBody
名称 @ResponseBody 类型 类注解或方法注解 位置 SpringMVC控制器类或方法定义上方 作用 设置当前控制器方法响应内容为当前返回值,无需解析
2.4 工作流程解析
包含关系:
- Web容器
- ServletContext
- WebApplicationContext
- UserController
- /save -> save() 【SpringMVC 的映射并不是放在 bean 中管理的】
2.4.1 启动服务器初始化过程
-
服务器启动,执行 web 服务器配置类 ServletContainersInitConfig,初始化web容器
- 功能类似于以前的web.xml
-
执行createServletApplicationContext方法,创建了WebApplicationContext对象(存在于 ServletContext 中)
-
该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
// 加载SpringMVC容器配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); // 注册配置 ctx.register(SpringMvcConfig.class); // tomcat服务器启动时就可以加载到SpringMvcConfig.class return ctx; }
-
-
加载SpringMvcConfig配置类,以在下一步加载所需的bean
// 创建SpringMVC的配置文件,加载controller对应的bean @Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
执行@ComponentScan加载对应的bean
扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
-
加载UserController,每个@RequestMapping的名称对应一个具体的方法
-
此时就建立了
/save
和 save() 方法的对应关系// 定义Controller,声明为Spring的bean @Controller public class UserController { // 设置当前操作的访问路径 @RequestMapping("/save") // 设置当前操作的返回值类型 @ResponseBody public String save() { System.out.println("user----save"); // 相应的内容直接返回 return "{'hello':'springmvc'}"; } }
-
-
执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
-
/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求// 设置那些请求归属于SpringMVC处理 @Override protected String[] getServletMappings() { // 将所有请求交给SpringMVC处理 return new String[]{"/"}; }
-
2.4.2 单次请求过程
-
发送请求
http://localhost/save
-
web容器发现该请求满足SpringMVC拦截规则,将请求交给 SpringMVC 处理
-
解析请求路径 /save
-
由 /save 匹配执行对应的方法 save()
- 上面的第五步已经将请求路径和方法建立了对应关系,通过 /save 就能找到对应的save方法
-
执行 save()
-
检测到有 @ResponseBody 直接将 save() 方法的返回值作为响应体返回给请求方
2.5 bean 加载控制
2.5.1 问题分析
问题:
- 哪些 bean 交给 SpringMVC 管理,哪些包交给 Spring 管理
- 因为功能不同,如何避免 Spring 错误加载到 SpringMVC 的 bean
-
包结构
-
config目录存入的是配置类,本篇和前面的内容已经写过的配置类有:
- ServletContainersInitConfig
- SpringConfig
- SpringMvcConfig
- JdbcConfig
- MybatisConfig
-
controller 目录存放的是 SpringMVC 的 controller 类
-
service 目录存放的是 service 接口和实现类
-
dao 目录存放的是 dao/Mapper 接口
-
-
管理
- SpringMVC加载其相关bean
- 表现层 bean(Controller),也就是controller包下的类
- Spring控制的bean
- 业务 bean(Service)
- 功能 bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)
- SpringMVC加载其相关bean
2.5.2 思路分析
加载Spring控制的bean的时候排除掉 SpringMVC 控制的 bean
- 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- 方式二:Spring加载的bean设定扫描范围为priv.dandelion,排除掉controller包中的bean
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
2.5.3 环境准备
-
创建 Web 的 Maven 项目,删除 web.xml 配置文件
-
依赖
<?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>priv.dandelion</groupId> <artifactId>02_bean_load</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> </dependencies> <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> </project>
-
创建对应的配置类
-
替代 web.xml 的 web 服务器配置类 ServletContainersInitConfig
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; } }
-
SpringMVC 配置类
@Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
Spring 配置类
@Configuration @ComponentScan("priv.dandelion") public class SpringConfig { }
-
-
实体类
public class User { private Integer id; private String name; private Integer age; // Getter // Settrt // toString }
-
Dao 接口
public interface UserDao { @Insert("insert into tbl_user(name,age)values(#{name},#{age})") public void save(User user); }
-
Service 实现类 (接口不表)
@Service public class UserServiceImpl implements UserService { public void save(User user) { System.out.println("user service ..."); } }
-
Controller
@Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'info':'springmvc'}"; } }
2.5.4 设置 bean 加载控制
-
web服务器启动时加载配置类的相关配置
-
标准方式
- 方式
- 修改 createRootApplicationContext() 方法中的内容
- 与 createServletApplicationContext() 基本相同但是加载 SpringConfig.class
- 修改 createRootApplicationContext() 方法中的内容
- 说明
- createServletApplicationContext() 加载的是 SpringMVC 环境配置
- createRootApplicationContext() 加载的是 Spring 环境配置
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; } }
- 方式
-
简化方式
说明:
- AbstractDispatcherServletInitializer 类包含一个子类 AbstractAnnotationConfigDispatcherServletInitializer 可以简化配置
- 方法名中包含 RootConfig 的是 Spring 的配置,包含 ServletConfig 的是对 SpringMVC 的配置,与标准方式中的相同
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
-
-
bean 加载控制方式
-
方案一:修改Spring 配置类,精准扫描
说明:
- 此处使用了MyBatis技术且是自动代理,所以可以不扫Dao
- 但是建议按照标准开发规则书写,通用性强
@Configuration @ComponentScan({"priv.dandelion.service","priv.dandelion.dao"}) public class SpringConfig { }
-
方案二
说明:
-
扫描所有的包
-
但是使用过滤器进行排除
- 过滤的类型是按注解过滤,过滤Controller注解
使用到的属性:
-
excludeFilters属性:设置扫描加载bean时,排除的过滤规则
-
type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
- ANNOTATION:按照注解排除
- ASSIGNABLE_TYPE:按照指定的类型过滤
- ASPECTJ:按照Aspectj表达式排除,基本上不会用
- REGEX:按照正则表达式排除
- CUSTOM:按照自定义规则排除
大家只需要知道第一种ANNOTATION即可
-
classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
@Configuration @ComponentScan( value = "priv.dandelion", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig { }
-
-
方式三(不区分 Spring 与 SpringMVC 的环境)【此处不做详细说明】
-
2.5.5 相关知识点:@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
相关属性 | excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes) |
3、请求与相应
3.1 设置请求映射路径
本小节注意:
- 当类上和方法上都添加了
@RequestMapping
注解,前端发送请求的时候,要和两个注解的value值相加匹配才能访问到。- @RequestMapping注解value属性前面加不加
/
都可以
3.1.1 环境准备
-
依赖
<?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>priv.dandelion</groupId> <artifactId>03_request_mapping</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies> <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> </project>
-
配置类
-
Spring 配置类(此处未使用到)
@Configuration @ComponentScan(value = "priv.dandelion", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig { }
-
SpringMVC 配置类
@Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
web服务器配置类 (简化配置)
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]; } }
-
-
Controller
-
UserController
@Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'module':'user save'}"; } @RequestMapping("/delete") @ResponseBody public String delete(){ System.out.println("user delete ..."); return "{'module':'user delete'}"; } }
-
BookController
@Controller public class BookController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("book save ..."); return "{'module':'book save'}"; } }
-
3.1.2 问题分析
-
以上环境准备完成后,启动服务器时会报错
[INFO] Initializing Servlet 'dispatcher' [WARNING] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method priv.dandelion.controller.UserController#save() to { /save}: There is already 'bookController' bean method priv.dandelion.controller.BookController#save() mapped. [ERROR] Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method priv.dandelion.controller.UserController#save() to { /save}: There is already 'bookController' bean method priv.dandelion.controller.BookController#save() mapped. at ... at ... ...
-
从错误信息可知
- UserController有一个save方法,访问路径为
http://localhost/save
- BookController也有一个save方法,访问路径为
http://localhost/save
- 当访问
http://localhost/saved
的时候,到底是访问 UserController 还是 BookController,就会出现冲突
- UserController有一个save方法,访问路径为
-
解决方案:为不同模块设置模块名作为请求路径前置
- 对于Book模块的save,将其访问路径设置
http://localhost/book/save
- 对于User模块的save,将其访问路径设置
http://localhost/user/save
- 对于Book模块的save,将其访问路径设置
3.1.3 设置映射路径
-
方案一(耦合度高不推荐):对每一个资源的 RequestMapping 进行修改
-
UserController
@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 delete(){ System.out.println("user delete ..."); return "{'module':'user delete'}"; } }
-
BookController(不表)
-
-
方案二:为 Controller 添加一个整体的 RequestMapping(称为请求路径前缀),其他不变
-
UserController
@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 delete(){ System.out.println("user delete ..."); return "{'module':'user delete'}"; } }
-
BookController(不表)
-
3.2 请求参数
3.2.1 环境准备
-
依赖、配置类见 3.1.1
-
实体类
-
Address
public class Address { private String province; private String city; // Getter,Setter,toString不表 }
-
User
public class User { private String name; private int age; // Getter,Setter,toString不表 }
-
-
Controller
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(){ return "{'module':'commonParam'}"; } }
3.2.2 参数传递及中文乱码处理方案
Get请求与参数:
http://localhost/commonParam?name=dandelion&age=18
POST请求与参数:
- 发送Post请求时,参数放在请求体中,若使用PostMan工具,参数需要写在Body模块中,发送表单数据时使用
x-www-from-urlencoded
(form-data
除了发送表单之外还可以发送文件)
-
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中文乱码:配置pom.xml
Tomcat8.5以后的版本已经处理了中文乱码的问题,但是IDEA中的Tomcat插件目前只到Tomcat7,所以需要修改pom.xml来解决GET请求中文乱码问题
<build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port><!--tomcat端口号--> <path>/</path> <!--虚拟目录--> <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集--> </configuration> </plugin> </plugins> </build>
-
-
POST请求
-
接收参数
POST请求接收参数代码与GET请求一致
-
POST中文乱码问题:设置过滤器
-
在web服务器配置类 ServletContainersInitConfig 中重写
getServletFilters()
方法,创建所需的过滤器对象,并设置编码字符集为UTF-8
-
CharacterEncodingFilter 是在 spring-web 包中,所以用之前需要导入对应的 jar 包
import org.springframework.web.filter.CharacterEncodingFilter;
@Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; }
-
-
3.3 五种类型参数传递
3.3.1 普通参数
普通参数的基本使用已经实现过,详见 3.2.2 参数传递及中文乱码处理方案
-
解决请求中的参数名称和 Controller 方法的参数不一致问题
- 当出现请求中的参数名称和Controller中方法的参数名不匹配时,无法正常接收到参数
- 使用 @RequestPaam() 注解修饰不一致的参数,为其指定需要匹配的请求参数
@Controller public class UserController { // `http://localhost/commonParam?username=dandelion&age=12` @RequestMapping("/commonParam") @ResponseBody public String commonParam(@RequestParam("username") String name, int age){ System.out.println("普通参数name:"+ name); System.out.println("普通参数age:"+ age); return "{'module':'commonParam'}"; } }
3.3.2 POJO 数据类型
- 直接使用一个实体类作为形参,框架会使用 setter 自动将数据进行写入
- 实体类中的属性名称需要和请求参数的名称保持一致,否则接收不到
- 若实体类中没有对应的 setter 可以和请求参数的参数名匹配,则默认零假空(未进行写入),可以使用该特性在实际开发中减少工作量
-
实体类
public class User { private String name; private int age; // Getter,Setter,toString不表 }
-
Controller
@Controller public class UserController { @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("POJO参数:"+ user); return "{'module':'pojoParam'}"; } }
3.3.3 嵌套 POJO 类型参数
对于嵌套的POJO类型,在进行参数传递时,也要使用嵌套的形式来书写请求参数名称
http://localhost/pojoParam?name=dandelion&age=12&address.province=hubei&address.city=wuhan
-
实体类
-
User
public class User { private String name; private int age; private Address address; // Getter,Setter,toString不表 }
-
Address
public class Address { private String province; private String city; // Getter,Setter,toString不表 }
-
-
Controller
Controller 部分代码与 3.3.3 嵌套 POJO 类型参数一致
3.3.4 数组类型参数
- 请求参数为数组时,不同的数组元素使用相同的请求参数名称
- 接收请求参数时,使用数组作为形参
-
请求
http://localhost/arrayParam?person=zhangsan&person=lisi&person=wangwu
-
接收
@Controller public class UserController { @RequestMapping("/arrayParam") @ResponseBody public String arrayParam(String[] person){ System.out.println("数组参数:"+ Arrays.toString(person)); return "{'module':'arrayParam'}"; } }
3.3.5 集合类型参数
-
请求
与数组参数的请求方式相同
-
接收
-
错误案例
以下代码段运行时会报错:
NoSuchMethodException: java.util.List.<init>()
,缺少构造,(内心:废话 List 接口哪来的构造)严重: Servlet.service() for servlet [dispatcher] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No primary or default constructor found for interface java.util.List] with root cause java.lang.NoSuchMethodException: java.util.List.<init>()
@Controller public class UserController { @RequestMapping("/listParam") @ResponseBody public String listParam(List<String> person){ System.out.println("集合参数:"+ person); return "{'module':'listParam'}"; } }
-
原因及解决方案
-
问题原因
SpringMVC 将 List 看做是一个 POJO 对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是 List 是一个接口无法创建对象,所以报错。
-
解决方案:使用
@RequestParam
注解- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系
- 显而易见,对于简单数据类型使用数组会比集合更简单些。
@Controller public class UserController { @RequestMapping("/listParam") @ResponseBody public String listParam(@RequestParam List<String> person){ System.out.println("集合参数:"+ person); return "{'module':'listParam'}"; } }
-
-
3.3.6 相关知识点:@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
3.4 JSON 数据传输参数
- 参数放在请求体中,若使用 PostMan 工具,参数需要写在Body模块中,发送表单数据时使用
raw
,并将数据格式修改为 JSON
3.4.1 JSON 数据传输参数分类与准备工作
-
参数分类
// json普通数组 ["value1","value2","value3",...] // json对象 {"key1":"value1","key2":"value2",...} // json对象数组 [{"key11":"value11",...},{"key21":"value21",...}]
-
准备工作
-
添加依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
-
开启SpringMVC注解驱动,用于开启 json 数据类型自动转换:
@EnableWebMvc
@Configuration @ComponentScan("priv.dandelion.controller") @EnableWebMvc public class SpringMvcConfig { }
-
参数前添加@RequestBody
- 使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
- 区别于 @RequestParam,本小节总结部分会进行说明
- 区别于 @ResponseBody,书写要正确
- 下文中展示
-
3.4.2 三种参数传输格式
3.4.2.1 json普通数组
-
JSON
["zhangsan","lisi","wangwu"]
-
接收
@RequestMapping("/listParamForJson") @ResponseBody // //使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据 public String listParamForJson(@RequestBody List<String> person){ System.out.println("list common(json)参数传递 list:" + person); return "{'module':'list common for json param'}"; }
3.4.2.2 json对象
- 若 JSON 对象中的 key 与实体类中的 setter 名称(标准书写)不能匹配时,不执行 setter ,实体类中的数据不变(不进行其他操作时默认为零假空)
- 同理,若不传递某一属性的值,实体类中的数据不变(不进行其他操作时默认为零假空)
-
JSON
// 单个POJO { "name":"dandelion", "age":12 } // 嵌套POJO { "name1":"dandelion", "age":12, "address":{ "province":"provinceName", "city":"cityName" } }
-
实体类
见 3.3.3
-
接收
@RequestMapping("/pojoParamForJson") @ResponseBody public String pojoParamForJson(@RequestBody User user){ System.out.println("pojo(json)参数传递 user:"+user); return "{'module':'pojo for json param'}"; }
3.4.2.3 json对象数组
-
JSON
[ {"name":"dandelion","age":15,"address":{"province":"provinceName","city":"cityName"}}, {"name":"dandelion000","age":12} ]
-
接收
@RequestMapping("/listPojoParamForJson") @ResponseBody public String listPojoParamForJson(@RequestBody List<User> list){ System.out.println("list pojo(json)参数传递 list:"+list); return "{'module':'list pojo for json param'}"; }
3.4.3 相关知识点
-
知识点1:
@EnableWebMvc
名称 @EnableWebMvc 类型 配置类注解 位置 SpringMVC配置类定义上方 作用 开启SpringMVC多项辅助功能 -
知识点2:
@RequestBody
-
整理
名称 @RequestBody 类型 形参注解 位置 SpringMVC控制器方法形参定义前面 作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 -
@RequestBody与@RequestParam区别
-
区别
- @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
- @RequestBody用于接收json数据【application/json】
-
应用
- 后期开发中,发送json格式数据为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
-
-
3.5 日期类型参数传递
-
接收案例
-
请求
http://localhost/dataParam?date=2022/02/22
-
接收
@RequestMapping("/dataParam") @ResponseBody public String dataParam(Date date, Date date1){ System.out.println("参数传递 date:"+date); System.out.println("参数传递 date1:"+date1); return"{'module':'data param'}"; }
-
结果
-
发送请求后会发现该部分代码会报错,但若请求参数中只有date而没有date1时则正常接收
-
报错信息:
方法参数类型不匹配,在将 String 转换为 Date 时出现问题,转换失败
[WARNING] 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 '2022-02-22'; nested exception is java.lang.IllegalArgumentException]
-
-
-
解决方案(接收任意日期格式的方法):使用 @DateTimeFormat 指定日期格式
-
请求
http://localhost/dataParam?date=2022/02/22&date1=22-02-2022&date2=2022-02-22 22:22:22
-
接收
@RequestMapping("/dataParam") @ResponseBody public String dataParam(Date date, @DateTimeFormat(pattern = "dd-MM-yyyy") Date date1, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date2){ System.out.println("参数传递 date:"+ date); System.out.println("参数传递 date1(dd-MM-yyyy):"+ date1); System.out.println("参数传递 date2(yyyy-MM-dd HH:mm:ss):"+ date2); return"{'module':'data param'}"; }
-
-
相关知识点
-
@DateTimeFormat
名称 @DateTimeFormat 类型 形参注解 位置 SpringMVC控制器方法形参前面 作用 设定日期时间型数据格式 相关属性 pattern:指定日期时间格式字符串 -
内部实现原理
SpringMVC中提供了很多类型转换接口和实现类,其中有 Converter 接口
-
Converter 接口
-
Converter所属的包为
org.springframework.core.convert.converter
-
框架中有提供很多对应Converter接口的实现类,用来实现不同数据类型之间的转换,如:
-
请求参数年龄数据(String→Integer)
-
日期格式转换(String → Date)
/** * S: the source type * T: the target type */ public interface Converter<S, T> { @Nullable //该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回 T convert(S source); }
-
-
HttpMessageConverter 接口
该接口是实现对象与 JSON 之间的转换工作,使用时在SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不省略
-
-
3.6 响应
3.6.1 环境准备
-
依赖
<?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>priv.dandelion</groupId> <artifactId>05_response</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> </dependencies> <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> </project>
-
配置类
-
服务器配置类
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]; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; } }
-
SpringMVC 配置类
@Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
-
实体类
public class User { private String name; private int age; //getter...setter...toString省略 }
-
webapp下创建页面 page.jsp
<html> <body> <h2>Hello Spring MVC!</h2> </body> </html>
-
Controller
@Controller public class UserController { }
3.6.2 响应页面(了解)
- 注意此处不能使用@ResponseBody,否则会将返回值内容作为字符串返回给前端
- 注意进行页面跳转时,返回值为页面名称,返回值类型为字符串
@RequestMapping("/toJumpPage")
public String toJumpPage() {
System.out.println("跳转页面");
return "page.jsp";
}
3.6.3 返回文本数据(了解)
- 注意此处 @ResponseBody 注解就不能省略
- 如果省略了会把
response text
当前页面名称去查找,如果没有回报404
@RequestMapping("/toText")
@ResponseBody
public String toText() {
System.out.println("返回纯文本数据");
return "response text";
}
3.6.4 响应 JSON 数据
准备工作:
开启SpringMVC注解驱动,用于开启 json 数据类型自动转换:
@EnableWebMvc
@Configuration @ComponentScan("priv.dandelion.controller") @EnableWebMvc public class SpringMvcConfig { }
3.6.4.1 响应 POJO 对象
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO() {
System.out.println("返回JSON数据对象");
User user = new User();
user.setName("dandelion");
user.setAge(12);
return user;
}
3.6.4.2 响应 POJO 集合对象
此处返回的是POJO的集合,基本数据类型的集合同理
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList() {
System.out.println("返回JSON数据对象");
User user1 = new User();
user1.setName("dandelion");
user1.setAge(12);
User user2 = new User();
user2.setName("dandelion000");
user2.setAge(15);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
return users;
}
3.6.4.3 相关知识点:@ResponseBody
-
整理
名称 @ResponseBody 类型 方法\类注解 位置 SpringMVC控制器方法定义上方和控制类上 作用 设置当前控制器返回值作为响应体,
写在类上,该类的所有方法都有该注解功能相关属性 pattern:指定日期时间格式字符串 -
说明
-
该注解可以写在类上或者方法上
-
写在类上就是该类下的所有方法都有@ReponseBody功能
-
当方法上有@ReponseBody注解后
- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成JSON响应给前端
-
此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:
-
对象转Json数据(POJO -> json)
-
集合转Json数据(Collection -> json)
-
-
4、REST风格
4.1 REST 简介
-
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格
-
REST风格与传统风格的区别
-
传统风格资源描述形式
http://localhost/user/getById?id=1
查询id为1的用户信息http://localhost/user/saveUser
保存用户信息
-
REST风格描述形式
http://localhost/user/1
http://localhost/user
-
-
REST风格的优点
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
-
REST风格的使用
-
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
- GET(查询)
http://localhost/users
查询全部用户信息 - GET(查询)
http://localhost/users/1
查询指定用户信息 - POST(新增/保存)
http://localhost/users
添加用户信息 - PUT(修改/更新)
http://localhost/users
修改用户信息 - DELETE(删除)
http://localhost/users/1
删除用户信息
- GET(查询)
-
请求方式:按照不同的请求方式代表不同的操作类型
- 发送GET请求是用来做查询
- 发送POST请求是用来做新增
- 发送PUT请求是用来做修改
- 发送DELETE请求是用来做删除
-
-
注意
- 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
- REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
- REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但是我们如果非要用GET请求做删除,这点在程序上运行是可以实现的
- 但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
- 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts......
- 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
-
RESTful
- 根据REST风格对资源进行访问称为RESTful
4.2 RESTful 入门案例
4.2.1 环境准备
-
依赖
<?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>priv.dandelion</groupId> <artifactId>06_rest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> </dependencies> <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> </project>
-
配置类
-
服务器配置类
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]; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; } }
-
SpringMVC配置类,开启json数据类型自动转换
@Configuration @ComponentScan("priv.dandelion.controller") @EnableWebMvc public class SpringMvcConfig { }
-
-
实体类
-
User
public class User { private String name; private int age; //getter...setter...toString省略 }
-
Book
public class Book { private String name; private double price; //getter...setter...toString省略 }
-
-
Controller
-
UserController
@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'}"; } }
-
BookController
@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'}"; } }
-
4.2.2 思路分析
- 将之前的增删改查替换成RESTful的开发方式
- 修改前: 新增: /save ,修改: /update,删除 /delete...
- 修改后:
- 增删改查: /users
- 根据GET查询、POST新增、PUT修改、DELETE删除对方法的请求方式进行限定
4.2.3 修改 RESTFUL 风格
4.2.3.1 各项操作
-
新增
-
请求
POST http://localhost/users
-
接收
@RequestMapping(value = "/users", method = RequestMethod.POST) @ResponseBody public String save(@RequestBody User user) { System.out.println("user save..."+user); return "{'module':'user save'}"; }
-
-
删除
-
请求
DELETE http://localhost/users/1
-
接收
-
使用REST风格时,参数写在请求路径中
-
接收时对参数使用 @PathVariable 注解,在资源路径的参数位置使用
{}
,其中的值应和参数名称保持一致 -
若参数名称和
{}
中的内容不一致,应手动进行绑定,使用 @PathVariable 注解的 value 属性,如下:@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable("id") Integer userId) {}
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}"; }
-
-
-
修改
-
请求
PUT http://localhost/users
-
接收
@RequestMapping(value = "/users", method = RequestMethod.PUT) @ResponseBody public String update(@RequestBody User user) { System.out.println("user update..." + user); return "{'module':'user update'}"; }
-
-
查询
-
查询单个
-
请求
GET http://localhost/users/1
-
接收
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET) @ResponseBody public String getById(@PathVariable Integer id) { System.out.println("user getById..." + id); return "{'module':'user getById'}"; }
-
-
查询所有
-
请求
GET http://localhost/users
-
接收
@RequestMapping(value = "/users", method = RequestMethod.GET) @ResponseBody public String getAll() { System.out.println("user getAll..."); return "{'module':'user getAll'}"; }
-
-
4.2.3.2 相关知识点:@PathVarlable
名称 | @PathVariable |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应 |
4.2.3.3 相关知识点:三种接收参数的注解
-
区别
- @RequestParam 用于接收url地址传参或表单传参
- @RequestBody 用于接收json数据
- @PathVariable 用于接收路径参数,使用{参数名称}描述路径参数
-
应用
- 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody 应用较广
- 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数
- 采用 RESTful 进行开发,当参数数量较少时,例如1个,可以采用 @PathVariable 接收请求路径变量,通常用于传递id值
4.3 RESTFUL 快速开发
4.3.1 开发简化
-
未简化代码
@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'}"; } }
-
简化过程(参见注释)
- 将请求路径中共同的部分抽取
- 将方法上@RequestMapping中不需要的value属性省略
- 将所有方法上的@ResponseBody抽取
- 存在@Controller和@ResponseBody,可以使用@RestController代替
- 使用@PostMapping、@DeleteMapping等注解代替@RequestMapping
// @Controller // 3.所有方法均需要@ResponseBody,抽取 // @ResponseBody @RestController // 4.其中包含了@Controller和@ResponseBody // 1.以前缀的形式将请求路径中相同的内容抽取出来 @RequestMapping("/books") public class BookController { // 2.value属性已经完全被抽取,可以省略 // @RequestMapping(method = RequestMethod.POST) // 5.简化上一行代码,下同 @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'}"; } }
-
简化后代码
@RestController @RequestMapping("/books") public class BookController { @PostMapping public String save(@RequestBody Book book){ System.out.println("book save..." + book); return "{'module':'book save'}"; } @DeleteMapping("/{id}") public String delete(@PathVariable Integer id){ System.out.println("book delete..." + id); return "{'module':'book delete'}"; } @PutMapping public String update(@RequestBody Book book){ System.out.println("book update..." + book); return "{'module':'book update'}"; } @GetMapping("/{id}") public String getById(@PathVariable Integer id){ System.out.println("book getById..." + id); return "{'module':'book getById'}"; } @GetMapping public String getAll(){ System.out.println("book getAll..."); return "{'module':'book getAll'}"; } }
4.3.2 相关知识点
-
知识点1:@RestController
名称 @RestController 类型 类注解 位置 基于SpringMVC的RESTful开发控制器类定义上方 作用 设置当前控制器类为RESTful风格,
等同于@Controller与@ResponseBody两个注解组合功能 -
知识点2:@GetMapping @PostMapping @PutMapping @DeleteMapping
名称 @GetMapping @PostMapping @PutMapping @DeleteMapping 类型 方法注解 位置 基于SpringMVC的RESTful开发控制器方法定义上方 作用 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,
例如@GetMapping对应GET请求相关属性 value(默认):请求访问路径
4.4 RESTFUL 案例
4.4.1 需求分析
4.4.2 环境准备
-
依赖
<?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>priv.dandelion</groupId> <artifactId>07_rest_case</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> </dependencies> <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> </project>
-
配置类
-
服务器配置类
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]; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; } }
-
SpringMVC配置类
@Configuration @ComponentScan("priv.dandelion.controller") @EnableWebMvc public class SpringMvcConfig { }
-
-
实体类
public class Book { private Integer id; private String type; private String name; private String description; //setter...getter...toString略 }
-
控制器
@Controller public class BookController { }
4.4.3 后台接口开发
@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<Book> getAll() {
System.out.println("book getAll is running ...");
List<Book> bookList = new ArrayList<Book>();
Book book1 = new Book();
book1.setType("计算机");
book1.setName("SpringMVC1");
book1.setDescription("123");
bookList.add(book1);
Book book2 = new Book();
book2.setType("计算机");
book2.setName("SpringMVC2");
book2.setDescription("234");
bookList.add(book2);
Book book3 = new Book();
book3.setType("计算机丛书");
book3.setName("SpringMVC3");
book3.setDescription("345");
bookList.add(book3);
return bookList;
}
}
4.4.4 页面访问处理
-
页面准备
-
环境:vue.js + axios + elementui
-
页面
<!DOCTYPE html> <html> <head> <!-- 页面meta --> <meta charset="utf-8"> <title>SpringMVC案例</title> <!-- 引入样式 --> <link rel="stylesheet" href="../plugins/elementui/index.css"> <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="../css/style.css"> </head> <body class="hold-transition"> <div id="app"> <div class="content-header"> <h1>图书管理</h1> </div> <div class="app-container"> <div class="box"> <div class="filter-container"> <el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input> <el-button class="dalfBut">查询</el-button> <el-button type="primary" class="butT" @click="openSave()">新建</el-button> </div> <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row> <el-table-column type="index" align="center" label="序号"></el-table-column> <el-table-column prop="type" label="图书类别" align="center"></el-table-column> <el-table-column prop="name" label="图书名称" align="center"></el-table-column> <el-table-column prop="description" label="描述" align="center"></el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button type="primary" size="mini">编辑</el-button> <el-button size="mini" type="danger">删除</el-button> </template> </el-table-column> </el-table> <div class="pagination-container"> <el-pagination class="pagiantion" @current-change="handleCurrentChange" :current-page="pagination.currentPage" :page-size="pagination.pageSize" layout="total, prev, pager, next, jumper" :total="pagination.total"> </el-pagination> </div> <!-- 新增标签弹层 --> <div class="add-form"> <el-dialog title="新增图书" :visible.sync="dialogFormVisible"> <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="图书类别" prop="type"> <el-input v-model="formData.type"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="图书名称" prop="name"> <el-input v-model="formData.name"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="描述"> <el-input v-model="formData.description" type="textarea"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取消</el-button> <el-button type="primary" @click="saveBook()">确定</el-button> </div> </el-dialog> </div> </div> </div> </div> </body> <!-- 引入组件库 --> <script src="../js/vue.js"></script> <script src="../plugins/elementui/index.js"></script> <script type="text/javascript" src="../js/jquery.min.js"></script> <script src="../js/axios-0.18.0.js"></script> <script> var vue = new Vue({ el: '#app', data:{ dataList: [],//当前页要展示的分页列表数据 formData: {},//表单数据 dialogFormVisible: false,//增加表单是否可见 dialogFormVisible4Edit:false,//编辑表单是否可见 pagination: {},//分页模型数据,暂时弃用 }, //钩子函数,VUE对象初始化完成后自动执行 created() { this.getAll(); }, methods: { // 重置表单 resetForm() { //清空输入框 this.formData = {}; }, // 弹出添加窗口 openSave() { this.dialogFormVisible = true; this.resetForm(); }, //添加 saveBook () { axios.post("/books",this.formData).then((res)=>{ }); }, //主页列表查询 getAll() { axios.get("/books").then((res)=>{ this.dataList = res.data; }); }, } }) </script> </html>
-
-
页面访问存在问题及解决方案
-
存在问题:无法访问到页面
-
报错内容
[WARNING] Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation] [WARNING] No mapping for GET /pages/books.html
-
原因
现在发送请求
http://localhost/pages/books.html
访问这个页面,但是被SpringMVC拦截,认为应该有一个 Controller 中方法的 RequestMapping 叫这个名字
-
-
解决方案 放行非SpringMVC的请求
- 遇到这种情况 SpringMVC 应当放行,由 web 服务器进行处理
- web 服务器配置类中 getServletMappings 拦截了所有的请求,交给 SpringMVC 处理,需要为 SpringMVC 的配置添加过滤,放行页面请求
- 对页面的过滤一般单独抽成一个功能类
-
功能类 SpringMvcSupport
- 需要继承 WebMvcConfigurationSupport,覆写 addResourceHandlers ,操作 registry 属性
- 添加 @Configuration 注解,以用于包扫描
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { // 添加资源过滤 @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { // 当访问页面时,如果请求的是/pages/**,不走MVC,走/pages目录下的内容 registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); registry.addResourceHandler("/js/**").addResourceLocations("/js/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); // ... } }
-
配置包扫描
@Configuration @ComponentScan({"priv.dandelion.controller","priv.dandelion.config"}) @EnableWebMvc public class SpringMvcConfig { }
-