SpringMVC学习记录-Spring基础入门!
前言
SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。
SpringMVC简概述:SpringMVC技术与Servlet技术功能等同,均属于web层开发技术
SpringMVC简介
SpringMVC概述:
- 回顾:Web程序的工作流程:
Web程序通过游览器访问页面;
前端页面使用异步提交的方式发送请求到后端服务器;
后端服务器采用表现层、业务层、数据层的三层架构的形式进行开发,页面发送的请求由表现层接收;
获取用户的请求参数后,将参数传递到业务层;
再由业务层访问数据层;
得到用户需要访问的数据后,将数据返回给表现层;
表现层拿到数据后将数据转化成Json格式;
随后发送给前端页面;
前端页面接收到数据后,解析数据并组织(渲染)成用户游览的最终页面信息;
最终发送回给游览器。
学习过程中,
数据层我们一开始用的是Jdbc,后来用了mybatis代替。
表现层采用的则是Servlet,现在来用Springmvc技术来代替。
-
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架,
也是一种表现层框架技术,用于进行表现层功能开发。
-
Spring的优点:
- 使用简单,开发便捷(相比于Servlet)
- 灵活性强
SpringMVC入门案例
入门案例
-
使用SpringMVC技术需要先导入SpringMVC坐标与Servlet坐标
<dependency> <groutId>javax.servlet</groutId> <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>
-
创建SpringMVC控制器类(等同于Servlet功能)
@Controller public class UserController{ @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'info':'springmvc'}"; } }
-
初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean
@Configuration @ComponentScan("com.catalpa.controller") public class SpringMvcConfig{ }
-
初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
public class ServletContainersInitConfig extends AbstractDispatcherServlertInitializer{ protected WebApplicationContext createServletApplicationContext(){ AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings(){ return new String[]{"/"}; } protected WebApplicationContext createRootApplicationContext(){ return null; } }
注解解释
-
@Controller
名称:@Controller
类型:类注解
位置:SpringMVC控制器类定义上方
作用:设定SpringMVC的核心控制器bean
举例:
@Controller public class UserController{ }
-
@RequestMapping
名称:@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问的路径
举例:
@RequestMapping("/save") public void save(){ System.out.println("ueser save ..."); }
相关属性:
- value(默认):请求访问路径
-
@ResponseBody
名称:@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法的响应内容为当前的返回值,直接映射,无需解析
举例:
@RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'info':'springmvc'}"; }
案例开发总结
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
- 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(通过注解@RequestMapping)与返回json数据(@ResponseBody)
-
AbstractDispatcherServletInitializer
AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化web3.0容器的抽象类
AbstractDispatcherServletInitializer提供了三个接口方法供用户实现
-
createServletApplicationContext()方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个Web容器范围。
protected WebApplicationContext createServletApplicationContext(){ AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMvcConfig.class); return ctx; }
-
getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/,表示拦截所有请求,任意请求都将转入到SpringMVC进行处理。
protected String[] getServletMappings(){ return new String[]{"/"}; }
-
createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方法同createServletApplicationContext()
protected WebApplicationContext createRootApplicationContext(){ return null; }
-
入门案例工作流程
分析:分为两个阶段
- 启动服务器初始化过程
- 服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 执行createServletApplicationContext方法,创建了WebApplicationContext对象(该对象其实是在ServletContext中的,被包含)
- 加载SpringMvcConfig
- 执行@ComponentScan加载对应的bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法(载入进去)
- 执行gerServletMappings方法,定义所有的请求都通过SpringMVC
- 单次请求过程
- 发送请求localhost/save
- web容器发现所有请求都结果SpringMVC,将请求交给SpringMVC处理
- 解析所有请求路径,如/save
- 由/save匹配执行对应的方法save()
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
(可根据如下的图进行理解)
bean加载控制
当有一些bean我们不需要被载入时应该怎么办?
当有两个Config配置类,他们都对应配置着不同bean,扫描不同bean,这个时候我们需要如何划分呢?
一般情况下,SpringMVC和Spring控制的bean是不同的
- SpringMVC相关bean:表现层bean
- Spring控制的bean: 业务bean(Service)、功能bean(DataSource等)
SpringMVC相关bean加载控制
SpringMVC加载的bean对应的包均在com.catalpa.controller包内
Spring相关bean加载控制
方式一:Spring加载的bean设定扫描范围为com.catalpa,排除掉controller包内的bean
方式二:Spring加载的bean设定扫描范围为精确范围,例如service包、dao包等
方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中
//方式一:
@Configuration
@ComponentScan(value="com.catalpa",
excludeFilters = @ComponentScan.Filter(
type = FilterType.AnnOTATION,
classes = Controller.class
)
)
public class SpringConfig{
}
(type 表示根据什么进行排除,此处选择的ANNOTATION表示的是根据注解排除;
classes则是写注解的类型。)
以下这图是对@ComponentScan进行解释
这里我们还需要来看一下SpringMvcConfig的配置类:
//@Configuration
@ComponentScan("com.catalpa.controller")
public class SpringMvcConfig{
}
注意: 此处的@Configuration注解被注释掉是有原因的。 SpringMvcconfig由前面已知是对Controller的bean控制。
此处如果带上@Configuration注解的话,当SpringConfig在配置扫描的时候,会把这个配置类也扫描进行,那么即使原先的@Controller注解是成功被排除的,在此刻也会被重新配置进去,使得前面的排除失效。
故这里不带@Configuration注解,不让该配置类被扫描到。
举例看一下测试类:
public class Appp{
public static void main(String[] args){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(ctx.getBean(UserController.class));
}
}
上面我们已经将第一种bean加载控制讲清楚了,下面我们来讲一下第二种,第二种就相对比较简单了。直接看代码就能理解了。
@Configuration
@ComponentScan({"com.catalpa.service","com.catalpa.dao"})
public class SpringConfig{
}
(第三种此处就不展开讲了。)
SpringMVC和Spring的配置载入
两者的载入是在AbstractDispatcherServletInitializer下实现的。
public class ServletContainersInitConfig extends AbstractDispatcherServlertInitializer{
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;
}
}
注意:两者的配置载入的方式都是一样的,都是通过AnnotationConfigWebApplicationContext,只有register处写入的配置类不同。(SpringMvcConfig.class 与 SpringConfig.class )
可以简化成如下:(但这样子做不方便查看是进行了什么操作。)
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
protected Class<?>[] getServletConfigClasses(){
return new Class[]{SpringMvcConfig.class};
}
protected Class<?>[] getRootConfigClasses(){
return new Class[]{SpringConfig.class};
}
protected String[] getServletMappings(){
return new String[]{"/"};
}
}
请求与响应
请求映射路径
使用的是@RequestMapping注解,不止可以在方法上进行注解,也可以在类注解上。
在类注解上做一个前缀路径,方便不同模块下的相同名字的注解路径进行成功映射。
请求参数
发送携带参数Get请求
利用Postman发送Get请求,并进行接收
发送携带参数Post请求
利用Postman发送Post请求,并进行接收
SpringMVC解决Post请求中文乱码问题
具体代码中实现如下:
此时用Post发送请求时,就可以发送中文了。
请求参数类型
参数类型大致分为如下的五种
(补充知识: POJO Plain Ordinary Java Object 简单java对象,内在含义指的是那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的java对象)
- 普通参数
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
传参数过程中我们常用到@RequestParam注解,它能给我们带来很多的便利。
名称:@RequestParam
类型:形参注解
位置:SpringMVC控制器方法形参定义前面
作用:绑定请求参数与处理器方法形参间的关系。
@RequestParam接收的参数是来自HTTP请求体或请求url的QueryString中,该注解用来处理Content-Type为application/x-www-form-urlencoded编码的内容,同时也可以用来处理POST等请求。
范例:
@RequestMapping("/commparamDifferentName")
@ResponseBody
public String commmonParamDifferentName(@RequestParam("name")String userName,int age){
System.out.println("dddd");
return "{'module':'common param different name'}";
}
注解的参数:
-
“name” :页面传来的地址参数名。
-
required:是否为必传参数
-
defaultValue:参数默认值
具体对不同请求参数类型,进行如下的分析介绍:
-
普通参数
分为两种,当地址参数名与形参变量名相同与不同。
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
当没有加@RequestParam进行集合类型参数传递的时候 ,由于List集合也是属于一个引用对象,所以和其他引用对象一样,请求参数传递进来的时候,自动装入了引用对象的属性中。比如list的length属性等。
此处只有加了@RequestParam注解后才能和我们预期的一样,装入到list集合中的数据去,作为集合的数据出现。
实战实例:
@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 "{'moudle':'common param'}";
}
//普通参数 : 请求参数名(地址参数名) 与形参名不同
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName, int age){
System.out.println("普通参数传递 userName ==> " + userName);
System.out.println("普通参数传递 age ==>"+age);
return "{'moudle':'common param different name'}";
}
//POJO参数
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+ user);
return "{'module':'pojo param'}";
}
//嵌套POJO参数
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("pojo嵌套pojo参数传递 user ==> "+ user);
return "{'module':'pojo contain pojo param'}";
}
//数组参数
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==>"+Arrays.toString(likes));
return "{'module':'array param'}";
}
//集合参数
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==>" + likes);
return "{'module':'list param'}";
}
}
传递JSON数据
-
json数组
-
json对象(POJO)
-
json数组(POJO)
接收请求中的json数据,主要分为如下四步
-
添加json数据转换相关坐标
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
-
设置发送json数据(通过postman软件进行模拟发送,在请求body中添加发送json数据)
-
开启自动转换json数据的支持(在SpringMvcConfig配置类中)
@Configuration @ComponentScan("com.catalpa.controller") @EnableWebMvc public class SpringMvcConfig{ }
@EnableWebMvc注解功能强大,该注解整合了很多功能,此处仅使用其中一部分功能,即能开启json数据进行自动类型转换(还能开启另外的功能,此处不展开讲)
-
设置接收json数据
@RequestMapping("/listParamForJson") @ResponseBody public String listParamForJson(@RequestBody List<String> likes){ System.out.println("list common(json)参数传递 list ==>" + likes); return "{'module':'list common for json param'}"; }
此处采用的是@RequestBody注解,来解决Spring/SpringBoot @RequestParam注解无法直接读取application/json格式数据的问题。
实战举例:
//集合参数:json格式
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==>"+likes);
return "{'module':'list common for json param'}";
}
//POJO参数:json格式
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==>"+user);
return "{'module':' pojo for json param'}";
}
//集合(POJO)参数:json格式
@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'}";
}
以上过程中用到的注解,介绍如下
-
@EnableWebMvc
- 名称:@EnableWebMvc
- 类型:配置类注解
- 位置:SpringMvc配置类定义上方
- 作用:开启SpringMVC多项辅助功能
-
@RequestBody
- 名称:@RequestBody
- 类型:形参注解
- 位置:SpringMVC控制器方法形参定义前面
- 作用:将请求中请求体所包含的数据传递给请求参数,此注解一个处理方法只能使用一次。
- 该注解一般处理的都是请求体中的数据。requestBody
- 能补足@RequestParam注解不好处理的json数据
- 一般用于处理非
Content-Type: application/x-www-form-urlencoded
编码格式的数据,比如application/json
、application/xml
等类型的数据。
@RequestParam与@RequestBody区别
- 区别
- @RequestParam用于接收url地址传参,表单传参
application/x-www-form-urlencoded
- @RequestBody用于接收json数据
application/json
- @RequestParam用于接收url地址传参,表单传参
- 应用
- 后期开发中,发送json数据格式为主,@RequestBody应用较多
- 如果发送非json格式数据,选用@RequestParam接收请求参数
日期类型参数传递
日期类型数据基于系统不同格式也有多种
- 2022-08-18
- 2022/08/18
- 08/18/2022
接收形参时,根据不同的日期格式设置不同的接收方式。使用@DateTimeFormat注解,在该注解的参数pattern中写明我们的日期格式。
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date date2){
System.out.println("参数传递 date ==>" +date);
System.out.println("参数传递 date(yyyy-MM-dd) ==>" +date1);
System.out.println("参数传递 date(yyyy/MM/dd HH:mm:ss) ==>" +date2);
return "{'module':'data param'}";
}
输出如下:
以上所用注解如下所示:
- @DateTimeFormat
- 名称:@DateTimeFormat
- 类型:形参注解
- 位置:SpringMVC控制器方法形参前面
- 作用:设定日期时间型数据格式
- 属性:pattern:日期时间格式字符串,如
"yyyy-MM-dd"
类型转换器(Converter)
日期格式的转换,内部利用的是类型转换器,根据Converter接口来实现的,该接口也实现了很多的类型转换问题,这个需要了解一下,后面学习中还会遇到这个接口。
响应json数据
响应类型:
-
响应页面
@RequestMapping("/toPage") public String toPage(){ return "page.jsp"; }
-
响应数据
-
文本数据
@RequestMapping("/toText") @ResponseBody public String toText(){ return "response text"; }
这里返回到页面的,其实是字符串,而并非json!!!(前面所说的只是模拟json,莫慌)
-
json数据
将POJO对象转为json对象
@RequestMapping("/toJsonPOJO") @ResponseBody public User toJsonPOJO(){ System.out.println("返回json对象数据"); User user = new User(); user.setName("赵云"); user.setAge(18); return user; }
将POJO集合对象转为json数组
@RequestMapping("/toJsonList") @ResponseBody public List<User> toJsonList(){ User user1 = new User(); user1.setName("赵云"); user1.setAge(41); User user2 = new User(); user2.setName("master 赵云"); user2.setAge(49); List<User> userList = new ArrayList<User>(); userList.add(user1); userList.add(user2); return userList; }
-
对前面所说的@ResponseBody做一个重审,说明其具体的功能
- @ResponseBody
名称:@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器返回值作为响应体
比如:如果是String的话,直接作为响应体;如果是对象则转为可识别的数据,再响应出去;对象转json;集合转json等。
举例:
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("save ...");
return "{'info':'springmvc'}";
}
类型转换器(HttpMessageConverter)
这一个注解所利用的接口是HttpMessageConverter接口,与Converter接口做的功能是一样的,只不过对应转换的东西不一样,都是用来做类型转换的。(如 数组(POJO)转json,json转数组(POJO))
REST风格
REST简介
-
概念
REST(Representational State Transfer),表现形式状态转换,改变获取资源的形式。
- 优点:
- 隐藏资源你的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
-
行为动作
按照REST风格访问资源时,使用行为动作区分 对资源进行了何种操作?
注意事项:上述行为是约定方法,约定不是规范,可以打破不执行,所以称为REST风格,而不是REST规范。
描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users,books,accounts…
-
RESTful
根据REST风格对资源进行访问,称为RESTful
REST入门案例
-
设定http请求动作(动词)
//添加用户信息 @RequestMapping(value = "/users", method = RequestMethod.POST) @ResponseBody public String save(@RequestBody User user){ System.out.println("user save..." + user); return "{'module':'user save'}"; } //修改用户信息 @RequestMapping(value = "/users", method = RequestMethod.PUT) @ResponseBody public String update(@RequestBody User user){ System.our.println("user update..." + user); return "{'module':'user update'}"; }
-
设定请求参数(路径变量)
//删除指定用户 @RequestMapping(value = "/users/{id}",method=RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id){ System.out.println("user delete..." + id); return "{'module':'user delete'}"; }
以上主要使用了两个注解@RequestMapping和@PathVariable
-
@RequestMapping
-
名称:@RequestMapping
-
类型:方法注解
-
位置:SpringMVC控制器方法定义上方
-
作用:设置当前控制器方法请求访问路径
-
举例:
@RequestMapping(value = "/users",method = RequestMethod.POST) @ResponseBody public String save(@RequestBody User user){ System.out.println("user save ..." + user); return "{'module':'user save'}"; }
-
属性:
- value(默认有):请求访问路径
- method:http请求动作,标准动作(GET/POST/PUT/DELETE)
-
-
@PathVariable
-
名称:@PathVariable
-
类型:形参注解
-
位置:SpringMVC控制器方法形参定义前面
-
作用:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
-
范例:
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id){ System.out.println("user delete..." + id); return "{'module':'user delete'}"; }
-
目前遇到的注解在方法形参中的注解,有@RequestBody,@RequestParam,@PathVariable三种,他们的作用也相近,都是用来辅助接收数据到形参。下面来做一个区别。
- 区别
- @RequestParam用于接收url地址传参或表单传参
- @RequestBody用于接收json数据
- @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
- 应用
- 后期开发中,发送请求参数超过1个,以json格式为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
- 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量(通常用于传递id值)
RESTful快速开发
简化代码编写
-
value有统一的路径,可以提前到类前进行注解@RequestMapping(“/books”)
(常是在公共请求路径后面加s,如books,users)
-
每个方法都有@ResponseBody,也可以将其提到类前
- 用**@RestController**简化合并@Controller和@ResponseBody两个注解
- 用@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等标准请求动作映射的注解,来简化method参数
基于RESTful页面数据交互案例
SSM整合
基础的整合大致流程如下:
SSM整合的过程具体又分为Spring整合Mybatis,和Spring整合SpringMVC
Spring整合Mybatis
- 大致流程
-
配置
- SpringConfig
- JDBCConfig、jdbc.properties
- MybatisConfig
-
模型
- Book
-
数据层标准开发
- BookDao
-
业务层标准开发
- BookService
- BookServiceImpl
-
测试接口
- BookServiceTest
- 事务处理
Spring整合SpringMVC
(包括基于RESTful标准控制器开发)
- web配置类
- SpringMvc配置类
- RESTful标准控制器开发
代码实践:
具体看gitee仓库
https://gitee.com/CatalpaHao/ssmtest
表现层与前端数据传输数据协议实现
——————表现层数据封装
表现层与前端数据进行传输的过程中,常遇到数据的规定不统一,读取或存取数据的方式不同,容易造成处理时的繁杂等情况。
后续提出统一数据返回结果类,双方按照一定的规范来处理,对表现层的数据进行封装。
-
设置统一数据返回结果类
public class Result{ private Object data; //存放返回的数据(如查询数据) private Integer code; //编码,表示该段数据的执行情况 private String msg; //消息message,情况具体说明(比如查询失败信息) //提供构造器和setter、getter }
(Result类中的字段并不是固定的,可以根据需求自行增减,提供若干个构造方法,方便操作)
-
设置统一数据返回结果编码(code)
public class Code{ public static final Integer SAVE_OK = 20011; public static final Integer DELETE_OK = 20021; public static final Integer UPDATE_OK = 20031; public static final Integer GET_OK = 20041; public static final Integer SAVE_ERR = 20010; public static final Integer DELETE_ERR = 20020; public static final Integer UPDATE_ERR = 20030; public static final Integer GET_ERR = 20040; }
(Code类的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK)
-
根据情况设定合理的Result
@RequestMapping("/books") public class BookController{ @Autowired private BookService bookService; @GetMapping("/{id}") public Result getById(@PathVariable Integer id){ Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK :Code.GET_ERR; String msg = book != null ? "" : "数据查询失败,请重试!"; return new Result(code,book,msg); //一般习惯会先写code。 } }
总的来说,表现层借助Result类和定义好的Code,配合业务将数据整理成统一格式,进行封装,反馈给前端页面,再由前端页面进行解析,将数据渲染到页面上。
异常处理器
程序在开发过程中不可避免的会遇到异常形象
出行异常形象的常见位置与常见诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据手机、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连连接长期未被释放等)
– 异常要进行分类处理,根据不同种类不同处理;所有的异常均抛出到表现层进行处理;异常要用AOP思想来处理。(----SpringMvc提供了快捷方式——异常处理器)
异常处理器
-
集中的、统一的处理项目中出现的异常
// REST风格下用@RestControllerAdvice ,还有另外类似功能的注解@ControllerAdvice @RestControllerAdvice public class ProjectExceptionAdvice{ @ExceptionHandler(Exception.class) public Result doException(Exception ex){ return new Result(666,null,"异常报错了,和你说一下"); } }
以上所用注解
-
@RestControllerAdvice
-
名称:@RestControler
-
类型:类注解
-
位置:Rest风格开发的控制器增强类定义上方
-
作用:为Rest风格开发的控制器类做增强,声明这个类是做异常处理的。
-
范例
@RestControllerAdvice public class ProjectExceptionAdvice{ }
-
说明:
此注解自带@ResponseBody注解与@Component注解,具备对应的功能
-
-
@ExceptionHandler
-
名称:@ExceptionHandler
-
类型:方法注解
-
位置:专用于异常处理的控制器方法上方
-
作用:设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行
-
范例:
@RestControllerAdvice public class ProjectExceptionAdvice{ @ExceptionHandler(Exception.class) public Result doException(Exception ex){ return new Result(666,null,"异常我来了"); } }
-
说明
此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
-
项目异常处理
异常分类:
- 业务异常
- 规范的用户行为产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常
- 项目运行过程中可预计且无法避免的异常
- 其他异常
- 编程人员未预期到的异常
异常处理方案
- 业务异常
- 发送对应消息传递给用户,提醒规范操作
- 系统异常
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
异常处理操作流程
-
自定义项目系统级异常
public class SystemException extends RuntimeException{ private Integer code; public SystemException(Integer code, String message){ super(message); this.code = code; } public SystemException(Integer code,String message, Throwable cause){ super(message, cause); this.code = code; } public Integer getCode(){ return code; } public void setCode(Integer code){ this.code = code; } }
-
自定义项目业务级异常
public class BusinessException extends RuntimeException{ private Integer code; public BusinessException(Integer code,String message){ super(message); this.code = code; } public BusinessException(Integer code,String message,Throwable cause){ super(message, cause); this.code = code; } public Integer getCode(){ return code; } public void setCode(Integer code){ this.code = code; } }
-
自定义异常编码(只是一部分,可以根据需求添加)
public class Code{ public static final Integer SYSTEM_UNKNOW_ERROR = 50001; public static final Integer SYSTEM_TIMOUT_ERROR = 50002; public static final Integer PROJECT_VALIDATE_ERROR = 60001; public static final Integer PROJECT_BUSINESS_ERROR = 60002; }
-
触发自定义异常
(只是做一个异常出现情况的模拟,并没有实际含义)
@Service public class BookServiceImpl implements BookService{ @Autoired private BookDao bookDao; public Book getById(Integer id){ if(id < 0){ throw new BusinessException(Code.PROJECT_BUSINESS_ERROR,"请勿进行非法操作!"); } return BookDao.getById(id); } }
-
拦截并处理异常
@RestControllerAdvice public class ProjectExceptionAdvice{ @ExceptionHandler(BusinessException.class) public Result doBusinessException(BusinessException ex){ return new Result(ex.getCode(),null,ex.getMessage()); } @ExceptionHandler(SystemException.class) public Result doSystemException(SystemException ex){ // 记录日志(错误堆栈) // 发送邮件给开发人员 // 发送短信给运维人员 return new Result(ex.getCode(), null, ex.getMessage()); } @ExceptionHandler(Exception.class) public Result doException(Exception ex){ // 记录日志(错误堆栈) // 发送邮件给开发人员 // 发送短信给运维人员 return new Result(Code.SYSTEM_UNKNOW_ERROR,null,"系统繁忙,请联系管理员!"); } }
-
异常处理器效果对比
成功的数据与异常报错的数据展示
案例:SSM整合标准开发(包括前端UI)
- 列表显示
- 新增
- 跳转到修改
- 修改
- 删除
拦截器
拦截器概念
-
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
-
作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 拦截器与过滤器区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同(执行的时机不同):Filter对所有访问进行增强(是在Tomcat服务器的阶段进行配置的),Interceptor仅针对SpringMVC的访问进行增强(取决于getServletMappings方法的配置–>“拦截的路径”)。
入门案例
-
步骤一:声明拦截器的bean,并实现HandlerInterceptor接口(注意要扫描加载bean)(示例)
@Component public class ProjectInterceptor implements HandlerInterceptor{ public boolean preHandle(..) throws Exception{ System.out.println("preHandle..."); return true; } public void postHandler(..) throws Exception{ System.out.println("postHandle..."); } public void afterCompletion(..) throws Exception{ System.out.println("afterCompletion..."); } }
-
步骤二:定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意加载配置类)(该配置类一定要用SpringMvcConfig来扫描来加载,否则将失效或报错)
@Configuration @ComponentScan({"com.catalpa.controller","com.catalpa.config"}) @EnableWebMvc public class SpringMvcSupport extends WebMvcConfigurationSupport{ @Override public void addInterceptors(InterceptorRegistry registry){ //... } }
-
步骤三:添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport{ @Autowired private ProjectInterceptor projectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(projectInterceptor).addPathPatterns("/books"); } }
-
使用标准接口WebMvcConfigurer简化开发(注意,该简化方法侵入性较强,一般不写在SpringMvcConfig里)
@Configuration @ComponentScan("com.catalpa.controller") @EnableWebMvc public class SpringMVcConfig implements WebMvcConfigurer{ @Autowired private ProjectInterceptor projectInterceptor; public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*"); } }
拦截器执行顺序,如图所示
拦截器参数
-
前置处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ System.out.println("preHandle..."); return true; }
- 参数:
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
- 返回值
- 返回值为false,被拦截的处理器将不再执行
- 返回值为true,拦截的处理器继续执行
- 参数:
-
后置处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modeAndView) throws Exception{ System.out.println("postHandle..."); }
- 参数
- modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。
- 参数
-
完成后处理
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{ System.out.println("afterCompletion..."); }
- 参数:
- ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
- 参数:
多拦截器执行顺序
拦截器配置:
创建多个拦截器类后,配置一个拦截器,加一条“add”代码。一个个顺序地写上即可配置。(如图的加代码方式)
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
}
- 当配置多个拦截器的时候,将会形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行 配置在前面的拦截器的afterCompletion操作
(如图可以加强理解)
- 拦截器链的运行顺序
- preHandle:与配置顺序相同,必定运行
- postHandle:与配置顺序相反,可能不运行
- afterCompletion:与配置顺序相反,可能不运行
总结
以上就是关于在黑马的课程里学习SpringMVC过程的笔记,记录下面,方便今后查看,也做一个交流。
SSM = Spring + SpringMvc + Mybatis。
就这样,老规矩,不秃头 日志,以上为个人笔记,也作为经验分享,大家可以参考使用,有问题也可以提出。