SpringMVC技术与servlet技术功能等同,均属于web层开发技术
SpringMVC简介
SpringMVC概述
SpringMVC是一种基于Java实现MIVC模型的轻量级web框架
优点
- 使用简单,开发便捷(相比于servlet)
- 灵活性强
SpringMVC是一种表现层框架技术
SpringMVC用于进行表现层功能开发
SpringMVC入门案例
①:使用SpringMVC技术需要先导入SpringMVC坐标与Servlet坐标
<?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>com.itheima</groupId>
<artifactId>springmvc_01_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--1.导入坐标springmvc和servlet-->
<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>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
②:创建SpringMVC控制器类(等同于Servlet功能)
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
// 2.定义controller
// 2.1使用@Controller定义bean
@Controller
public class UserController {
// 2.2设置当前操作的访问路径
@RequestMapping("/save")
// 2.3设置当前操作的返回值类型
@ResponseBody
public String save() {
System.out.println("user save ...");
return "{'info':'springmvc}";
}
}
③:初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// 3.创建springmvc的配置文件,加载controller对应的bean
@Configuration
@ComponentScan("com.example.controller")
public class SpringMVCConfig {
}
④:初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
package com.example.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
// 4.定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类,产生springmvc容器(本质还是spring容器)
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置由springmvc控制器处理的请求映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
注解:
名称:@Controller
类型:类注解
位置:SpringMVC控制器类定义上方
作用:设定SpringMVC的核心控制器bean
范例:
@Controller
public class UserController {
}
名称:@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径
范例:
@RequestMapping("/save")
public String save() {
System.out.println("user save ...");
return "{'info':'springmvc}";
}
相关属性
value(默认):请求访问路径
名称:@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法响应内容为当前返回值,无需解析
范例:
// 2.定义controller
// 2.1使用@Controller定义bean
@Controller
public class UserController {
// 2.2设置当前操作的访问路径
@RequestMapping("/save")
// 2.3设置当前操作的返回值类型
@ResponseBody
public String save() {
System.out.println("user save ...");
return "{'info':'springmvc}";
}
}
AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
AbstractDispatcherServletInitializer提供三个接口方法供用户实现
- createServletApplicationContext()方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
- createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()
- getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理
入门案例工作流程分析
- 启动服务器初始化过程
- 服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 执行createServletApplicationContext方法,创建了WebApplicationContext对象
- 加载SpringMvcConfig
- 执行@ComponentScan加载对应的bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 执行getServletMappings方法,定义所有的请求都通过SpringMVC
- 单次请求过程
- 发送请求localhost/save
- web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方
bean加载控制
SpringMVC加载其相关bean(表现层bean),也就是controller包下的类
Spring控制的bean
- 业务bean(Service)
- 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)
如何让Spring和SpringMVC分开加载各自的内容。
因为功能不同,如何避免Spring错误加载到SpringMVC的bean?
- 方式一:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
@Configuration
@ComponentScan(value="com.itheima",
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
- 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
@Configuration
@ComponentScan({"com.itheima.service","comitheima.dao"})
public class SpringConfig {
}
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
PostMan
Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件
作用:常用于进行接口测试
特征
- 简单
- 实用
- 美观
- 大方
请求与响应
请求映射路径
-
名称:@RequestMapping
-
类型:方法注解 类注解
-
位置:SpringMVC控制器方法定义上方
-
作用:设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀
-
范例:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @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'}"; } }
-
属性
- value(默认):请求访问路径,或访问路径前缀
请求参数
@Controller
public class UserController {
//请求路径映射
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age) {
System.out.println("name:"+name+",age:"+age);
return "{'user':'common param'}";
}
}
GET请求:
选择GET。
postman中直接输入
http://localhost:8080/commonParam?name=faiz&age=555
点击send
运行结果:
name:faiz,age:555
POST请求:
选择POST。
在postman中输入
http://localhost:8080/commonParam
下面选择body,再选择x-www-form-urlencoded
再KEY和VALUE下分别添加:
name faiz
age 555
点击send,运行结果为:
name:faiz,age:555
Post请求中文乱码处理:
- 为web容器添加过滤器并指定字符集,Spring-web包中提供了专用的字符过滤器
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
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 filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
普通参数
- 普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数
- 请求参数名与形参变量名不同,使用@RequestParam绑定参数关系
@Controller
//类上方配置的请求映射与方法上面配置的请求映射连接在一起,形成完整的请求映射路径
//@RequestMapping("/user")
public class UserController {
//请求路径映射
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(@RequestParam("name2") String name,int age) {
System.out.println("name:"+name+",age:"+age);
return "{'user':'common param'}";
}
}
POJO参数
- POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数
package com.itheima.domain;
public class User {
String name;
int age;
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 +
'}';
}
}
package com.itheima.controller;
import com.itheima.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
//类上方配置的请求映射与方法上面配置的请求映射连接在一起,形成完整的请求映射路径
//@RequestMapping("/user")
public class UserController {
//请求路径映射
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(User user) {
System.out.println("user:"+user);
return "{'user':'common param'}";
}
}
http://localhost:8080/commonParam?name=faiz&age=555
运行结果为:
user:User{name='faiz', age=555}
嵌套POJO参数
- 嵌套POJO参数:POJO对象中包含POJO对象
package com.itheima.domain;
public class Address {
String province;
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;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
package com.itheima.domain;
public class User {
String name;
int age;
Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
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 +
", address=" + address +
'}';
}
}
- 嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
package com.itheima.controller;
import com.itheima.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
//类上方配置的请求映射与方法上面配置的请求映射连接在一起,形成完整的请求映射路径
//@RequestMapping("/user")
public class UserController {
//请求路径映射
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(User user) {
System.out.println("user:"+user);
return "{'user':'common param'}";
}
}
http://localhost:8080/commonParam?name=faiz&age=555&address.city=sy&address.province=hn
运行结果为:
user:User{name='faiz', age=555, address=Address{province='hn', city='sy'}}
数组参数
- 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数
@Controller
public class UserController {
//请求路径映射
@RequestMapping("/param")
@ResponseBody
public String param(String[] names) {
System.out.println("names:"+ Arrays.toString(names));
return "{'user':'common param'}";
}
}
http://localhost:8080/param?names=faiz&names=wxm&names=tdzs
运行结果为
names:[faiz, wxm, tdzs]
集合保存普通参数
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
@Controller
public class UserController {
//请求路径映射
@RequestMapping("/param")
@ResponseBody
public String param(@RequestParam List<String> names) {
System.out.println("names:"+ names);
return "{'user':'common param'}";
}
}
http://localhost:8080/param?names=faiz&names=wxm&names=tdzs
运行结果为:
names:[faiz, wxm, tdzs]
接收请求中的json数据
- 添加json数据转换相关坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version>
</dependency>
-
设置发送json数据(请求body中添加json数据)
再postman中选择Body,再选择raw,再选择JSON
-
开启自动转换json数据的支持
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc // 这里
public class SpringMvcConfig {
}
- 设置接收json数据
@Controller
public class UserController {
//请求路径映射
@RequestMapping("/param")
@ResponseBody
public String param(@RequestBody List<String> names) {
System.out.println("names:"+ names);
return "{'user':'common param'}";
}
}
在postman中输入
["aa","bb","cc","dd"]
点击send,结果为
names:[aa, bb, cc, dd]
@RequestBody与@RequestParam区别
-
区别
-
@RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
-
@RequestBody用于接收json数据【application/json】
-
-
应用
-
后期开发中,发送json格式数据为主,@RequestBody应用较广
-
如果发送非json格式数据,选用@RequestParam接收请求参数
-
日期类型参数传递
-
日期类型数据基于系统不同格式也不尽相同
-
2088-08-18
-
2088/08/18
-
08/18/2088
-
-
接收形参时,根据不同的日期格式设置不同的接收方式
@Controller
public class UserController {
//请求路径映射
@RequestMapping("/param")
@ResponseBody
public String param(Date date,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date date2) {
System.out.println("date:"+ date);
System.out.println("date1:"+ date1);
System.out.println("date2:"+ date2);
return "{'user':'common param'}";
}
}
http://localhost:8080/param?date=2023/07/22&date1=2023-07-21&date2=2023/07/22 09:33:55
运行结果为:
date:Sat Jul 22 00:00:00 GMT+08:00 2023
date1:Fri Jul 21 00:00:00 GMT+08:00 2023
date2:Sat Jul 22 09:33:55 GMT+08:00 2023
响应
响应页面(了解)
@Controller
@RequestMapping("/book")
public class BookController {
//请求路径映射
@RequestMapping("index")
public String index(){
System.out.println("index");
return "/book.jsp";
}
}
响应文本数据(了解)
@Controller
@RequestMapping("/book")
public class BookController {
@RequestMapping("text")
@ResponseBody
public String text(){
System.out.println("text");
return "Hello World";
}
}
响应json数据(对象转json)
@Controller
@RequestMapping("/book")
public class BookController {
//请求路径映射
@RequestMapping("index")
public String index(){
System.out.println("index");
return "/book.jsp";
}
@RequestMapping("text")
@ResponseBody
public String text(){
System.out.println("text");
return "Hello World";
}
@RequestMapping("pojo")
@ResponseBody
public User pojo(){
User user = new User();
user.setName("faiz");
user.setAge(18);
return user;
}
}
http://localhost:8080/book/pojo
运行结果:
{
"name": "faiz",
"age": 18,
"address": null
}
响应json数据(对象集合转json数组)
@Controller
@RequestMapping("/book")
public class BookController {
//请求路径映射
@RequestMapping("list")
@ResponseBody
public List<User> list(){
User user = new User();
user.setName("faiz");
user.setAge(18);
User user2 = new User();
user2.setName("faiz");
user2.setAge(18);
List<User> list = new ArrayList<>();
list.add(user);
list.add(user2);
return list;
}
}
http://localhost:8080/book/list
运行结果为:
[
{
"name": "faiz",
"age": 18,
"address": null
},
{
"name": "faiz",
"age": 18,
"address": null
}
]
名称:@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器返回值作为响应体
REST风格
REST简介
REST(Representational State Transfer),表现形式状态转换
-
传统风格资源描述形式
http://localhost/user/getById?id=1
http://localhost/user/saveUser
-
REST风格描述形式
http://localhost/user/1
http://localhost/user
优点:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
- http://localhost/users 查询全部用户信息 GET(查询)
- http://localhost/users/1 查询指定用户信息 GET(查询)
- http://localhost/users 添加用户信息 POST(新增/保存)
- http://localhost/users 修改用户信息 PUT(修改/更新)
- http://localhost/users/1 删除用户信息 DELETE(删除)
根据REST风格对资源进行访问称为RESTful
RESTful入门案例
package com.itheima.controller;
import com.itheima.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class UserController {
//请求路径映射
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
//请求路径映射
@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(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("user update ..."+user);
return "{'module':'user update'}";
}
//请求路径映射
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById ..."+id);
return "{'module':'user getById'}";
}
}
@RequestMapping
名称:@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径
属性
- value(默认):请求访问路径
- method:http请求动作,标准动作(GET/POST/PUT/DELETE)
@PathVariable
名称:@PathVariable
类型:形参注解
位置:SpringMVC控制器方法形参定义前面
作用:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
@RequestBody @RequestParam @PathVariable
- 区别
- @RequestParam用于接收url地址传参或表单传参
- @RequestBody用于接收json数据
- @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
- 应用
- 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
- 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值
REST快速开发
//@Controller
//@ResponseBody
@RestController
@RequestMapping("users")
public class UserController {
//请求路径映射
// @RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
//请求路径映射
// @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("user delete ..."+id);
return "{'module':'user delete'}";
}
//请求路径映射
// @RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody User user){
System.out.println("user update ..."+user);
return "{'module':'user update'}";
}
//请求路径映射
// @RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("user getById ..."+id);
return "{'module':'user getById'}";
}
}
@RestController
名称:@RestController
类型:类注解
位置:基于SpringMVC的RESTful开发控制器类定义上方
作用:设置当前控制器类为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能
@GetMapping @PostMapping @PutMapping @DeleteMapping
名称:@GetMapping @PostMapping @PutMapping @DeleteMapping
类型:方法注解
位置:基于SpringMVC的RESTful开发控制器方法定义上方
作用:设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求
属性
- value(默认):请求访问路径
设置对静态资源的访问放行
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/????时候,走/pages目录下的内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
SSM整合
SSM整合
SSM整合流程
1.创建工程
2.SSM整合
- Spring
- SpringConfig
- MyBatis
- Mybatisconfig
- JdbcConfig
- jdbc.properties
- SpringMVC
- servletConfig
- SpringMvcConfig
3.功能模块
- 表与实体类
- dao(接口+自动代理)
- service(接口+实现类)
- 业务层接口测试(整合JUnit )
- controller
- 表现层接口测试(PostMan )
表现层数据封装
表现层数据封装
- 设置统一数据返回结果类
package org.example.controller;
public class Result {
private Object data;
private Integer code;
private String msg;
public Result() {
}
public Result(Integer code, Object data) {
this.data = data;
this.code = code;
}
public Result(Integer code, Object data, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
注意事项
Result类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作
- 设置统一数据返回结果编码
package org.example.controller;
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
package org.example.controller;
import org.example.domain.Book;
import org.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
@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);
}
@GetMapping
public Result getAll() {
List<Book> bookList = bookService.getAll();
Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
String msg = bookList != null ? "" : "数据查询失败,请重试";
return new Result(code,bookList,msg);
}
}
异常处理器
出现异常现象的常见位置与常见诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决——AOP思想
异常处理器
集中的、统一的处理项目中出现的异常
在controller下新建一个类:
package org.example.controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public Result doException(Exception e) {
System.out.println("异常:"+e);
return new Result(555,null,e.toString());
}
}
项目异常处理方案
项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期到的异常
项目异常处理方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
- 自定义项目系统级异常
package org.example.exception;
public class SystemException extends RuntimeException{
private Integer code;
public SystemException(String message, Integer code) {
super(message);
this.code = code;
}
public SystemException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 自定义项目业务级异常
package org.example.exception;
public class BusinessException extends RuntimeException{
private Integer code;
public BusinessException(String message, Integer code) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 自定义异常编码(持续补充)
package org.example.controller;
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;
public static final Integer BUSINESS_ERR = 60002;
public static final Integer BUSINESS_UNKNOWN_ERR = 59999;
public static final Integer SYSTEM_ERR = 50002;
}
- 触发自定义异常
package org.example.service.impl;
import org.example.controller.Code;
import org.example.dao.BookDao;
import org.example.domain.Book;
import org.example.exception.BusinessException;
import org.example.exception.SystemException;
import org.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
bookDao.save(book);
return true;
}
@Override
public boolean update(Book book) {
bookDao.update(book);
return true;
}
@Override
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
// 这里
@Override
public Book getById(Integer id) {
// 将可能出现的异常进行包装,转换成自定义异常
if (id == 0) {
throw new BusinessException("请输入规范的id", Code.BUSINESS_ERR);
}
try {
int i = 1/0;
} catch (Exception e) {
throw new SystemException("服务器访问超时,请重试",e,Code.SYSTEM_ERR);
}
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
- 拦截并处理异常
package org.example.controller;
import org.example.exception.BusinessException;
import org.example.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException e) {
// 记录日志
// 发送消息给运维
// 发送邮件给开发人员,e对象发给
return new Result(e.getCode(),null,e.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doSystemException(BusinessException e) {
return new Result(e.getCode(),null,e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result doException(Exception e) {
System.out.println("异常:"+e);
return new Result(Code.BUSINESS_UNKNOWN_ERR,null,"系统繁忙,请稍后再试");
}
}
拦截器
拦截器概念
拦截器(Interceptor )是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
作用:
- 在指定的方法调用前后执行预先设定后的的代码
- 阻止原始方法的执行
拦截器与过滤器区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
入门案例
- 声明拦截器的bean,并实现HandlerInterceptor接口(注意:扫描加载bean)
package org.example.controller.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class ProjectInceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
// false终止原始操作的执行
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("afterCompletion...");
}
}
- 定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置)
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
}
}
- 添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个
package org.example.config;
import org.example.controller.interceptor.ProjectInceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInceptor projectInceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInceptor).addPathPatterns("/books","/books/*");
}
}
- 使用标准接口webMvcConfigurer简化开发(注意:侵入式较强)
package org.example.config;
import org.example.controller.interceptor.ProjectInceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ComponentScan({"org.example.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInceptor projectInceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInceptor).addPathPatterns("/books","/books/*");
}
}
拦截器执行流程
拦截器参数
前置处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
System.out.println("handler:"+handler+" handler.getClass():"+handler.getClass());
HandlerMethod hm = (HandlerMethod) handler;
System.out.println("preHandle..."+contentType);
// false终止原始操作的执行
return true;
}
参数
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
返回值
- 若返回值为false,被拦截的处理器将不执行
后置处理
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
参数
- modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
完成后处理
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
参数
- ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
拦截器链配置
多拦截器执行顺序
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作