SSM所需的前置知识为Javaweb,同时也是SpringBoot所需前置知识
SpringMVC
1. SpringMVC简介
SpringMVC技术与Servlet技术功能相同,均属于web层开发技术,但SpringMVC技术更简洁,能用更少的代码进行开发
1.1 SpringMVC概述
- SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
- 功能
- 用于进行表现层功能开发
- 优点
- 使用简单,开发便捷(相比于Servlet)
- 灵活性强
1.2 入门案例★
- 使用SpringMVC技术需要先导入SpringMVC坐标与Servlet坐标
<!--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>
- 创建SpringMVC控制器类(等同于Servlet功能)
/*2.定义controller类同时将其标记成bean*/ @Controller public class UserController { @RequestMapping("/save") // 设置当前操作的访问路径 @ResponseBody // 设置当前操作的返回值类型 public String save() { System.out.println("user save ..."); return "{'module':'springmvc'}"; } }
- 初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean
/*3.创建springmvc的配置文件,扫描加载controller对应的bean*/ @Configuration @ComponentScan("com.wang.controller") public class SpringMvcConfig{ }
- 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理请求
/*4.定义一个servlet容器启动的配置类,在里面加载spring的配置*/ public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { // 加载springMVC容器配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMvcConfig.class); return ctx; } // 设置哪些请求归属springMVC处理 @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 加载spring容器配置 @Override protected WebApplicationContext createRootApplicationContext() { return null; } }
入门案例用到的注解
名称 | 类型 | 位置 | 作用 |
---|---|---|---|
@Controller | 类注解 | SpringMVC控制器定义上方 | 设置SpringMVC的核心控制器bean |
@ResquestMapping | 方法注释 | SpringMVC控制器方法定义上方 | 设置当前控制器方法请求访问路径 |
@ResponseBody | 方法注释 | SpringMVC控制器方法定义上方 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
SpringMVC开发总结(1+N)
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
- SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
Servlet容器配置类
AbstractDispatcherServletInitializer
类是SpringMVC提供的快速初始化web3.0容器的抽象类AbstractDispatcherServletInitializer
提供三个接口方法供用户实现-
createServletAppilcationContext()方法,创建servlet容器时,加载SpringMVC核心配置类对应的bean并放入
WebApplicationContext
对象中,而WebApplicationContext
的作用范围为ServletContext范围,即整个web容器范围protected WebApplicationContext createServletApplicationContext(){ AnnotattionConfigWebApplicationContext ctx= new AnnotattionConfigWebAppklicationtContext(); ctx.register(SpringMvcConfig.class); return ctx; }
-
getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为" / "表示拦截所有请求,任意请求都将转入到SpringMVC进行处理
protected String[] getServletMapping(){ return new String[]("/"); }
-
createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletAppilcationContext()
protected WebApplicationContext createRootApplicationContext(){ return null; }
-
1.3 工作流程分析★
- 启动服务器初始化过程
- 服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 执行createServletApplicationContext方法,创建了WebApplicationContext对象
- 加载SpringMvcConfig
- 执行@ComponentScan加载对应的bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 执行getServletMappings方法,定义所有请求都通过SpringMVC
- 单次请求过程
- 发送请求localhost/save
- web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
1.4 Controller加载控制
- SpringMVC相关bean
- 表现层bean(Controller)
- Spring控制的bean
- 业务层bean(Service)
- 功能bean(DataSource等)
- SpringMVC相关bean加载控制
- SpringMVC加载的bean对应的包均在com.wang.controller包内
- Spring相关bean加载控制
-
方式一:Spring加载的bean设定扫描范围为com.wang,排除包含@controller注解的bean
@Configuration @ConmponentScan(value = "com.wang", excludeFilters = @ComponentScna.Filter( type = FilterType.ANNOTATION, classer = Controller.class ) ) public class SpringConfig{}
-
方式二:Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等
@Configuretion /* dao层在MyBatis中通过自动代理得到实现对象,可以不扫描加载, 但为了适配所有的数据层技术实现标准开发还是扫描加载了更合适 */ @ComponentScan({"com.wang.service", "com.wang.dao"}) public class SpringConfig{}
-
方式三:不区分Spring与SpringMVC的环境,加载到同一环境中(见后续简化开发部分)
-
bean的加载格式
//4.定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
// 加载springMVC容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
// 设置哪些请求归属springMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 加载spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
简化开发
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
注解深入学习
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | Spring配置类上方 |
属性 | excludeFilters :排除扫描路径中加载的bean,需要指定类别(type)与具体项(classes)includeFilters :加载指定的bean,需要指定类别(type)与具体项(classes) |
范例:
@Configuration
@ConmponentScan(value="com.wang",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig{}
1.5 Postman
略
2. 请求与响应
2.1 请求映射路径
名称 | @RequestMapping |
---|---|
类型 | 方法注解/类注解 |
位置 | SpringMVC控制器类上方或类中方法定义上方 |
作用 | 设置当前控制器方法请求访问路径,如果设置在类上统一设置当前类中所有方法的请求访问路径前缀 |
属性 | value(默认):请求访问路径 method:HTTP请求动作(POST/GET/PUT/DELETE) |
范例:
@Controller
@RequestMapping("/user")
public class UserController{
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
}
2.2 请求参数
Post请求中文乱码处理
- 在Servlet容器ServletContainersInitConfig.java中添加过滤器并指定字符集,Spring-web包中提供了专用的字符过滤器类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 乱码处理 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
参数种类
-
普通参数:URL地址传参
- 地址参数名与形参变量名相同,定义形参即可接收参数
@RequestMapping("/commonParam") @ResponseBody public String commonParam(String name, int age) { System.out.println("普通参数传递 name ==> " + name); System.out.println("普通参数传递 age ==> " + age); return "{'module':'common param'}"; }
- 地址参数名与形参变量名不同,使用注解接受对应参数
名称 @RequestParam
类型 形参注解 位置 SpringMVC控制器方法形参定义前面 作用 绑定请求参数与处理器方法形参间的关系 参数 required: 是否为必传参数
defaultValue:参数默认值@RequestMapping("/commonParamDifferentName") @ResponseBody public String commonParamDifferentName(@RequestParam("name") String userName, int age){ System.out.println("普通参数 userName ==>" + userName); System.out.println("普通参数 userName ==>" + age); return "{'module':'common param different name'}"; }
- 地址参数名与形参变量名相同,定义形参即可接收参数
-
POJO参数:请求参数名与形参对象属性名相同,定义POJP类型形参即可接收参数
@RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("pojo参数传递 user ==>" + user); return "{'module':'pojo param'}"; }
-
嵌套POJO参数:同POJO类型参数,其中嵌套的POJO属性在URL路径中通过对象.属性来绑定参数关系
@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'}"; }
-
集合类型参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
@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的pom坐标
<!--json数据处理--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.4</version> </dependency>
-
设置发送JSON数据(在Postman请求中Body-raw-JSON中添加JSON数据)
-
在SpringMvcConfig.class上加入@EnableWebMvc,开启自动转换json数据的支持
/*3. 创建springmvc的配置文件,加载controller对应的bean*/ @Configuration @ComponentScan(value = "com.wang", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) @EnableWebMvc // 开启自动转换json数据的支持 public class SpringMvcConfig { }
注意:@EnableWebMvc注解功能强大,整合了多个功能,此处仅使用了其中一部分功能,即JSON数据进行自动类型转换。
-
在Controller(控制层)编写代码,设置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
配置类注解 SpringMVC配置定义上方 开启SpringMVC多项辅助功能 见本节JSON数据传参第三步 @RequestBody
形参注解 SpringMVC控制器方法形参定义前面 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 见本节JSON数据传参第四步
@RequestBody
与@RequestParam
区别
- 区别
@RequestParam
用于接收url地址传参和表单传参【application/x-www-form-urlencoded】@RequestBody
用于接收json数据【application/json】
- 应用
- 后期开发中,发送json格式数据为主,
@ResquestBody
应用较广 - 如果发送非json格式数据,选用
@RequestParam
接收请求参数
- 后期开发中,发送json格式数据为主,
2.3 日期类型参数传递
- 日期类型数据基于系统不同格式也不尽相同
- 2022-08-30
- 2022/08/30
- 08/18/2022
- 接收形参时,根据不同的日期格式设置不同的接收方式
@RequestMapping("/dateParam")
@ResponseBody
public String dateParam(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);
retrun "{'module':'data param'}";
}
日期类型注解
名称 | @DateTimeFormat |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参前面 |
作用 | 设定日期时间型数据格式 |
参数 | pattern = “日期时间格式字符串”(eg.yyyy-MM-dd、yyyy/MM/dd HH:mm:ss) |
类型转化器
- Converter接口
- 请求参数年龄数据(String ==>Integer)
- 日期格式转换 (String ==> Date)
public interface Converter<S,T>{ @Nullable T convert(S source); }
@EnableWebMvc
功能之一:根据类型匹配对应类型转换器
2.4 响应
响应类型包括页面、数据(文本数据、JSON数据)
-
响应页面
@RequestMapping("/toPage") public String toPage(){ return "page.jsp"; }
-
响应文本数据
@RequestMapping("/toText") @ResponseBody public String toText(){ return "response text"; }
-
响应JSON数据(对象转JSON)
//响应POJO对象数据 @RequestMapping("/toJsonPOJO") @ResponseBody public User toJsonPOJO(){ User user = new User(); user.setName("wang"); user.setAge(24); return user; }
-
响应对象集合转JSON数组
//响应POJO集合对象 @RequestMapping("/toJsonList") @ResponseBody public List<User> toJsonList(){ User user= new User(); user.setName("wang"); user.setAge(24); User user1= new User(); userOne.setName("chen"); userOne.setAge(18); List<User> userList = new ArrayList<>(); userList.add(user); userList.add(user1); return userList; }
名称 | @ResponseBody |
---|---|
类型 | 方法注解 |
位置 | SpringMVC控制器方法定义上方 |
作用 | 设置当前控制器返回值作为响应体 |
范例 | 见本节响应数据部分代码 |
类型转换器(HttpMessageConverter)
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> var1, @Nullable MediaType var2);
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
3. REST风格
-
REST(Representational State Transfer)表现形式状态转换
-
传统风格资源描述形式
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
-
REST风格描述形式
- http://localhost/user/1
- http://localhost/user
-
-
优点:
- 隐藏资源的访问行为,无法通过地址得知对资源的具体操作
- 简化书写
3.1 REST简介
按照REST风格访问资源时使用行为动作区分对资源的具体操作,根据REST风格对资源进行访问称为RESTful。
URL | 操作 | 请求形式 |
---|---|---|
http://localhost/users | 查询全部用户信息 | GET (查询) |
http://loocalhost/users/1 | 查询指定用户信息 | GET(查询) |
http://localhost/users | 添加用户信息 | POST(新增/保存) |
http://localhost/users | 修改用户信息 | PUT(修改/更新) |
http://localhost/users/1 | 删除用户信息 | DELETE(删除) |
注意事项:
- 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范;
- 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源非单个资源,例如:users,books,accounts…
3.2 RESTful入门案例
-
设定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 publiic String update(@RequestBody User user){ System.out.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 deleter'}"; }
注解详解
名称 | 类型 | 位置 | 作用 | 属性 |
---|---|---|---|---|
@PathVariable | 形参注解 | SpringMVC控制器方法形参定义前面 | 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应 | / |
@RequestMapping | 方法注解 | SpringMVC控制器定义上方 | 设置当前控制器方法请求访问路径 | value(默认):请求访问路径 method:HTTP请求动作(POST/GET/PUT/DELETE) |
@RequestBody
@RequestParam
@PathVariable
对比学习
- 区别
@RequestParam
用于接收URL地址传参或表单传参@RequestBody
用于接收JSON数据@PathVariable
用于接收路径参数,使用{参数名称}描述路径参数
- 应用
- 后期开发中,发送请求参数超过一个时,以JSON格式为主,
@RequestBody
应用较广 - 如果发送非json格式数据,选用
@RequestParam
接收请求参数 - 采用RESTful进行开发,当参数数量较少时(例如1个),常采用
@PathVariable
接收路径变量,常用于传递id值
- 后期开发中,发送请求参数超过一个时,以JSON格式为主,
3.3 RESTful快速开发
@RestController
@RequestMapping("/books")
public class BookController{
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ..." + book);
return "{'module':'book save'}";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete ..." + id);
return "{'module':'book delete'}";
}
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update ..." + book);
return "{'module':'book update'}";
}
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById ..." + id);
return "{'module':'book getById'}";
}
@GetMapping
public String getAll(){
System.out.println("book getAll ...");
return "{'module':'book getAll'}";
}
}
合并注解事项简化开发
名称 | 类型 | 位置 | 作用 | 属性 |
---|---|---|---|---|
@RestController | 类注解 | 基于SpringMVC的RESTful开发控制器类定义上方 | 设置当前控制器类为RESTful风格,等同于@Controller 与@ResponseBody 两个注解组合 | / |
@GetMapping | 方法注解 | 基于SpringMVC的RESTful开发控制器方法定义上方 | 设置当前控制器方法请求访问路径与请求动作(Get) | value(默认):请求访问路径 |
@PostMapping | 方法注解 | 基于SpringMVC的RESTful开发控制器方法定义上方 | 设置当前控制器方法请求访问路径与请求动作(Post) | value(默认):请求访问路径 |
@PutMapping | 方法注解 | 基于SpringMVC的RESTful开发控制器方法定义上方 | 设置当前控制器方法请求访问路径与请求动作(Put) | value(默认):请求访问路径 |
@DeleteMapping | 方法注解 | 基于SpringMVC的RESTful开发控制器方法定义上方 | 设置当前控制器方法请求访问路径与请求动作(Delete) | value(默认):请求访问路径 |
3.4 案例:基于RESTful页面数据交互
-
制作SpringMVC控制类,并通过PostMan测试接口功能(SpringMvcConfig.class和ServletContainersInitConfig.class配置同之前)
@RestController @RequestMapping("/books") public Class BookController{ @PostMapping public String save(@RequestBody Book book) { System.out.println("book save ==>" + book); return "{'moudule':'book save success'}"; } @GetMapping public List<Book> getAll(){ System.out.println("book getAll is running..."); List<Book> bookList = new ArrayList<>(); Book book = new Book(); book.setType("计算机"); book.setName("SpringMVC入门教程"); book.setDescription("小试牛刀"); bookList.add(book); Book book1 = new Book(); book1.setType("计算机"); book1.setName("SpringMVC入门教程"); book1.setDescription("加油"); bookList.add(book1); return bookList; } }
-
设置对静态资源的访问放行
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { // 当访问/pages/*时走/pages目录下的内容访问 registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); // 放行js registry.addResourceHandler("/js/**").addResourceLocations("/js/"); // 放行css registry.addResourceHandler("/css/**").addResourceLocations("/css/"); // 放行插件 registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); } }
-
前端页面通过异步提交访问后台控制器
// 添加 saveBook(){ axios.post("/books", this.formData).then((res)=>{ }); }, // 主页列表查询 getAll(){ axios.get("/books").then((res)=>{ this.dataList = res.data; }); },
-
总结
- 先做后台功能,开发接口并调通接口
- 再做页面异步调用,确认功能可以正常访问
- 最后再完成页面数据的显示
- 补充:放行静态资源访问
4. SSM整合
4.1 SSM整合
-
创建工程,并导入坐标
<?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.wang</groupId> <artifactId>springmvc_08_ssm</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!--spring整合mybatis需要三个坐标:mybatis、mybatis-spring、mysql-connect-java--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--该项目中选择使用druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version> </dependency> <!--测试用--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--做springmvc开发时,web容器要用到的--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!--实现json文件与各数据类型之间的互相转换--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
SSM整合
-
Spring
- SpringConfig
@Configuration @ComponentScan("com.wang") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class, MyBatisConfig.class}) public class SpringConfig{ }
-
MyBatis
- MybatisConfig
public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage("com.wang.domain"); return factoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.wang.dao"); return msc; } }
- JdbcConfig
public class JdbcConfig{ @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 transactionManager(DataSource dataSource){ DataSourceTransactionManager ds = new DataSourceTransactionManager(); ds.setDataSource(dataSource); return ds; } }
- jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_db jdbc.username=root jdbc.password=******
-
SpringMVC
- ServletConfig
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String[]{"/"}; } // 乱码处理——post表单提交时处理中文乱码 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
- SpringMvcConfig
@Configuration @ComponentScan("com.wang.controller") @EnableWebMvc public class SpringMvcConfig { }
-
-
功能模块
- domain(表与实体类)
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class Book { private Integer id; private String type; private String name; private String description; }
- dao(接口+自动代理)
public interface BookDao { /*@Insert("insert into tbl_book values(null, #{type}, #{name}, #{description})")*/ @Insert("insert into tbl_book (type, name, description) values(#{type}, #{name}, #{description})") public void save(Book book); @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}") public void update(Book book); @Delete("delete from tbl_book where id = #{id}") public void delete(Integer id); @Select("select * from tbl_book where id = #{id}") public Book getById(Integer id); @Select("select * from tbl_book") public List<Book> getAll(); }
-
service(接口+实现类)
- 接口
@Transactional public interface BookService { /** * 保存 * @param book * @return */ public Boolean save(Book book); /** * 修改 * @param book * @return */ public Boolean update(Book book); /** * 根据id删除 * @param id * @return */ public Boolean delete(Integer id); /** * 根据id查询 * @param id * @return */ public Book getById(Integer id); /** * 查询全部 * @return */ public List<Book> getAll(); }
- 实现类
@Service public class BookServiceImpl implements BookService { @Autowired // Mybatis使用自动代理生成dao层的bean导致此处可能报错,修改IDEA配置即可解决问题 private BookDao bookDao; public Boolean save(Book book) { bookDao.save(book); return true; } public Boolean update(Book book) { bookDao.update(book); return true; } public Boolean delete(Integer id) { bookDao.delete(id); return true; } public Book getById(Integer id) { return bookDao.getById(id); } public List<Book> getAll() { return bookDao.getAll(); } }
- 业务层接口测试(整合JUnit)
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById(){ Book book = bookService.getById(1); System.out.println(book); } @Test public void testGetAll(){ List<Book> list = bookService.getAll(); System.out.println(list); } }
-
controller
@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_ALL_OK : Code.GET_ALL_ERR; String msg = bookList != null ? "全部数据查询成功" : "数据查询失败请重试"; return new Result(code, bookList, msg); } }
- 数据传输协议所要用到的bean
@Data @AllArgsConstructor @NoArgsConstructor @ToString public class Result { private Integer code; private Object data; private String msg; public Result(Integer code, Object data) { this.code = code; this.data = data; } }
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 GET_ALL_OK = 20051; 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 GET_ALL_ERR = 20050; }
- 表现层接口测试(Apifox)
4.2 表现层数据封装
为了便于实现前后端数据交互,统一返回数据的格式,在Controller包中添加Result类(定义返回值类型)和Code类(定义返回值对象中对应的静态常量),并将所有返回值封装至Result对象中让Controller里面的方法都返回Result类型的结果。
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Result {
private Integer code;
private Object data;
private String msg;
public Result(Integer code, Object data) {
this.code = code;
this.data = data;
}
}
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 GET_ALL_OK = 20051;
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 GET_ALL_ERR = 20050;
}
4.3 异常处理器
异常处理器
- 程序开发过程中不可避免地会出现异常
- 出现异常现象的常见位置与常见诱因如下:
- 框架内部抛出异常:因使用不规范导致
- 数据层抛出的异常:因为外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集,校验规则导致(例如:不匹配的数据类型导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长时间未释放等)
- 各个层级均可能出现异常,所有的异常抛出到表现层处理
- 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,因此建议通过AOP思想实现异常处理。
定义异常处理器(集中统一地处理项目中的异常)
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public Result doExpetion(Exception ex){
System.out.println("异常被捕获!");
return new Result(001, null, "异常被捕获!");
}
}
注解 | 类型 | 位置 | 作用 | 范例 | 备注 |
---|---|---|---|---|---|
@RestControllerAdvice | 类注解 | Rest风格开发的控制器增强类定义上方 | 为Rest风格开发的控制器类做增强 | 见本节代码 | 此注解自带@ResponseBody 注解与@Component 注解,具备对应的功能 |
@ExceptionHandler | 方法注解 | 专用于异常处理的控制器方法上方 | 设定指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行 | 见本节代码 | 此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常 |
4.4 项目异常处理方案
项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为操作产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期到的异常
项目异常处理方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常(Exception)
- 发送固定消息给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
项目异常处理
-
自定义项目系统级异常
public class SystemException extends RuntimeException{ private Integer code; public Integer getCode() { return 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 class BusinessException extends RuntimeException{ private Integer code; public Integer getCode() { return 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 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 GET_ALL_OK = 20051; 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 GET_ALL_ERR = 20050; public static final Integer SYSTEM_UNKNOW_ERROR = 50001; public static final Integer SYSTEM_TIMEOUT_ERROR = 50002; public static final Integer PROJECT_VALIDATE_ERROR = 60001; public static final Integer PROJECT_BUSINESS_ERROR = 60002; }
-
触发定义异常
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; public Book getById(Integer id) { // 模拟业务异常 if(id < 0){ throw new BusinessException(Code.PROJECT_BUSINESS_ERROR, "请勿进行非法操作!"); } // 将可能出现的异常进行包装,转换成自定义异常 try { int i = 1/0; }catch (Exception ex){ throw new SystemException(Code.SYSTEM_TIMEOUT_ERROR, "服务器访问超时,请重试", ex); } return bookDao.getById(id); } }
-
拦截并处理异常
@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(SystemException.class) public Result doSystemException(SystemException exception) { //记录日志(错误堆栈) //发送消息给运维 //发送邮件给开发人员,exception对象发送给开发人员 System.out.println("系统异常被捕获"); return new Result(exception.getCode(), null, exception.getMessage()); } @ExceptionHandler(BusinessException.class) public Result doBusinessException(BusinessException exception) { System.out.println("业务异常被捕获"); return new Result(exception.getCode(), null, exception.getMessage()); } @ExceptionHandler(Exception.class) public Result doException(Exception exception) { //记录日志(错误堆栈) //发送消息给运维 //发送邮件给开发人员,exception对象发送给开发人员 System.out.println("其他异常被捕获"); return new Result(Code.SYSTEM_UNKNOW_ERROR, null, "系统繁忙请稍后再试!"); } }
-
异常处理效果对比
4.5 案例:SSM整合标准开发
自定义项目系统级异常
axios.get("/books").then((res)=>{});
axios.post("/books", this.formData).then((res)=>{});
axios.delete("/books/" + row.id).then((res)=>{});
axios.put("/books", this.formData).then((res)=>{});
axios.get("/books/" + row.id).then((res)=>{});
5. 拦截器
5.1 拦截器概念
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,再SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 拦截器和过滤器的区别
- 归属不同:Filter(过滤器)属于Servlet技术,Interceptor(拦截器)属于SpringMvc技术
- 拦截内容不同:Filter(过滤器)可以对所有访问进行增强,Interceptor(拦截器)仅针对SpringMVC的访问进行增强
5.2 入门案例
- 声明拦截器的bean,并实现HandlerInterceptor接口(注意:扫描加载bean);
@Component public class ProjectInterceptor 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("afterCompletion......"); } }
- 定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置);
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Override protected void addInterceptor(InterceptorRegistry registry) { ..... } }
- 添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个;
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*"); } }
- 使用标准接口WebMvcConfigurer简化开发(注意:侵入式较强)
@Configuration @ComponentScan({"com.itheima.controller"}) @EnableWebMvc //实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性 public class SpringMvcConfig implements WebMvcConfigurer { @Autowired private ProjectInterceptor projectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*"); } }
拦截器执行流程
5.3 拦截器参数
@Component
public class ProjectInterceptor 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("afterCompletion......");
}
}
具体参数:
- handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装,可用于获取拦截方法信息
- request:请求对象
- response:响应对象
- modelAndView:获取页面跳转相关数据
- ex:拿到原始程序执行过程中出现的异常,表现层出现的异常
注:preHandle方法中若返回false,那么被拦截的处理器将不再继续执行。
5.4 拦截器链配置
多拦截器执行顺序
拦截器链的运行顺序
- preHandle:与配置顺序相同,必定运行
- postHandle:与配置顺序相反,可能不运行
- afterCompletion: 与配置顺序相反,可能不运行
解释:顺序情况下的话,走到哪里出现错误返回false,则后面的postHandle拦截器都不运行,afterComletion会运行