SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
SpringMVC是处于Web层的框架,所以其主要作用就是用来接收前段发过来的请求和数据,然后经过处理之后将处理结果响应给前端,所以如何处理情趣和响应是SpringMVC中非常重要的一块内容。
REST是一种软件架构风格,可以降低开发的复杂性,提高系统的可伸缩性,后期的应用也是非常广泛。
对于SpringMVC的学习,最终要达成的目标:
- 掌握基于SpringMVC获取请求参数和响应JSON数据操作
- 熟练应用基于REST风格的请求路径设置与参数传递
- 能根据实际业务建立前后端开发通信协议,并进行实现
- 基于SSM整合技术开发任意业务模块功能
SpringMVC概述
学习SpringMVC我们先来回顾下现在Web程序是如何做的,我们现在的Web程序大都基于MVC三层架构来实现的。
- 如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极其不利
- 所以将后端服务器Servlet拆分成三层,分别是web、service和dao
web
层主要由servlet
来处理,负责页面请求和数据的收集以及响应结果给前端service
层主要负责业务逻辑的处理dao
层主要负责数据的增删改查操作
- 所以将后端服务器Servlet拆分成三层,分别是web、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格式进行返回
- SpringMVC主要负责的就是
- controller如何接收请求和数据
- 如何将请求和数据转发给业务层
- 如何将响应数据转换成JSON发挥到前端
- SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
- 优点
- 使用简单、开发快捷(相比较于Servlet)
- 灵活性强
- 优点
这里说的优点,我们通过下面的讲解与联系慢慢体会
SpringMVC入门案例
因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?
- 创建web工程(Maven结构)
- 设置tomcat服务器,加载web工程(tomcat插件)
- 导入坐标(Servlet)
- 定义处理请求的功能类(UserServlet)
- 设置请求映射(配置映射关系)
SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?
- 创建web工程(Maven结构)
- 设置tomcat服务器,加载web工程(tomcat插件)
- 导入坐标(SpringMVC+Servlet)
- 定义处理请求的功能类(UserController)
- 设置请求映射(配置映射关系)
- 将SpringMVC设定加载到Tomcat容器中
案例制作
-
步骤一:创建Maven项目
-
步骤二:导入所需坐标(SpringMVC+Servlet)
在pom.xml
中导入下面两个坐标<!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--springmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
步骤三:创建SpringMVC控制器类(等同于我们前面做的Servlet)
//定义Controller,使用@Controller定义Bean @Controller public class UserController { //设置当前访问路径,使用@RequestMapping @RequestMapping("/save") //设置当前对象的返回值类型 @ResponseBody public String save(){ System.out.println("user save ..."); return "{'module':'SpringMVC'}"; } }
-
步骤四:初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的Bean
//创建SpringMVC的配置文件,加载controller对应的bean @Configuration // @ComponentScan("com.blog.controller") public class SpringMvcConfig { }
-
步骤五:初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
//定义一个servlet容器的配置类,在里面加载Spring的配置,继承AbstractDispatcherServletInitializer并重写其方法 public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { //加载SpringMvc容器配置 protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(SpringMvcConfig.class); return context; } //设置哪些请求归SpringMvc处理 protected String[] getServletMappings() { //所有请求都交由SpringMVC处理 return new String[]{"/"}; } //加载Spring容器配置 protected WebApplicationContext createRootApplicationContext() { return null; } }
-
步骤六:访问http://localhost:8080/save
页面上成功出现{'info':'springmvc'},至此我们的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控制器类或方法定义上方 |
作用 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
入门案例小结
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
- SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
工作流程解析
这里将SpringMVC分为两个阶段来分析,分别是启动服务器初始化过程
和单次请求过程
启动服务器初始化过程
-
服务器启动,执行ServletContainerInitConfig类,初始化web容器
-
功能类似于web.xml
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(SpringMvcConfig.class); return context; } protected String[] getServletMappings() { return new String[]{"/"}; } protected WebApplicationContext createRootApplicationContext() { return null; } }
-
-
执行createServletApplicationContext方法,创建了WebApplicationContext对象
-
该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(SpringMvcConfig.class); return context; }
-
-
加载SpringMvcConfig配置类
@Configuration @ComponentScan("com.blog.controller") public class SpringMvcConfig { }
-
执行@ComponentScan加载对应的bean
- 扫描指定包及其子包下所有类上的注解,如Controller类上的
@Controller
注解
- 扫描指定包及其子包下所有类上的注解,如Controller类上的
-
加载UserController,每个@RequestMapping的名称对应一个具体的方法此时就建立了/save和save()方法的对应关系
@Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'module':'SpringMVC'}"; } }
-
执行getServletMappingsS方法,设定SpringMVC拦截请求的路径规则/代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
protected String[] getServletMappings() { return new String[]{"/"}; }
单次请求过程
- 发送请求
http://localhost:8080/save
- web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 上面的第5步已经将请求路径和方法建立了对应关系,通过
/save
就能找到对应的save()
方法
- 上面的第5步已经将请求路径和方法建立了对应关系,通过
- 执行
save()
- 检测到有
@ResponseBody
直接将save()
方法的返回值作为响应体返回给请求方
Bean加载控制
问题分析
入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig
的配置类,在之前学习Spring的时候也创建过一个配置类SpringConfig
。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?
我们先来回顾一下项目结构
com.blog
下有config
、controller
、service
、dao
这四个包
-
config
目录存入的是配置类,写过的配置类有:
- ServletContainersInitConfig
- SpringConfig
- SpringMvcConfig
- JdbcConfig
- MybatisConfig
-
controller
目录存放的是SpringMVC
的controller
类 -
service
目录存放的是service
接口和实现类 -
dao
目录存放的是dao/Mapper
接口
controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC
加载还是让Spring
加载呢?
-
SpringMVC
控制的bean
- 表现层bean,也就是
controller
包下的类
- 表现层bean,也就是
-
Spring
控制的bean
- 业务bean(
Service
) - 功能bean(
DataSource
,SqlSessionFactoryBean
,MapperScannerConfigurer
等)
- 业务bean(
分析清楚谁该管哪些bean以后,接下来要解决的问题是如何让Spring
和SpringMVC
分开加载各自的内容。
思路分析
对于上面的问题,解决方案也比较简单
- 加载Spring控制的bean的时候,
排除掉
SpringMVC控制的bean
那么具体该如何实现呢?
- 方式一:Spring加载的bean设定扫描范围
com.blog
,排除掉controller
包内的bean - 方式二:Spring加载的bean设定扫描范围为精确扫描,具体到
service
包,dao
包等 - 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中(
了解即可
)
环境准备
在入门案例的基础上追加一些类来完成环境准备
-
导入坐标
<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.46</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>
-
com.blog.config
下新建SpringConfig
类@Configuration @ComponentScan("com.blog") public class SpringConfig { }
-
创建一张数据表
create table tb_user( id int primary key auto_increment, name varchar(25), age int )S
-
新建
com.blog.service
,com.blog.dao
,com.blog.domain
包,并编写如下几个类- User
- UserDao
- UserService
- UserServiceImpl
public class User { private Integer id; private String name; private Integer age; public User() { } public User(Integer id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
-
编写App运行类
public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); System.out.println(context.getBean(UserController.class)); } }
设置bean加载控制
-
运行App运行类,如果Spring配置类扫描到了UserController类,则会正常输出,否则将报错
当前配置环境下,将正常输出com.blog.controller.UserController@8e0379d
-
解决方案一:修改Spring配置类,设定扫描范围为精准范围
@Configuration @ComponentScan({"com.blog.dao","com.blog.service"}) public class SpringConfig { }
再次运行App运行类,报错
NoSuchBeanDefinitionException
,说明Spring配置类没有扫描到UserController,目的达成 -
解决方案二:修改Spring配置类,设定扫描范围为com.blog,排除掉controller包中的bean
@Configuration @ComponentScan(value = "com.blog", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class )) public class SpringConfig { }
-
excludeFilters属性:设置扫描加载bean时,排除的过滤规则
-
type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
- ANNOTATION:按照注解排除
- ASSIGNABLE_TYPE:按照指定的类型过滤
- ASPECTJ:按照Aspectj表达式排除,基本上不会用
- REGEX:按照正则表达式排除
- CUSTOM:按照自定义规则排除
-
classes属性:设置排除的具体注解类,当前设置排除
@Controller
定义的bean
运行程序之前,我们还需要把SpringMvcConfig
配置类上的@ComponentScan
注解注释掉,否则不会报错,将正常输出
- 出现问题的原因是
- Spring配置类扫描的包是
com.blog
- SpringMVC的配置类,
SpringMvcConfig
上有一个@Configuration
注解,也会被Spring扫描到 - SpringMvcConfig上又有一个
@ComponentScan
,把controller类又给扫描进来了 - 所以如果不把
@ComponentScan
注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来,演示的效果就出不来 - 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。
- Spring配置类扫描的包是
运行程序,同样报错NoSuchBeanDefinitionException
,目的达成
最后一个问题,有了Spring的配置类,要想在tomcat服务器启动将其加载,我们需要修改ServletContainersInitConfig
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载SpringMvc配置
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringMvcConfig.class);
return context;
}
//设置哪些请求归SpringMvc处理
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载Spring容器配置
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringConfig.class);
return context;
}
}
对于上面的ServletContainerInitConfig
配置类,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类
我们改用继承它的子类AbstractAnnotationConfigDispatcherServletInitializer
,然后重写三个方法即可
public class ServletContainerInitConfig 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
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
相关属性 | excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes) |
PostMan工具的使用
PostMan使用
创建WorkSpace工作空间
发送请求
保存当前请求
请求与响应
前面我们已经完成了入门案例相关的知识学习,接来了我们就需要针对SpringMVC相关的知识点进行系统的学习。
SpringMVC是web层的框架,主要的作用是接收请求、接收数据、响应结果。
所以这部分是学习SpringMVC的重点内容,这里主要会讲解四部分内容:
- 请求映射路径
- 请求参数
- 日期类型参数传递
- 响应JSON数据
设置请求映射路径
环境准备
-
创建一个Maven项目
-
导入坐标
这里暂时只导servlet
和springmvc
的就行<!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--springmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
编写UserController和BookController
@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'}"; } } @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'}"; } }
创建
SpringMvcConfig
配置类@Configuration @ComponentScan("com.blog.controller") public class SpringMvcConfig { }
-
创建
ServletContainersInitConfig
类,初始化web容器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[]{"/"}; } }
-
直接启动Tomcat服务器,会报错
com.blog.controller.UserController#save()
to { /save}: There is already ‘bookController’ bean method
com.blog.controller.BookController#save() mapped.
从错误信息可以看出:
UserController
有一个save方法,访问路径为http://localhost/save
BookController
也有一个save方法,访问路径为http://localhost/save
- 当访问
http://localhost/save
的时候,到底是访问UserController
还是BookController
?
问题分析
团队多人开发,每人设置不同的请求路径,冲突问题该如何解决?
- 解决思路:为不同模块设置模块名作为请求路径前置
- 对于Book模块的save,将其访问路径设置
http://localhost/book/save
- 对于User模块的save,将其访问路径设置
http://localhost/user/save
- 对于Book模块的save,将其访问路径设置
这样在同一个模块中出现命名冲突的情况就比较少了。
设置映射路径
-
修改Controller
@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'}"; } }
问题是解决了,但是每个方法前面都需要进行修改,写起来比较麻烦而且还有很多重复代码,如果/user后期发生变化,所有的方法都需要改,耦合度太高。
优化配置路径
@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'}"; } }
@Controller @RequestMapping("/book") public class BookController { @RequestMapping("/save")s @ResponseBody public String save(){ System.out.println("book save .."); return "{'module':'book module'}"; } }
注意:
- 当类上和方法上都添加了
@RequestMapping
注解,前端发送请求的时候,要和两个注解的value值相加匹配才能访问到。 @RequestMapping
注解value属性前面加不加/
都可以
请求参数
请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数?
关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:
GET
POST
针对于不同的请求前端如何发送,后端如何接收?
环境准备
-
继续使用上面的环境即可,编写两个模型类
User
类和Address
类public class User { private String name; private int age; public User() { } public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
public class Address { private String province; private String city; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public Address() { } public Address(String province, String city) { this.province = province; this.city = city; } @Override public String toString() { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + '}'; } }
-
同时修改一下
UserController
类@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name){ System.out.println("普通参数传递name --> " + name); return "{'module':'commonParam'}"; } }
参数传递
-
GET发送单个参数
-
启动Tomcat服务器,发送请求与参数:http://localhost/commonParam?name=Jerry
-
接收参数
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name){ System.out.println("普通参数传递name --> " + name); return "{'module':'commonParam'}"; } }
注意get请求的key需与commonParam中的形参名一致
控制台输出普通参数传递name --> Jerry
-
-
GET发送多个参数
-
发送请求与参数:
localhost:8080/user/commonParam?name=Jerry&age=18
-
接收参数
@Controller @RequestMapping("/user") 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'}"; } }
控制台输出
普通参数传递name —> Jerry
普通参数传递age —> 18
-
-
接收参数
和GET一致,不用做任何修改@Controller @RequestMapping("/user") 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'}"; } }
控制台输出如下
普通参数传递name —> Tom
普通参数传递age —> 19 -
POST请求中文乱码
如果我们在发送post请求的时候,使用了中文,则会出现乱码 -
解决方案:配置过滤器
JAVA 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}; } }
重启Tomcat服务器,并发送post请求,使用中文,控制台输出如下
普通参数传递name —> 张三
普通参数传递age —> 19
-
五种类型参数传递
前面我们已经能够使用GET或POST来发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有
- 普通类型
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
下面我们就来挨个学习这五种类型参数如何发送,后台如何接收
普通类型
普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
-
发送请求与参数:
localhost:8080/user/commonParam?name=Helsing&age=1024
-
后台接收参数
@Controller @RequestMapping("/user") 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'}"; } }
-
控制台输出
普通参数传递name —> Helsing
普通参数传递age —> 1024
如果形参与地址参数名不一致该如何解决?例如地址参数名为username
,而形参变量名为name
,因为前端给的是username
,后台接收使用的是name
,两个名称对不上,会导致接收数据失败
-
解决方案:使用@RequestParam注解
-
发送请求与参数:
localhost:8080/user/commonParam?username=Helsing&age=1024
-
后台接收参数
@Controller @RequestMapping("/user") public class UserController { @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'}"; } }
-
POJO数据类型
简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用POJO数据类型。
- POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数
此时需要使用前面准备好的两个POJO类
public class User {
private String name;
private int age;
public class Address {
private String province;
private String city;
-
发送请求和参数:
localhost:8080/user/pojoParam?name=Helsing&age=1024
-
后台接收参数
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递 @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("POJO参数传递user --> " + user); return "{'module':'pojo param'}"; }
-
控制台输出如下
POJO参数传递user —> User
-
注意:
- POJO参数接收,前端GET和POST发送请求数据的方式不变。
- 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
嵌套POJO类型
-
环境准备
我们先将之前写的Address类,嵌套在User类中public class User { private String name; private int age; private Address address;
-
嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
-
发送请求和参数:
localhost:8080/user/pojoParam?name=Helsing&age=1024&address.province=Beijing&address.city=Beijing
-
后台接收参数
@RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("POJO参数传递user --> " + user); return "{'module':'pojo param'}"; }
-
控制台输出如下
POJO参数传递user —> User{name=’Helsing’, age=1024, address=Address{province=’Beijing’, city=’Beijing’}}
注意:请求参数key的名称要和POJO中属性的名称一致,否则无法封装
数组类型
举个简单的例子,如果前端需要获取用户的爱好,爱好绝大多数情况下都是多选,如何发送请求数据和接收数据呢?
-
数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
-
发送请求和参数:
localhost:8080/user/arrayParam?hobbies=sing&hobbies=jump&hobbies=rap&hobbies=basketball
-
后台接收参数
@RequestMapping("/arrayParam") @ResponseBody public String arrayParam(String[] hobbies){ System.out.println("数组参数传递user --> " + Arrays.toString(hobbies)); return "{'module':'array param'}"; }
-
控制台输出如下
数组参数传递user —> [sing, jump, rap, basketball]
集合类型
数组能接收多个值,那么集合是否也可以实现这个功能呢?
-
发送请求和参数:
localhost:8080/user/listParam?hobbies=sing&hobbies=jump&hobbies=rap&hobbies=basketball
-
后台接收参数
@RequestMapping("/listParam") @ResponseBody public String listParam(List hobbies) { System.out.println("集合参数传递user --> " + hobbies); return "{'module':'list param'}"; }
-
运行程序,报错
java.lang.IllegalArgumentException: Cannot generate variable name for non-typed Collection parameter type
- 错误原因:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
-
解决方案是:使用@RequestParam注解
@RequestMapping("/listParam") @ResponseBody public String listParam(@RequestParam List hobbies) { System.out.println("集合参数传递user --> " + hobbies); return "{'module':'list param'}"; }
-
控制台输出如下
集合参数传递user —> [sing, jump, rap, basketball]
知识点:
@RequestParam
名称 @RequestParam 类型 形参注解 位置 SpringMVC控制器方法形参定义前面 作用 绑定请求参数与处理器方法形参间的关系 相关参数 required:是否为必传参数 defaultValue:参数默认值 -
JSON数据传输参数
现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是JSON,所以前端如果发送的是JSON数据,后端该如何接收?
对于JSON数据类型,我们常见的有三种:
- json普通数组([“value1”,”value2”,”value3”,…])
- json对象({key1:value1,key2:value2,…})
- json对象数组([{key1:value1,…},{key2:value2,…}])
下面我们就来学习以上三种数据类型,前端如何发送,后端如何接收
JSON普通数组
-
步骤一:导入坐标
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
-
步骤二:开启SpringMVC注解支持 使用
@EnableWebMvc
,在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。@Configuration @ComponentScan("com.blog.controller") //开启json数据类型自动转换 @EnableWebMvc public class SpringMvcConfig { }
-
步骤四:后台接收参数,参数前添加
@RequestBody
使用@RequestBody
注解将外部传递的json数组数据映射到形参的集合对象中作为数据@RequestMapping("/jsonArrayParam") @ResponseBody public String jsonArrayParam(@RequestBody List<String> hobbies) { System.out.println("JSON数组参数传递hobbies --> " + hobbies); return "{'module':'json array param'}"; }
控制台输出如下
JSON数组参数传递hobbies —> [唱, 跳, Rap, 篮球]
JSON对象
-
请求和数据的发送:
{ "name":"菲茨罗伊", "age":"27", "address":{ "city":"萨尔沃", "province":"外域" } }
-
接收请求和参数
@RequestMapping("/jsonPojoParam") @ResponseBody public String jsonPojoParam(@RequestBody User user) { System.out.println("JSON对象参数传递user --> " + user); return "{'module':'json pojo param'}"; }
控制台输出如下
JSON对象参数传递user —> User{name=’菲茨罗伊’, age=27, address=Address{province=’外域’, city=’萨尔沃’}}
JSON对象数组
-
发送请求和数据
[ { "name":"菲茨罗伊", "age":"27", "address":{ "city":"萨尔沃", "province":"外域" } }, { "name":"地平线", "age":"136", "address":{ "city":"奥林匹斯", "province":"外域" } } ]
-
接收请求和参数
@RequestMapping("/jsonPojoListParam") @ResponseBody public String jsonPojoListParam(@RequestBody List<User> users) { System.out.println("JSON对象数组参数传递user --> " + users); return "{'module':'json pojo list param'}"; }
控制台输出如下
JSON对象数组参数传递user —> [User{name=’菲茨罗伊’, age=27, address=Address{province=’外域’, city=’萨尔沃’}}, User{name=’地平线’, age=136, address=Address{province=’外域’, city=’奥林匹斯’}}]
小结
SpringMVC接收JSON数据的实现步骤为:
- 导入jackson包
- 开启SpringMVC注解驱动,在配置类上添加
@EnableWebMvc
注解 - 使用PostMan发送JSON数据
- Controller方法的参数前添加
@RequestBody
注解
知识点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
接收请求参数
- 后期开发中,发送json格式数据为主,
日期类型参数传递
日期类型比较特殊,因为对于日期的格式有N多中输入方式,比如
- 2088-08-18
- 2088/08/18
- 08/18/2088
- …
针对这么多日期格式,SpringMVC该如何接收呢?下面我们来开始学习
-
步骤一:编写方法接收日期数据
@RequestMapping("/dateParam") @ResponseBody public String dateParam(Date date) { System.out.println("参数传递date --> " + date); return "{'module':'date param'}"; }
-
步骤二:启动Tomcat服务器
-
步骤三:使用PostMan发送请求:
localhost:8080/user/dateParam?date=2077/12/21
-
步骤四:查看控制台,输出如下
参数传递date —> Tue Dec 21 00:00:00 CST 2077
-
步骤五:更换日期格式
为了能更好的看到程序运行的结果,我们在方法中多添加一个日期参数@RequestMapping("/dateParam") @ResponseBody public String dateParam(Date date1,Date date2) { System.out.println("参数传递date1 --> " + date1); System.out.println("参数传递date2 --> " + date2); return "{'module':'date param'}"; }
使用PostMan发送请求,携带两个不同的日期格式,
localhost:8080/user/dateParam?date1=2077/12/21&date2=1997-02-13
发送请求和数据后,页面会报400,The request sent by the client was syntactically incorrect.
错误的原因是将1997-02-13
转换成日期类型的时候失败了,原因是SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd
,而我们现在传递的不符合其默认格式,SpringMVC就无法进行格式转换,所以报错。
解决方案也比较简单,需要使用@DateTimeFormat
注解@RequestMapping("/dateParam") @ResponseBody public String dateParam(Date date1,@DateTimeFormat(pattern = "yyyy-MM-dd") Date date2) { System.out.println("参数传递date1 --> " + date1); System.out.println("参数传递date2 --> " + date2); return "{'module':'date param'}"; }
重新发送请求与数据,控制台输出如下,问题解决
参数传递date1 —> Tue Dec 21 00:00:00 CST 2077
参数传递date2 —> Thu Feb 13 00:00:00 CST 1997 -
步骤六:携带具体时间的日期
接下来我们再来发送一个携带具体时间的日期,如localhost:8080/user/dateParam?date1=2077/12/21&date2=1997-02-13&date3=2022/09/09 16:34:07
,那么SpringMVC该怎么处理呢?
继续修改UserController类,添加第三个参数,同时使用@DateTimeFormat
来设置日期格式@RequestMapping("/dateParam") @ResponseBody public String dateParam(Date date1, @DateTimeFormat(pattern = "yyyy-MM-dd") Date date2, @DateTimeFormat(pattern ="yyyy/MM/dd HH:mm:ss") Date date3) { System.out.println("参数传递date1 --> " + date1); System.out.println("参数传递date2 --> " + date2); System.out.println("参数传递date3 --> " + date3); return "{'module':'date param'}"; }
控制台输出如下
参数传递date1 —> Tue Dec 21 00:00:00 CST 2077
参数传递date2 —> Thu Feb 13 00:00:00 CST 1997
参数传递date3 —> Fri Sep 09 16:34:07 CST 2022
知识点:@DateTimeFormat
名称 | @DateTimeFormat |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参前面 |
作用 | 设定日期时间型数据格式 |
相关属性 | pattern:指定日期时间格式字符串 |
内部实现原理
我们首先先来思考一个问题:
- 前端传递字符串,后端使用日期Date接收
- 前端传递JSON数据,后端使用对象接收
- 前端传递字符串,后端使用Integer接收
- 后台需要的数据类型有很多种
- 在数据的传递过程中存在很多类型的转换
问
:谁来做这个类型转换?
答
:SpringMVC
问
:SpringMVC是如何实现类型转换的?
答
:SpringMVC中提供了很多类型转换接口和实现类
在框架中,有一些类型转换接口,其中有:Converter接口
注意:Converter所属的包为org.springframework.core.convert.converter
/**
* S: the source type
* T: the target type
*/
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
//该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
T convert(S source);
}
到了源码页面我们按Ctrl+H可以来看看Converter接口的层次结构 这里给我们提供了很多对应Converter接口的实现类,用来实现不同数据类型之间的转换
HttpMessageConverter
接口
该接口是实现对象与JSON之间的转换工作
注意:需要在SpringMVC的配置类把@EnableWebMvc
当做标配配置上去,不要省略
SSM整合
代码
https://gitee.com/lucky_sungit/spring_study.git
流程
-
创建工程
-
pom
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </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.2.8</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>8.0.22</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
-
-
SSM整合
-
spring
-
SpringConfig
@Configuration @ComponentScan({"com.nwnu.sun.service", "com.nwnu.sun.dao"}) //@ComponentScan(value = "com.nwnu.sun", // excludeFilters = @ComponentScan.Filter( // type = FilterType.ANNOTATION, // classes = Controller.class // ) //) @Import({MybatisConfig.class, DatasourceConfig.class}) public class SpringConfig { }
-
-
Mybatis
-
MybatisConfig
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setTypeAliasesPackage("com.nwnu.sun.dto"); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.nwnu.sun.dao"); return mapperScannerConfigurer; } }
-
JdbcConfig
@PropertySource("classpath:jdbc.properties") public class DatasourceConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
-
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456
-
-
springmvc
-
ServletConfig
public class ServletcontainerInit extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class};//加载spring } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvc.class};//加载springmvc容器 } @Override protected String[] getServletMappings() { return new String[]{"/"};//设置映射 } //处理乱码问题 protected Filter[] getServletFilers() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
-
SpringMvcConfig
@Configuration @ComponentScan({"com.nwnu.sun.controller"}) @EnableWebMvc public class SpringMvc implements WebMvcConfigurer { @Autowired private MyInterpetor myInterpetor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterpetor).addPathPatterns("/books/action/all"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/page/**").addResourceLocations("/page"); } }
-
-
-
功能模块
-
表与实体
@AllArgsConstructor @NoArgsConstructor @Data public class Book { private Integer id; private String type; private String name; private String description; }
-
dao(接口+自动代理)
@Mapper public interface BookDao { @Select("select * from book where id = #{id}") public Book queryById(Integer id); @Insert("insert into book (type,name,description) values(#{type},#{name},#{description})") public Boolean insertBook(Book book); @Update("update set book type=#{type},#{name},#{description}") public Boolean updateBook(Book book); @Delete("delete from book where id = #{id}") public Boolean deleteById(Integer id); @Select("select * from book") public List<Book> queryAll(); }
-
service(接口+实现类)
@Transactional public interface BookService { public Book queryById(Integer id); public Boolean insertBook(Book book); public Boolean updateBook(Book book); public Boolean deleteById(Integer id); public List<Book> queryAll(); }
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public Book queryById(Integer id) { return bookDao.queryById(id); } @Override public Boolean insertBook(Book book) { return bookDao.insertBook(book); } @Override public Boolean updateBook(Book book) { return bookDao.updateBook(book); } @Override public Boolean deleteById(Integer id) { return bookDao.deleteById(id); } @Override public List<Book> queryAll() { return bookDao.queryAll(); } }
-
业务接口测试(整合Junit)
//这是在MVC配置类中存在 @EnableWebMvc 该注解导致报错,在使用Junit测试时需去掉该注解即可。 @RunWith(SpringRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookControllerTest { @Autowired private BookService bookService; @Test public void quertAll() { List<Book> books = bookService.queryAll(); System.out.println(books.toString()); } @Test public void insert(){ Book book = new Book(); book.setName("化学书"); book.setType("化学"); book.setDescription("这是一本化学书"); Boolean aBoolean = bookService.insertBook(book); System.out.println(aBoolean); } @Test public void delete(){ Boolean aBoolean = bookService.deleteById(2); System.out.println(aBoolean); } }
-
-
controller
-
表现层接口测试(postman)
@RestController @RequestMapping("/books") public class BookController { @Autowired private BookService bookService; @GetMapping("/action/{id}") public Result queryById(@PathVariable Integer id) { if (id != 2) { throw new BusinessException(Resultcode.BUSEXEC.getCode(), Resultcode.BUSEXEC.getMessage()); } Book book = bookService.queryById(id); return new Result(Resultcode.SUCCESS.getCode(), book, Resultcode.SUCCESS.getMessage()); } @PutMapping("/action") public Result insertBook(@RequestBody Book book) { Boolean aBoolean = bookService.insertBook(book); return new Result(Resultcode.SUCCESS.getCode(), aBoolean, Resultcode.SUCCESS.getMessage()); } @Delete("/action/{id}") public Result deleteByid(@PathVariable Integer id) { Boolean aBoolean = bookService.deleteById(id); return new Result(Resultcode.SUCCESS.getCode(), aBoolean, Resultcode.SUCCESS.getMessage()); } @GetMapping("/action/all") public Result quertAll() { List<Book> books = bookService.queryAll(); return new Result(Resultcode.SUCCESS.getCode(), books, Resultcode.SUCCESS.getMessage()); } }
-
前后端数据
@Data @AllArgsConstructor @NoArgsConstructor public class Result { private Long code; private Object object; private String message; public static Result success(Object o) { return new Result(Resultcode.SUCCESS.getCode(), o, Resultcode.SUCCESS.getMessage()); } public static Result failed(Object o){ return new Result(Resultcode.ERROR.getCode(), o,Resultcode.ERROR.getMessage()); } }
public enum Resultcode { SUCCESS(10010, "成功"), ERROR(10011, "失败"), SYSEXCE(10012, "系统异常"), BUSEXEC(10013, "业务异常"); private final long code; private final String message; Resultcode(long code, String message) { this.code = code; this.message = message; } public long getCode() { return this.code; } public String getMessage() { return this.message; } }
-
异常处理器
-
出现异常现象的常见位置与常见诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作、导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放)
-
思考
1.各个层级出现异常,异常书写代码写在哪一层?
2.表现层处理异常,每个方法中 单独书写,代码书写量巨大且意义不强,如何解决?AOP思想
-
异常处理器
-
集中的、统一的处理项目中出现的异常
@RestControllerAdvice public class MyExceptionAdvice { @ExceptionHandler(Exception.class) //定义当前处理哪一种异常 public Result doException(Exception e) { System.out.println("异常哪里跑"); return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage()); } }
-
注解@RestControllerAdvice
-
类型: 类注解
-
位置:Rest风格开发的控制器增强类定义上方
-
作用:为Rest风格开发的控制器类做增强
-
范例
@RestControllerAdvice public class ProjectExceptionAdvice{ }
-
说明:次注解自带@ResponseBody注解与@Component注解,具备对应的功能
-
-
注解 @ExceptionHanadler
-
类型:方法注解
-
位置:专用于异常处理的控制器方法上方
-
作用:设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行
-
范例:
@ExceptionHandler(Exception.class) public Result doException(Exception e) { System.out.println("异常哪里跑"); return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage()); }
-
说明:此类方法可以更具处理的异常不同,制作多个方法分别处理对应的异常
-
-
异常初期器处理效果对比
-
项目异常处理方案
-
用户异常请求
-
项目运行中无法度量的异常
-
项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期到的异常
- 业务异常(BusinessException)
-
项目异常处理方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
- 业务异常(BusinessException)
-
代码编写
-
业务异常
package com.nwnu.sun.exception; public class BusinessException extends RuntimeException { private long code; //定义异常编码 public BusinessException(long code, String message) { super(message); this.code = code; } public BusinessException(long code, String message, Throwable cause) { super(message, cause); this.code = code; } public long getCode() { return code; } public String getMessage() { return super.getMessage(); } }
-
系统异常
package com.nwnu.sun.exception; public class SystemException extends RuntimeException { private long code; public SystemException(long code, String message) { super(message); this.code = code; } public SystemException(long code, String message, Throwable cause) { super(message, cause); this.code = code; } public long getCode() { return code; } public String getMessage() { return super.getMessage(); } }
-
异常处理器
package com.nwnu.sun.exception; import com.nwnu.sun.comm.Result; import com.nwnu.sun.comm.Resultcode; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @author nwnu * @date 2022/11/23 10:21 * @description 全局控制器异常处理器 */ @RestControllerAdvice public class MyExceptionAdvice { @ExceptionHandler(Exception.class) public Result doException(Exception e) { System.out.println("异常哪里跑"); //返回通用对象 return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage()); } @ExceptionHandler(SystemException.class) public Result doSysException(SystemException se) { System.out.println("出现系统异常"); System.out.println(se.getCode() + se.getMessage()); return new Result(se.getCode(), null, se.getMessage()); } @ExceptionHandler(BusinessException.class) public Result doBusinessException(BusinessException bs) { System.out.println("出现业务异常"); return new Result(bs.getCode(), null, bs.getMessage()); } }
-
-
异常处理对比
资源处理器
servlet容器初始化拦截了 ‘/’ 路径,导致浏览器无法获取html资源
增加资源处理类
@Configuration
public class SpringmvcSupport implements WebMvcConfigurer {
//静态资源展示
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//swagger 和 knife4j
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/page/**").addResourceLocations("/page");
}
}
拦截器
-
拦截器简介
-
概念
是一种动态拦截方法调用的机制,在SpringMvc中动态拦截控制器方法的执行
-
作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
-
拦截器与过滤器区别
-
归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
-
拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
@Override protected String[] getServletMappings() { return new String[]{"/"}; }
在过滤器过滤后的请求,才能进行拦截器
-
入门案例
在上述整合案例中添加拦截器相关代码
-
自定义拦截器 MyInterpetor,注意添加注解 @Component相关注解让sprng容器管理
@Component public class MyInterpetor implements HandlerInterceptor { //原始操作之前运行的代码 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("prehandle"); return true; } //原始操作之后 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("posthandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("after"); } }
-
定义配置类,继承 WebmMvcConfigurationSupport ,实现addInterceptor 方法
注意添加@Configuration注解,并检查springmvc 的@ComponentScan 是否扫描到该配置
@Configuration public class SpringmvcSupport extends WebMvcConfigurationSupport { @Autowired private MyInterpetor myInterpetor; //拦截器 @Override protected void addInterceptors(InterceptorRegistry registry) { //设置拦截路径,路径可以通过可变参数设置多个 registry.addInterceptor(myInterpetor).addPathPatterns("/books/action/all"); } }
-
postman 请求测试
检查日志是否打印
简化配置
使用标准接口webMvcConfigurer简化开发(注意:侵入式较强)
@Configuration
@ComponentScan({"com.nwnu.sun.controller"})
@EnableWebMvc
public class SpringMvc implements WebMvcConfigurer {
@Autowired
private MyInterpetor myInterpetor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterpetor).addPathPatterns("/books/*");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/page/**").addResourceLocations("/page");
}
}
执行流程
参数
-
前置处理
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("prehandle"); return true; }
-
参数
- request: 请求对象
- response: 响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
-
返回值
- 返回值为false,被拦截的处理器将不执行
-
完成后处理
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("after"); }
-
参数
- ex:如果处理执行过程中出现异常对象,可以针对异常情况进行单独处理
多拦截器执行顺序
- preHandle:与配置顺序相同,必定运行
- postHandle:与配置顺序相反,可能不运行
- afterCompletion:与配置顺序相反,可能不运行
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~