SpringMVC入门
SpringMVC概述
随着互联网的发张,传统的MVC设计模式因为是同步调用,性能慢慢的跟不上需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。前端如果是通过异步调用的方式进行交互,后台就需要将返回的数据转化成json格式进行返回。
SpringMVC是一种基于Java实现的MVC模型的轻量级Web框架。
SpringMVC主要负责的就是:
- controller如何接收请求和数据
- 如何将请求和数据发给业务层
- 如何将响应数据转化为json发回前端
SpringMVC入门案例
Servlet开发步骤
在进行SpringMVC入门案例之前,先来回顾使用Servlet是如何开发的。步骤如下:
- 创建Web项目(maven结构)
- 配置Tomcat服务器
- 在pom.xml中导入servlet坐标
- 定义处理请求的功能类(UserServlet)
- 设置请求映射(配置映射关系)
SpringMVC创建过程
SpringMVC的创建过程和创建Servlet的流程几乎一致,具体如下:
-
创建Web工程(maven结构)
使用骨架的方式创建maven项目
-
配置tomcat服务器
-
导入SpringMVC和Servlet坐标
-
定义处理请求的功能类(UserController)
-
设置请求映射(配置映射关系)
-
将SpringMVC设置加载到Tomcat容器中
以上步骤中的第4、5、6步是关键,具体操作如下:
- 创建配置类
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
- 创建Controller类
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save....");
return "{'info':springmvc}";
}
}
在创建Controller类这一步,已经完成了定义处理请求的功能类和设置请求映射。
在这里有几点需要注意一下。1.SpringMVC中功能类上的注解必须使用@Controller而不能使用@Component。2.通过在方法上添加注解@RequestMapping(“/xxx”)配置映射关系。3.save()方法要有String类型返回,没有添加@ResponseBody时,返回的是页面名称。添加@ResponseBody注解后,返回的是json数据。
- 使用配置类替换web.xml
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类
@Override
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置由springmvc控制器处理的请求映射路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
通过使用配置类替换web.xml将SpringMVC加载到tomcat容器中。在这一步中,用于替换web.xml的配置类需要继承抽象类AbstractDispatcherServletInitializer
,并重写其中的三个方法createServletApplicationContext()
、getServletMappings()
和createRootApplicationContext()
。三者的作用分别是加载springmvc配置类、设置由控制器处理的请求映射路径以及加载spring配置类
至此SpringMVC的入门案例就已经完成。
SpringMVC入门案例总结
入门案例总结
使用SpringMVC开发需要做的事情可以理解成1+n。
一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
- SpringMVC核心配置类(设置配置类,扫描controller包,加载controller控制器bean)
多次工作
- 定义处理请求的控制类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(ResponseBody)
启动服务器初始化过程
- 服务器启动,执行
ServletContainersInitConfig
类,初始化web容器。功能类似于以前的web.xml - 执行
ServletContainersInitConfig
类中的createServletApplicationContext
方法,创建WebApplicationContext
对象。该方法加载了SpringMVC的配置类SpringConfig
来初始化SpringMVC容器。 - 加载SpringMvcConfig配置类
- 执行@ComponentScan加载对应的bean。
- 加载UserController,每个@RequestMapping的名称对应一个具体方法
- 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则。
return new String[]{"/"}
代表拦截所有请求。
Bean加载控制
SpringMVC整合Spring时遇到的问题
在开发项目时,controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载?
- SpringMVC加载其相关bean(表现层bean),也就是controller包下的类。
- Spring控制的bean包括业务层bean、DataSource、SqlSessionFactoryBean、MapperScannerConfigurer等。
在之前的项目中,springcofig类配置的@Component(“com.itheima”)把属于SpringMVC的controller类也给扫描到了。所以接下来我们要解决的问题是:如何避免Spring错误加载到SpringMVC的bean?
避免Spring错误加载SpringMVC的bean的解决方法
实现思路
- 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包下的bean
- 方式三:不区分Spring和SpringMVC的环境,加载到同一个环境中(了解)
环境准备
SpringConfig类代码如下:
@Configuration
@EnableTransactionManagement
@ComponentScan({"com.itheima"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
SpringMvcConfig类代码如下:
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
接着,我们定义一个App类用来测试Spring的容器是否存在controller包下类的bean。App类的代码如下:
在这里插入代码片public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = ctx.getBean(UserController.class);
System.out.println(userController);
}
}
通过运行App类,控制台获取打印结果为:
com.itheima.controller.UserController@62e6b5c8
。
说明SpringMVC的bean被Spring错误的加载了。
设置bean加载控制
方式一:修改Spring配置类,设定扫描范围为精确范围
@Configuration
@EnableTransactionManagement
@ComponentScan({"com.itheima.dao","com.itheima.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
修改后运行发现获取不到指定bean,代码报错。说明通过修改@ComponentScan为精确扫描可以解决问题。
方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包下的bean
@Configuration
@EnableTransactionManagement
@ComponentScan(value = "com.itheima",excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class))
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
注意:测试的时候,需要把SpringMvcConfig配置类上的@ComponentScan注解注释掉,否则不会报错。
tomcat服务器加载Spring配置类
使用new AnnotationConfigWebApplicationContext();
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类
@Override
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置由springmvc控制器处理的请求映射路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
Web配置类更简单的配置方式
Web配置类通过继承AbstractAnnotationConfigDispatcherServletInitializer
类
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[]{"/"};
}
}
PostMan工具的使用
PostMan:一款功能强大的网页调试和发送网页HTTP请求的Chrom插件
作用:常用于进行接口测试
具体使用方法可以查看官网
请求与响应
SpringMVC是web层的框架,主要的作用是接收请求、接收数据、响应结果。所以请求和响应是学习SpringMVC的重要内容,主要内容有以下四部分:
- 请求映射路径
- 请求参数
- 日期类型参数传递
- 响应json数据
请求映射路径
场景设想:在Controller包下可能存在多个控制类,并且不同控制类中方法的@RequestMapping的值都是相同的。这样在浏览器中输入路径会指向多个控制类的方法。很显然这样是不行的。
解决思路:为不同模块设置模块名作为请求路径前置
代码实现:在Controller类上添加@RequestMapping
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save....");
return "{'info':springmvc}";
}
}
通过修改成以上代码,在浏览器中输入http://localhost:8080/springmvc/user/save
即可访问到UserController中的save方法。
请求参数
请求映射路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以收到前端的请求,接收到请求后,**如何接收到请求页面传递的参数?**这又是一个问题。
关于请求参数的传递和接收是与请求方式是有关系的,目前比较常见的两种请求方式为:
- GET
- POST
接下来我们来为两种的请求方式的不同情况编写后端代码,只需修改方法的形参列表即可。
参数传递
- GET发送单个参数
@RequestMapping("/delete")
@ResponseBody
public String delete(String name){
System.out.println("user delete....name:"+name);
return "{'info':springmvc}";
}
- GET发送多个参数
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(Integer id,String name){
System.out.println("user save....id:"+id+" name:"+name);
return "{'info':springmvc}";
}
}
-
GET请求中文
Tomcat8.5以后的版本已经处理了中文乱码的问题。 -
POST发送参数
和GET一致,不用做任何修改
@RequestMapping("/delete")
@ResponseBody
public String delete(String name){
System.out.println("user delete....name:"+name);
return "{'info':springmvc}";
}
- POST请求中文乱码
需要在Web配置类中配置过滤器
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[]{"/"};
}
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
五种类型参数传递
前面我们已经能够使用GET或者POST发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有:
- 普通参数
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
接下来演示一下这五种参数是如何发送的。
普通参数:url地址传参,地址参数名和形参变量名相同,定义形参即可接收参数。
如果形参和地址参数名不一样,可以通过使用@RequestParam注解来解决问题。
比如当前端发送以下内容:localhost:8080/springmvc/user/save?uid=20&username=fyc
后端只需要添加注解@RequestParam(“uid”)和@RequestParam(“username”)即可,具体代码如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam(value = "uid",required = false) Integer id, @RequestParam(value = "username",required = false) String name){
System.out.println("user save....id:"+id+" name:"+name);
return "{'info':springmvc}";
}
}
注意!!!如果参数使用了@RequestParam注解,此参数默认是必传的,否则会报400错误。如果想实现不一定上传功能,则需要将注解中的required设置为false。
POJO数据类型:如果前端发送的参数比较多,那么后台接收参数的时候就比较复杂,这个时候可以考虑使用POJO数据类型。
POJO参数:请求参数名和形参对象属性名相同,定义POJO类型形参即可接收参数。
在domain包下,创建Account类
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
//....
//getter和setter方法略
}
此时,前端发送以下内容:localhost:8080/springmvc/user/save?id=20&name=fyc&money=1000
当然,也可以发送POST请求,如下图:
后端只需在方法中加入对应的参数public String save(Account account)
具体代码如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(Account account){
System.out.println("user save....account:"+account);
return "{'info':springmvc}";
}
}
注意!!! 请求参数的key的名称要和POJO中属性名一致,否则无法封装。
嵌套POJO类型参数
嵌套POJO类型参数:请求参数名和形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。
在domain包下创建User类和Address类,其中Address类是User类的属性。大致代码如下:
public class User {
private String name;
private String age;
private Address address;
//getter、setter和toString方法 略
}
public class Address {
private String province;
private String city;
//getter、setter和toString方法 略
}
前端发送POST请求,如下图:
后端只需要修改方法中的形参为对应的POJO即可:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(User user){
System.out.println("user save....account:"+user);
return "{'info':springmvc}";
}
}
数组类型参数
数组参数:请求参数名与形参对象属性名相同,且请求参数为多个,定义数组类型即可接收参数。
前端发送如下GET请求:localhost:8080/springmvc/user/save?likes=打篮球&likes=游泳&likes=慢跑
后端只需要修改形参返回值类型为数组,并且形参对象名与请求参数名一致即可。具体代码如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(String[] likes){
System.out.println("user save....likes:"+Arrays.toString(likes));
return "{'info':springmvc}";
}
}
集合类型参数
接收集合类型的参数的前后端代码与接收数组类型的参数非常相像。但是,当我们把形参对象由String[] likes
改成List<String> likes
时代码却会报错。
报错的原因是:SpringMVC将List看作是一个POJO来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
解决方案是:使用@RequestParam注解
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam List<String> likes){
System.out.println("user save....likes:"+ likes);
return "{'info':springmvc}";
}
}
对于简单数据类型,使用数组会比使用集合更简单一些。
JSON数据传输参数
现在比较流行的开发方式为异步调用,前后台以异步方式进行交换,传输的数据使用的是JSON。
对于JSON数据类型,常见的有三种类型:
- json普通数组:{“value1”,“value2”,“value3”,…}
- json对象:{“key1”:“value1”,“key2”:“value2”,“key3”:“value3”,…}
- json对象数组:[{“key1”:“value1”,“key2”:“value2”,…},{“key1”:“value1”,“key2”:“value2”,…}…]
对于上述常见3种JSON类型的数据,我们要学会如何进行前端发送数据,后端接收数据。接下来分别演示3种JSON数据类型的前后端发送接收步骤。
JSON普通数组
- 在pom.xml中添加jackson依赖
SpringMVC默认使用的是jackson来处理json的转换,所以要在pom.xml添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
- 开启SpringMVC注解支持
在SpringMVC的配置类中开启SpringMVC的注解支持,这里就包含了将JSON转化为对象的功能。
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc//开启SpringMVC注解支持
public class SpringMvcConfig {
}
- 参数前添加@RequestBody
之前如果想要传递集合类型参数,需要在形参前添加@RequestParam。而在JSON普通数组中,将使用@RequestBody来代替@RequestParam
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody List<String> likes){
System.out.println("user save....likes:"+ likes);
return "{'info':springmvc}";
}
}
- 启动运行程序
- 使用PostMan发送JSON数据
JSON对象数据
我们只需关注请求和数据如何发送以及后端数据如何接收。
前端发送请求:
后端接受数据:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody User user){
System.out.println("user save....likes:"+ user);
return "{'info':springmvc}";
}
}
JSON对象数组
前端发送请求:
后端接收数据:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestBody User[] users){
System.out.println("user save....likes:"+ Arrays.toString(users));
return "{'info':springmvc}";
}
}
日期类型参数传递
前面我们处理过简单数据类型,POJO数据类型,数组和集合数据类型以及JSON数据类型,接下来我们还得处理一种开发中比较常见的数据类型,日期数据类型。
日期类型比较特殊,因为对于日期的格式有N多种输入方式。针对不同日期格式,SpringMVC应该如何接收并处理日期类型数据,这是一个问题。
- 编写方法接收日期类型数据
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(Date date){
System.out.println("user save....Date:"+ date);
return "{'info':springmvc}";
}
}
- 使用PostMan发送请求
控制台打印结果为:user save....Date:Wed Jul 13 00:00:00 CST 2022
说明确实可以通过以上方法传递日期类型参数。但日期的格式多种多样,当我们改变的url中日期格式时控制台会报错。这说明只有yyyy/MM/dd
日期格式可以被后端正确识别为data对象。
要想解决因为日期格式问题而导致后端接收不到Date对象,需要使用@DateTimeFormat注解。
当我们修改后端为以下代码后:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
System.out.println("user save....Date:"+ date);
return "{'info':springmvc}";
}
}
前端发送如下请求:
发送请求后,控制台打印user save....Date:Wed Jul 13 00:00:00 CST 2022
,说明前端发送的日期类型参数已被后端正确识别并接收。
内部实现原理
在数据的传输过程中存在很多类型的转换,这些类型转换由SpringMVC完成。
在SpringMVC中提供了很多类型转换接口和实现类。其中有Converter接口以及它下面的实现类。
注意!!!:SpringMVC的配置类把@EnableWebMvc当作标配配置上去,不要省略。
响应
SpringMVC接收到请求和数据后,可以进行一些处理,这些处理是可以转发给Service层,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户。
比如:根据用户ID查询用户信息等等。
对于响应,,主要就包含两部分内容:
- 响应页面
- 响应数据(文本数据和JSON数据)
因为异步调用时目前常用的主流方式,所以我们需要更关注的就是如何返回JSON数据,对于其他只需了解即可。
环境搭建
在之前SpringMVC入门案例的基础上,在webapp下创建page.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
HELLO WORLD!
</body>
</html>
响应页面(了解即可)
前端发送请求http://localhost:8080/springmvc/user/save
后端代码如下:
@Controller
@RequestMapping("/user")
public class UserController {
//@ResponseBody
//注意
//1.此处不能添加@ResponseBody,如果加入该注解,会直接将page.html当作字符串返回到前端
//2.方法需要返回String
@RequestMapping("/save")
public String save(){
System.out.println("跳转页面");
return "../page.jsp";
}
}
前端发送请求后,页面显示:
响应JSON数据
- 响应POJO对象
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public User save(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("fyc");
user.setAge(15);
return user;
}
}
前端发送请求后,返回结果如下:
- 响应POJO集合对象
将方法的返回值类型改成List即可
学习总结
SpringMVC是一款基于java实现的MVC模型的轻量级Web层框架。
SpringMVC的目的:它的出现主要是用来代替Servlet,来实现对前端发送的请求和数据的接收,将数据处理后响应回前端。
Servlet存在的很明显的一个问题是:一个功能对应一个Servlet类。当项目比较大时,Web层下将存在大量的Servlet类。SpringMVC的出现可以很好的解决这个问题。SpringMVC将一个对象的所有功能都整合到一个类中,在减少Web层类的个数的同时,还能减少代码量。
SpringMVC要代替Servlet就必须要解决请求与响应问题。在使用SpringMVC解决问题前需要先配置好SpringMVC。大概的流程如下:
- 创建maven结构的web项目
- 在pom.xml中导入servlet和spring-webmvc的jar包
- 配置tomcat服务器
- 删除web.xml创建名为ServletContainersInitConfig的web配置类并完成其中配置
- 创建SpringMvcConfig配置类并完成配置
- 创建控制类,在控制类中完成请求和响应的代码编写
有几个细节需要注意下:
- web配置类中的方法可以完成4个配置:1.SpringMVC容器的创建 2.Spring容器的创建 3.请求映射路径的设置 4.解决post请求中文乱码的过滤器设置
- 在SpringMvcConfig配置类中把@EnableWebMvc作为标准配置,这个注解的作用之一是实现JSON数据类型与对象数据类型的类型转换。
- 在每个控制类上添加@Controller不能使用@Component,因为这是要添加到SpringMVC容器不是Spring。类上和方法上均可使用@RequestMapping指明路径。在方法上添加@ResponseBody表明方法返回值不是页面。
在后续中,一个项目会同时用到Spring和SpringMVC,它们都有自己的容器将类的对象作为Bean加载到自己的容器中。很明显,controller包下的类应该被放置在SpringMVC容器中。如何避免controller包下的类被Spring配置类扫描到这是一个问题。这里涉及到Bean加载控制。通常情况下,修改spring配置类中的包扫描为精确扫描即可。
在请求与响应中的请求部分:
请求的类型可以有GET,POST。
请求所携带的数据可以是
1.普通参数 2.POJO类型参数 3.嵌套POJO类型参数 4.数组类型参数 5.集合类型参数 6.JSON数据类型 7.时间数据类型 这常见的7种,其中JSON数据类型又分为3种:1.普通数组类型 2.对象类型 3.对象数组类型。
面对这么种类的请求,传统方法测试非常麻烦。所以使用专门的网页调试工具PostMan。它可以很方便的发送各种请求,携带各种类型数据。
- 面对前5种数据类型,修改控制类中方法的形参即可。需要注意请求参数名与形参对象的属性名需要一致。其中集合类型参数得在形参前添加@RequestBody 。
- 面对JSON数据类型,需要在形参前添加@RequestBody 。
- 面对时间数据类型,需要在形参前添加@DateTimeFormat(pattern = “yyyy-MM-dd”)
至此服务端可以接收到各种类型的数据,接下来可以处理接收到的数据并响应回前端。响应分为响应页面和响应JSON数据。响应页面需要将方法上的@ResponseBody注解删除。响应JSON数据需要修改方法的返回值类型。
至此我们便完成了,SpringMVC的入门学习。