文章目录
- 1 SpringBoot 基本介绍
- 2 依赖管理和自动配置
- 3 容器功能
- 5 Lombok
- ~~6 Spring Initailizr~~
- 7 yaml
- 8 WEB 开发-静态资源访问
- 9 Rest 风格请求处理
- 10 接收参数相关注解
- 11 自定义转换器
- 13 内容协商
- 14 Thymeleaf(感觉过时 ) 在复习看这里
- 15 拦截器-HandlerInterceptor
- 16 文件上传
- 17 异常处理
- 18 注入 Servlet、Filter、Listener
- 19 内置 Tomcat 配置和切换
- 20 数据库操作
- 21 Spring Boot 整合 MyBatis
- 22 Spring Boot 整合 MyBatis-Plus
1 SpringBoot 基本介绍
1.1 官方文档
1.1.1 官网: https://spring.io/projects/spring-boot
1.1.2 学习文档: https://docs.spring.io/spring-boot/docs/current/reference/html/
1.1.3 离线文档: spring-boot-reference.pdf
1.1.4 在线 API: https://docs.spring.io/spring-boot/docs/current/api/
1.2 Spring Boot 是什么?
1.2.1 第一句话: Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序
1.2.2 第二句话: Spring Boot 直接嵌入 Tomcat、Jetty 或 Undertow ,可以"直接运行" SpringBoot 应用程序
1.3 SpringBoot 快速入门
1.3.1 需求/图解说明
● 构建一个 SpringBoot 项目,浏览器发送/hello 请求 [http://localhost:8080/hello],响应Hello,SpringBoot
1.3.2 完成步骤
确认开发环境是 jdk 8 或以上,maven 在 3.5+
在 springboot2\01_quickstart\pom.xml 引入 SpringBoot 父工程和 web 项目场景启动器
<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>org.example</groupId>
<artifactId>SpringBootQuickStart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootQuickStart</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mysql.version>5.1.49</mysql.version>
</properties>
<!--导入SpringBoot父工程,规定的写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- 导入web项目场景启动器,会自动导入和web开发相关的所有依赖,非常方便-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 创 建 springboot2\01_quickstart\src\main\java\com\hspedu\springboot\ MainApp. 是 javaSpringBoot 应用主程序
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @Author: GQLiu
* @DATE: 2024/4/7 14:45
*/
@SpringBootApplication(scanBasePackages = {"org.example", "a"})
public class MainApp {
// 启动SpringBoot应用程序
public static void main(String[] args) {
// 启动springboot应用程序/项目
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
// 如何查看容器中注入的组件
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
- 运行 MainApp.java 完成测试, 浏览器 http://localhost:8080/hello
1.3.3 快速入门小结
- SpringBoot 比较传统的 SSM 开发, 简化整合步骤, 提高开发效率
- 简化了 Maven 项目的 pom.xml 依赖导入, 可以说是一键导入, 如图.
- 引入一个 spring-boot-starter-web, 到底发生了什么? 一图胜千言
- 内置 Tomcat , 简化服务器的配置
- 当然 SpringBoot 还有很多优势,后面老韩逐步深入讲解
1.4 Spring SpringMVC SpringBoot 的关系
- 他们的关系大概是: Spring Boot > Spring > Spring MVC
- Spring MVC 只是 Spring 处理 WEB 层请求的一个模块/组件, Spring MVC 的基石是Servlet
- Spring 的核心是 IOC 和 AOP, IOC 提供了依赖注入的容器 , AOP 解决了面向切面编程
- Spring Boot 是为了简化开发, 推出的封神框架(约定优于配置[COC],简化了 Spring 项目的配置流程), SpringBoot 包含很多组件/框架,Spring就是最核心的内容之一,也包含 SpringMVC
- Spring 家族,有众多衍生框架和组件例如 boot、security、jpa 等, 他们的基础都是 Spring
1.4.2 如何理解 -约定优于配置
1、约定优于配置(Convention over Configuration/COC),又称按约定编程,是一种软件设计规范, 本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)
2、例如在模型中存在一个名为 User 的类,那么对应到数据库会存在一个名为 user 的表,只有在偏离这个约定时才需要做相关的配置 (例如你想将表名命名为 t_user 等非 user 时才需要写关于这个名字的配置)
3、简单来说就是假如你所期待的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待时, 才需要对约定进行替换配置
4、约定优于配置理念【老韩解读:为什么要搞一个约定优于配置】约定其实就是一种规范,遵循了规范,那么就存在通用性,存在通用性,那么事情就会变得相对简单,程序员之间的沟通成本会降低,工作效率会提升,合作也会变得更加简单
2 依赖管理和自动配置
2.1 依赖管理
2.1.1 什么是依赖管理
- spring-boot-starter-parent 还有父项目, 声明了开发中常用的依赖的版本号
- 并且进行 自动版本仲裁 , 即如果程序员没有指定某个依赖 jar 的版本,则以父项目指定的版本为准
2.1.2 修改自动仲裁/默认版本号
- 需求说明: 将 SpringBoot mysql 驱动修改成 5.1.49
修改版本仲裁:
- 方式1:显示导入的mysql依赖。并明确的指定version
- 方式2:在自己的pom.xml文件中,另用指定mysql的key
都是在pom.xml文件中修改。
方式1
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
方式2
<!-- 方式2
在properties中添加这个,指定key,然后后面的dependency中就不用写这个对应的key了
<properties>
<mysql.version>5.1.49</mysql.version>
</properties>
-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.2 starter 场景启动器
2.2.1 starter 场景启动器基本介绍
- 开发中我们引入了相关场景的 starter,这个场景中所有的相关依赖都引入进来了,比如我们做 web 开发引入了,该 starter 将导入与 web 开发相关的所有包
导入的包如下:
- 依赖树 : 可以看到 spring-boot-starter-web ,帮我们引入了 spring-webmvc,spring-web开发模块,还引入了 spring-boot-starter-tomcat 场景,spring-boot-starter-json 场景,这些场景下面又引入了一大堆相关的包,这些依赖项可以快速启动和运行一个项目,提高开发效率
- 所有场景启动器最基本的依赖就是 spring-boot-starter , 前面的依赖树分析可以看到,这个依赖也就是 SpringBoot 自动配置的核心依赖
2.2.2 官方提供的 starter
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
- 在开发中我们经常会用到 spring-boot-starter-xxx ,比如 spring-boot-starter-web,该场景是用作 web 开发,也就是说 xxx 是某种开发场景。
- 我们只要引入 starter,这个场景的所有常规需要的依赖我们都自动引入。
- SpringBoot2 支 持 的 所 有 场 景 如 下 :
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
2.2.3 第三方 starter
- SpringBoot 也支持第三方 starter
- 第三方 starter 不要从 spring-boot 开始,因为这是官方 spring-boot 保留的命名方式的。第三方启动程序通常以项目名称开头。例如,名为 thirdpartyproject 的第三方启动程序项目通常被命名为 thirdpartyproject-spring-boot-starter。
- 也就是说:xxx-spring-boot-starter 是第三方为我们提供的简化开发的场景启动器
2.3 自动配置
2.3.1 自动配置基本介绍
小伙伴还记得否,前面学习 SSM 整合时,需要配置 Tomcat 、配置 SpringMVC、配置如何扫描包、配置字符过滤器、配置视图解析器、文件上传等[如图],非常麻烦。而在SpringBoot 中,存在自动配置机制,提高开发效率
简单回顾以前 SSM 整合的配置.
2.3.2 SpringBoot 自动配置了哪些?
- 自动配置 Tomcat
2. 自动配置 SpringMVC
- 自动配置 Web 常用功能: 比如字符过滤器, 老韩提示: 通过获取 ioc 容器,查看容器创建 的 组 件 来 验 证 , 修 改
D:\hsp_springboot_temp\01-quickstart\src\main\java\com\hspedu\springboot\MainApp.java
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @Author: GQLiu
* @DATE: 2024/4/7 14:45
*/
@SpringBootApplication(scanBasePackages = {"org.example", "a"})
public class MainApp {
// 启动SpringBoot应用程序
public static void main(String[] args) {
// 启动springboot应用程序/项目
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
// 如何查看容器中注入的组件
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
4. 自 动 配 置 : 默 认 扫 描 包 结 构 !!! , 官 方 文 档
和MyApp.java 在同一个包下(一般是package org.example)的所有配置都会被扫描。但是如果不在同一个包,就会扫描不到。会报错:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jun 24 14:56:23 CST 2019
There was an unexpected error (type=Not Found, status=404).
No message available
2.3.3 如何修改默认配置
- 需求:要求能扫描 com.hspedu 包下的 HiController.java 应该如何处理?
- 创建: springboot2\01_quickstart\src\main\java\com\hspedu\HiController.java, 并测试,这时是访问不到的.
- 修改 MainApp.java, 增加扫描的包, 并完成测试.
@SpringBootApplication(scanBasePackages = {"org.example", "a"})
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @Author: GQLiu
* @DATE: 2024/4/7 14:45
*/
@SpringBootApplication(scanBasePackages = {"org.example", "a"})
public class MainApp {
// 启动SpringBoot应用程序
public static void main(String[] args) {
// 启动springboot应用程序/项目
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
// 如何查看容器中注入的组件
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
2.3.3.2 resources\application.properties 配置大全
- SpringBoot 项目最重要也是最核心的配置文件就是 application.properties,所有的框架配
置都可以在这个配置文件中说明 - 地址:
2.3.3.3 resources\application.properties 修改配置
● 各 种 配 置 都 有 默 认 , 可 以 在 resources\application.properties 修 改 , application.properties 文件我们可以手动创建
# 修改server监听端口8080
server.port=10010
# 修改文件上传的大小
# 这些配置是在哪里读取的?
# 默认配置最终都是映射到某个类。例如multipart.max-file-size 会映射/关联到MyltipartProperties 上。
# 将光标放在该属性上, 输入Ctrl + b 就可以定位这个属性是管理到哪个类的
spring.servlet.multipart.max-file-size=10MB
2.3.3.4 resources\application.properties 常用配置★
#端口号
server.port=10000
#应用的上下文路径(项目路径)
server.servlet.context-path=/allModel
#指定 POJO 扫描包来让 mybatis 自动扫描到自定义的 POJO
mybatis.type-aliases-package=com.cxs.allmodel.model
#指定 mapper.xml 的路径
#(application 上配置了@MapperScan(扫面 mapper 类的路径)和 pom.xml 中放行了 mapper.xml 后,
# 配 置 mapper-locations 没 有 意 义 。 如 果 mapper 类 和 mapper.xml 不 在 同 一 个 路 径 下 时 ,
mapper-locations 就有用了)
mybatis.mapper-locations=classpath:com/cxs/allmodel/mapper
#session 失效时间(单位 s)
spring.session.timeout=18000
#数据库连接配置
#mysql 数据库 url
mysql.one.jdbc-url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai&useSSL=false
#mysql 数据库用户名
mysql.one.username=
#数据库密码
mysql.one.password=
#线程池允许的最大连接数
mysql.one.maximum-pool-size=15
#日志打印:日志级别 trace<debug<info<warn<error<fatal 默认级别为 info,即默认打印 info 及其以
上级别的日志
#logging.level 设置日志级别,后面跟生效的区域,比如 root 表示整个项目,也可以设置为某个包下,
也可以具体到某个类名(日志级别的值不区分大小写)
logging.level.com.cxs.allmodel.=debug
logging.level.com.cxs.allmodel.mapper=debug
logging.level.org.springframework.web=info
logging.level.org.springframework.transaction=info
logging.level.org.apache.ibatis=info
logging.level.org.mybatis=info
logging.level.com.github.pagehelper = info
logging.level.root=info
#日志输出路径
logging.file=/tmp/api/allmodel.log
#配置 pagehelper 分页插件
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
#jackson 时间格式化
spring.jackson.serialization.fail-on-empty-beans=false
#指定日期格式,比如 yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
#指定日期格式化时区,比如 America/Los_Angeles 或者 GMT+10
spring.jackson.time-zone=GMT+8
#设置统一字符集
spring.http.encoding.charset=utf8
#redis 连接配置
# redis 所在主机 ip 地址
spring.redis.host=
#redis 服务器密码
spring.redis.password=
#redis 服务器端口号
spring.redis.port=
#redis 数据库的索引编号(0 到 15)
spring.redis.database=14
## 连接池的最大活动连接数量,使用负值无限制
#spring.redis.pool.max-active=8
#
## 连接池的最大空闲连接数量,使用负值表示无限数量的空闲连接
#spring.redis.pool.max-idle=8
#
## 连接池最大阻塞等待时间,使用负值表示没有限制
#spring.redis.pool.max-wait=-1ms
#
## 最小空闲连接数量,使用正值才有效果
#spring.redis.pool.min-idle=0
#
## 是否启用 SSL 连接. ##spring.redis.ssl=false
#
## 连接超时,毫秒为单位
#spring.redis.timeout= 18000ms
#
## 集群模式下,集群最大转发的数量
#spring.redis.cluster.max-redirects=
#
## 集群模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
#spring.redis.cluster.nodes=
#
## 哨兵模式下,Redis 主服务器地址
#spring.redis.sentinel.master=
#
## 哨兵模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
#spring.redis.sentinel.nodes= 127.0.0.1:5050,127.0.0.1:5060
2.3.3.5 resources\application.properties 自定义配置
● 还可以在 properties 文件中自定义配置,通过@Value(“${}”)获取对应属性值
application.properties 文件
my.website=https://www.baidu.com
某个 Bean:
@Value("${my.website}")
private String bdUrl; // 为bdUrl添加注解。
2.3.4 SpringBoot 在哪配置读取 application.properites (@Deprecated了,不细看)
1、打开 ConfigFileApplicationListener.java , 看一下源码(按住Ctrl + N 然后输入ConfigFileApplicationListener就能打开这个文件。)
2.3.5 自动配置 遵守按需加载原则
- 自动配置遵守按需加载原则:也就是说,引入了哪个场景 starter 就会加载该场景关联的 jar 包,没有引入的 starter 则不会加载其关联 jar。
- SpringBoot 所 有 的 自 动 配 置 功 能 都 在 spring-boot-autoconfigure 包 里 面
- 在 SpringBoot 的 自 动 配 置 包 , 一 般 是 XxxAutoConfiguration.java, 对 应XxxxProperties.java, 如图
过程:
首先XXProperties.java读取配置文件中要修改的配置,如果没有则全部都是默认值。
然后XXProperties.java会作为JavaBean注入XXAutoConfiguration.java中,然后XXAutoConfiguration.java就可以用XXProperties.java中的内容。
3 容器功能
3.1 Spring 注入组件的注解
@Component、@Controller、 @Service、@Repository
说明: 这些在 Spring 中的传统注解仍然有效,通过这些注解可以给容器注入组件
编写JavaBean
package bean;
import org.springframework.stereotype.Repository;
/**
* @Author: GQLiu
* @DATE: 2024/4/29 15:19
*/
@Repository
public class A {
}
- 在 springboot2\01_quickstart\src\main\java\com\hspedu\springboot\MainApp.java 获取, 完成测试
package org.example;
import bean.A;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @Author: GQLiu
* @DATE: 2024/4/7 14:45
*/
@SpringBootApplication(scanBasePackages = {"org.example", "a", "bean"})
public class MainApp {
// 启动SpringBoot应用程序
public static void main(String[] args) {
// 启动springboot应用程序/项目
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
System.out.println("启动springboot成功");
A abean = ioc.getBean(A.class); // 要在自动扫描的包scanBasePackages里设置bean包才能扫描到里面的A
System.out.println("aBean="+abean);
}
}
3.2 @Configuration
:演示在 SpringBoot, 如何通过@Configuration 创建配置类来注入组件
● 回顾传统方式如何通过配置文件注入组件
-
- 创建 springboot2\01_quickstart\src\main\resources\beans.xml
- 创建 springboot2\01_quickstart\src\main\resources\beans.xml
● 使用 SpringBoot 的@Configuration 添加/注入组件
- 创 建springboot2\01_quickstart\src\main\java\com\hspedu\springboot\config\BeanConfig.java
package springboot.example.config;
import org.springframework.context.annotation.Scope;
import springboot.example.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
* 1. @Configuration 标识是一个 配置类,等价于配置文件。
* 2. 程序员可以通过 @Bean 注解注入bean对象到容器
* 3. 当一个类被@Configuration 标识,该类的Bean也会注入容器。
* */
@Configuration
public class BeanConfig {
/*
* 1. @Bean: 表示给容器添加组件,就是Monster bean
* 2. monster01(): 表示你的方法名 默认 作为 Bean的名字/id
* 3. Monster: 注入类型,注入bean的类型是Monster
* 4. new Monster(200, "牛魔王", 500, "疯魔拳"); 为注入的具体信息
* 5. @Bean(name="Monster_nmw") 表示将默认的 Bean的名字/id 更改为 Monster_nmw
* 6. 添加 @Scope("prototype") 表示返回的是多例
* */
@Bean
@Scope("prototype")
public Monster monster01() {
return new Monster(200, "牛魔王", 500, "疯魔拳");
}
}
- 修改 MainApp.java , 从配置文件/容器获取 bean , 并完成测试
3.2.2 @Configuration 注意事项和细节
- 配置类本身也是组件, 因此也可以获取
//配置类本身也是组件, 因此也可以获取
BeanConfig beanConfig = ioc.getBean(BeanConfig.class);
System.out.println("beanConfig= " + beanConfig);
- SpringBoot2 新增特性: proxyBeanMethods 指定 Full 模式 和 Lite 模式
3. 配置类可以有多个, 就和 Spring 可以有多个 ioc 配置文件是一个道理.
即BeanConfig可以有多个。在MainApp.java中都可以获取。
3.3 @Import
演示在 SpringBoot, 如何通过 @Import 来注入组件
// 注意@Import 方式注入的组件, 默认组件的名字就是全类名
@Import({Dog.class, Cat.class})
// @Configuration//标识这是一个配置类: 等价 配置文件
@Configuration(proxyBeanMethods = false)
public class BeanConfig {
}
3.4 @Conditional
- 条件装配:满足 Conditional 指定的条件,则进行组件注入
- @Conditional 是一个根注解,下面有很多扩展注解
按住Ctrl + n, 输入conditional查看所有的条件装配注解:
3.4.2 应用实例
- 要求: 演示在 SpringBoot, 如何通过 @ConditionalOnBean 来注入组件
- 只有在容器中有 name = monster_nmw 组件时,才注入 dog01, 代码如图
@Import({Dog.class, Cat.class})
// @Configuration//标识这是一个配置类: 等价 配置文件
@Configuration(proxyBeanMethods = false)
public class BeanConfig {
/*
@ConditionalOnBean(name = "monster_nmw"):
1. 表示只有容器中注入了 name = monster_nmw 的组件,下面的组件(dog01)才会被注入
2. @ConditionalOnBean(name = "monster_nmw") 也可以放在类名处,则表示对该配置类中所有要注入的组件都进行条件约束
3. 还有很多其它条件约束注解
*/
@ConditionalOnBean(name = "monster_nmw")
@Bean
public Dog dog01() {
return new Dog();
}
}
3.5 @ImportResource
3.5.1 作用:原生配置文件引入, 也就是可以直接导入 Spring 传统的 beans.xml ,可以认为是 SpringBoot 对 Spring 容器文件的兼容.
3.5.2 @ImportResource 应用实例
- 需求: 将 beans.xml 导入到 BeanConfig.java 配置类, 并测试是否可以获得 beans.xml注入/配置的组件
- 修改 BeanConfig.java / 或者创建新的 BeanConfig3.java(建议创建新的配置类) 来测试,使用@ImportResource 导入 beans.xml
package springboot.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
/**
* @Author: GQLiu
* @DATE: 2024/4/30 23:55
*/
@Configuration
@ImportResource(locations = "classpath:beans.xml") // 这里的classpath:就是 SpringBootProjects/SpringBootQuickStart/src/main/resources
// 导入多个时,写法:@ImportResource(locations = {"classpath:beans.xml", "classpath:beans2.xml"})
public class BeanConfig2 {
}
- 在MainApp.java中测试
package springboot.example;
import springboot.example.bean.Monster;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.awt.print.Pageable;
/**
* @Author: GQLiu
* @DATE: 2024/4/7 14:45
* Page 113
*/
@SpringBootApplication()
public class MainApp {
// 启动SpringBoot应用程序
public static void main(String[] args) {
// 启动springboot应用程序/项目
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
// 测试@ImportResource
System.out.println("monster03:" + ioc.getBean("monster03"));
System.out.println(ioc.getBean("monster03"));
}
}
3.6 配置绑定
一句话:使用 Java 读取到 SpringBoot 核心配置文件 application.properties 的内容,并且把它封装到 JavaBean 中
- 需求: 将 application.properties 指定的 k-v 和 JavaBean 绑定
// 在application.properties中配置增加下面的属性
#设置属性 k-v
furn01.id=100
furn01.name=soft_chair!!
furn01.price=45678.9
- 编写JavaBean:E:\JavaCode\SpringBootProjects\SpringBootQuickStart\src\main\java\springboot\example\bean\Furn.java
package springboot.example.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author: GQLiu
* @DATE: 2024/5/1 13:12
*/
/*
* 1. @Component 将Furn注册为一个组件
* 2. @ConfigurationProperties(prefix = "furn01") 指定在application.properties前置,这样Furn组件就会与 属性文件中的值绑定了。
* */
@Component
@ConfigurationProperties(prefix = "furn01")
public class Furn {
private Integer id;
private String name;
private Double price;
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 Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
- 修改 HelloController.java,增加如下代码:
@Autowired
Furn furn;
@RequestMapping("/furn")
@ResponseBody
public Furn furn() {
return furn;
}
- 启动 SpringBoot 主程序,测试
- 配置绑定还有第 2 种方式, 也给小伙伴演示下, 完成测试,效果一样, 注意: 注销@Component 需 要 在 BeanConfig.java( 老 韩 说 明 : 也 可 以 是 其 它 配 置 类 ) 配 置@EnableConfigurationProperties(Furn.class), 否则会提示错误
3.6.3 注意事项和细节
- 如果 application.properties 有中文, 需要转成 unicode 编码写入, 否则出现乱码
soft_chair\u6c99\u53d1!!
- 使用 @ConfigurationProperties(prefix = “furn01”) 会提示如下信息, 但是不会影响使用
5 Lombok
Lombok 作用
- 简化 JavaBean 开发, 可以使用 Lombok 的注解让代码更加简洁
- Java 项目中,很多没有技术含量又必须存在的代码:POJO 的 getter/setter/toString;异常处理;I/O 流的关闭操作等等,这些代码既没有技术含量,又影响着代码的美观,Lombok应运而生
- 常用方法
@ToString:生成toString(), 默认情况下也会生成无参构造器。
@NoArgsConstructor:lombok :注解, 会在编译时生成无参构造器。如果生成了其他构造器,默认的无参构造器就会被覆盖掉。所以还需要构造一个无参构造器。
- 演 示 使 用 Lombok 支 持 日 志 输 出 ( 建 议 使 用 slf4j), 修 改01_quickstart\src\main\java\com\hspedu\springboot\controller\HelloController.java
修改JavaBean Furn.java
修改HelloController.java
@Autowired
Furn furn;
@RequestMapping("/furn")
@ResponseBody
public Furn furn() {
// 普通写法
log.info("furn=" + furn);
// 占位写法
log.info("furn={}", furn);
return furn;
}
打印成功!
6 Spring Initailizr
● Spring Initailizr 作用
- 程序员通过 Maven Archetype 来生成 Maven 项目,项目原型相对简陋, 需要手动配置, 比较灵活.
- 通过 Spring 官方提供的 Spring Initializr 来构建 Maven 项目,能完美支持 IDEA 和 Eclipse,
让程序员来选择需要的开发场景(starter),还能自动生成启动类和单元测试代码。 - Spring Initailizr 对 Idea 版本有要求 同时还要走网络, 我自己还是习惯用, Maven Archetype 来生成 Maven 项目
6.2.1 需求: 使用 Spring Initailizr 创建 SpringBoot 项目,并支持 web 应用场景,支持 MyBatis
7 yaml
1、YAML 是"YAML Ain’t a Markup Language"(YAML 不是一种标记语言) 的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言),是为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名
1、YAML 以数据做为中心,而不是以标记语言为重点
2、YAML 仍然是一种标记语言, 但是和传统的标记语言不一样, 是以数据为中心的标记语言.
3、YAML 非常适合用来做以数据为中心的配置文件 [springboot : application.yml]
7.2 使用文档
7.3 yaml 基本语法
- 形式为 key: value;注意: 后面有空格
- 区分大小写
- 使用缩进表示层级关系
- 缩进不允许使用 tab,只允许空格 [有些地方也识别 tab , 推荐使用空格]
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- 字符串无需加引号
- yml 中, 注释使用 #
7.4 数据类型
7.4.1 字面量
- 字面量:单个的、不可再分的值。date、boolean、string、number、null
- 保存形式为 key: value 如图
7.4.2 对象
- 对象:键值对的集合, 比如 map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3}
monster: {id: 100,name: 牛魔王}
#或换行形式
k:
k1: v1
k2: v2
k3: v3
monster:
id: 100
name: 牛魔王
- 举例说明
7.4.3 数组
- 数组:一组按次序排列的值, 比如 array、list、queue
行内写法: k: [v1,v2,v3]
hobby: [打篮球, 打乒乓球, 踢足球]
#或者换行格式
k:
- v1
- v2
- v3
hobby:
- 打篮球
- 打乒乓球
- 踢足球
- 举例说明
7.5 yaml 应用实例
需求: 使用 yaml 配置文件 和 JavaBean 进行数据绑定, 体会 yaml
- 创建项目 configuration , 完成 yaml 的使用
- 运行效果
7.5.3 代码实现
- 创建一个新的 SpringBoot 项目 - configuration , 老韩使用灵活配置方式创建项目
- 在 pom.xml 引入 lombok, 并切换一下 springboot 版本
<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>org.example</groupId>
<artifactId>configuration</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>configuration</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--导入SpringBoot父工程,规定的写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--导入lombook-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 导入web项目场景启动器,会自动导入和web开发相关的所有依赖,非常方便-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 创建 02_configuration\src\main\java\com\hspedu\springboot\bean\Car.java , 老韩提醒一个小细节: 创建的 bean 需要在 SpringBootApplication 包或者其子包, 否则不会被默认扫描, 同时也不能完全使用 lombok 的相关简化注解.
- 创建 02_configuration\src\main\java\com\hspedu\springboot\bean\Monster.java
package org.example.bean;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @Author: GQLiu
* @DATE: 2024/5/1 20:31
*/
@Component
@Data
@ConfigurationProperties(prefix="monster") // ConfigurationProperties 是关联application.yml中的数据
@ToString
public class Monster {
private Integer id;
private String name;
private Integer age;
private Boolean isMarried;
private Date birth;
private Car car;
private String[] skill;
private List<String> hobby;
private Map<String, Object> wife;
private Set<Double> salaries;
private Map<String, List<Car>> cars;
}
- 创建 resources/application.yml, 演示各种写法
@RestController:
monster:
id: 100
name: 牛魔王
age: 8989
isMarried: true
birth: 2000/12/5
car: {name: 宝马, price: 9999}
# skill: [芭蕉扇, 牛魔拳]
skill:
- 芭蕉扇
- 牛魔拳
hobby: [喝酒, 吃肉]
wife: {key01: 玉面狐狸,key02: 铁扇公主}
salaries:
- 100.1
- 200.2
cars:
grade01:
- { name: 保时捷,price: 999999 }
- name: 法拉利
price: 888888.88
grade02:
- { name: 宝马,price: 200000 }
- name: 奥迪
price: 88888.3
- 创建 com/hspedu/springboot/controller/HiController.java
package org.example.controller;
import org.example.bean.Monster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: GQLiu
* @DATE: 2024/5/1 21:27
*/
@RestController
public class HiController {
@Autowired
Monster monster;
@RequestMapping("/monster")
public Monster getMonster() {
return monster;
}
}
- 启动项目,完成测试
7.6 yaml 使用细节
- 如 果 application.properties 和 application.yml 有 相 同 的 前 缀 值 绑 定 , 则application.properties 优先级高, 开发时,应当避免
- 字符串无需加引号, 这个在前面已经演示了, 如果你用" " 或者 ’ ’ 包起来, 也可以 , 简单举例
- 解决 yaml 配置文件,不提示字段信息问题:在 pom.xml 加 入 spring-boot-configuration-processor 依 赖 , 可 以 从spring-boot-reference.pdf 拷贝(在"E:\BaiduSyncdisk\A研究生\分布式 微服务\分布式 微服务\SpringBoot\资料\spring-boot-reference.pdf")
<!-- 引入 yaml 文件提示, 可以看到 JavaBean 字段提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- 老韩提示: 如果还没有提出提示, 可以安装一个 yaml 插件来搞定
8 WEB 开发-静态资源访问
8.2 基本介绍
- 只要静态资源放在类路径下: /static 、 /public 、 /resources 、 /META-INF/resources 可以被直接访问- 对应文件 WebProperties.java
类路径如何查看:进入WebProperties.java 中查看
- 常见静态资源:JS、CSS 、图片(.jpg .png .gif .bmp .svg)、字体文件(Fonts)等
- 访问方式 :默认: 项目根路径/ + 静态资源名 比如 http://localhost:8080/hi.jpg . - 设置WebMvcProperties.java
private String staticPathPattern = "/**";
8.3 快速入门
- 创建 SpringBoot 项目 springbootweb , 老师使用灵活配置方式来创建项目
- 创建相关静态资源目录, 并放入测试图片, 没有目录,自己创建即可, 完成测试
8.4 静态资源访问注意事项和细节
静态资源访问原理:静态映射是 /**, 也就是对所有请求拦截,请求进来,先看 Controller能不能处理,不能处理的请求交给静态资源处理器,如果静态资源找不到则响应 404 页面
改变静态资源访问前缀,比如我们希望 http://localhost:8080/hspres/* 去请求静态资源,应用场景:静态资源访问前缀和控制器请求路径冲突
- 创建 D:\idea_java_projects\springboot2\03_web\src\main\resources\application.yml
spring: mvc: static-path-pattern: /hspres/** # /hspres 是加的前缀,/**表示拦截所有请求
- 重启应用,完成测试, 浏览器输入: http://localhost:8080/hspres/1.jpg
改变默认的静态资源路径,比如希望在类路径下增加 hspimg 目录 作为静态资源路径 ,并完成测试
2) 配置 springboot2\03_web\src\main\resources\application.yml, 增加路径spring: mvc: static-path-pattern: /hspres/** web: resources: # 修改/指定静态资源的访问路径 static-locations: [classpath:/hspimg/, classpath:/public/, classpath:/static/]
图片的意思是把原来的文件里的路径再加上,否则原来的路径此时再访问就访问不到了。
9 Rest 风格请求处理
- Rest 风格支持(使用 HTTP 请求方式动词来表示对资源的操作)
- 举例说明:
● 请求方式: /monster
● GET-获取怪物
● DELETE-删除怪物
● PUT-修改怪物
● POST-保存妖怪
9.2 SpringBoot Rest 风格应用实例
需求: 演示 SpringBoot 中如何实现 Rest 风格的 增删改查
- 创建 com/hspedu/web/controller/MonsterController.java
package org.example.controller;
import org.springframework.web.bind.annotation.*;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 20:14
*/
@RestController
public class MonsterController {
// @RequestMapping(value = "/monster", method = RequestMethod.GET)
@GetMapping(value = "/monster")
public String getMonster() {
return "GET-查询妖怪";
}
// 等价于
// @RequestMapping(value = "/monster", method = RequestMethod.POST)
@PostMapping("/monster")
public String saveMonster() {
return "POST-添加妖怪";
}
@PutMapping(value = "/monster")
public String putMonster() {
return "PUT-修改妖怪";
}
@DeleteMapping("/monster")
public String deleteMonster() {
return "DELETE-删除妖怪";
}
}
- 使用 Postman 完成测试, 请求 url: http://localhost:8080/monster
Rest 风格请求 -注意事项和细节
1、客户端是 PostMan 可以直接发送 Put、delete 等方式请求,可不设置 Filter
2、如果要 SpringBoot 支持 页面表单的 Rest 功能, 则需要注意如下细节
-----1) Rest 风 格 请 求 核 心 Filter ; HiddenHttpMethodFilter , 表 单 请 求 会 被HiddenHttpMethodFilter 拦截 , 获取到表单 _method 的值, 再判断是 PUT/DELETE/PATCH(老师注释: PATCH 方法是新引入的,是对 PUT 方法的补充,用来对已知资源进行局部更新:https://segmentfault.com/q/1010000005685904)
----2) 如果要 SpringBoot 支持 页面表单的 Rest 功能, 需要在 application.yml 启用 filter 功能,否则无效
----3) 修改 application.yml 启用 filter 功能
spring:
mvc:
static-path-pattern: /hspres/**
hiddenmethod:
filter:
enabled: true #开启页面表单的 Rest 功能
web:
resources:
static-locations: [classpath:/hspimg/, classpath:/public/, classpath:/static/]
----- 4) 修改对应的页面, 同学们自己测即可.
- 创建 D:\hsp_springboot_temp\03-web\src\main\resources\public\rest.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>rest</title>
</head>
<body>
<h1>测试 rest 风格的 url, 来完成请求.</h1>
<form action="/monster" method="post">
u: <input type="text" name="name"><br/>
<!--如果要测试 delete, put , 就打开下面的注释-->
<!-- <input type="hidden" name="_method" value="delete">-->
<input type="submit" value="点击提交">
</form>
</body>
</html>
9.3 思考题
- 留一个思考题: 为什么这里 return “GET-查询妖怪”, 返回的是字符串, 而不是转发到对应的资源文件?-在 SpringMVC
答:因为@ResController 是一个复合注解, 含有@ResponseBody, 所以springboot 底层(springmvc), 在处理return “xxx” 时, 会以@ResponseBody 注解进行解析处理, 即返回Json格式的字符串 “xxx”, 而不会使用视图解析器来处理。如果我们把 @RestController 改成 @Controller , 当你访问getMonster() 时, 如果你有 xxx.html就会转发到 xxx.html , 如果没有 xxx.html , 就会报 404。
提示: 在测试时, 将 xxx.html 放在 main\resources\public\xxx.html 进行测试, 并在application.yml 配置视图解析器
10 接收参数相关注解
10.1 基本介绍
- SpringBoot 接收客户端提交数据/参数会使用到相关注解
- 详 解 @PathVariable 、 @RequestHeader 、 @ModelAttribute 、 @RequestParam 、@MatrixVariable、@CookieValue、@RequestBody
10.2 接收参数相关注解应用实例
需求: 演示各种方式提交数据/参数给服务器,服务器如何使用注解接收
- 创建 03_web\src\main\resources\public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello, 韩顺平教育</h1>
基本注解:
<hr/>
<a href="monster/100/king">@PathVariable-路径变量 monster/100/king</a><br/><br/>
</body>
</html>
- 演示@PathVariable 使用,创建 com/hspedu/web/controller/ParameterController.java ,完成测试
- 演示@RequestHeader 使用,修改 ParameterController.java , 完成测试
@PathVariable 是用于当传入变量名和函数变量名不同时,映射的
@RequestHeader 是用于得到 Http请求头 的
package org.example.controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 21:24
*/
@RestController
public class ParameterController{
/*
/monster/{id}/{name} 解读
1. /monster/{id}/{name} 构成完整请求路径
2. {id} {name} 就是占位变量
3. @PathVariable("name") 这里的name 和{name} 命名保持一致
4. String name_ 表示自定义名字。故意不同以示区别
5. @PathVariable Map<String, String> map 把所有传递的值传入map
*/
@GetMapping("/monster/{id}/{name}")
public String pathVariable(@PathVariable("id") Integer id,
@PathVariable("name") String name_,
@PathVariable Map<String, String> map,
@RequestHeader("Host") String host,
@RequestHeader Map<String, String> header) {
System.out.println("id= " + id + " name= " + name_);
System.out.println(map);
System.out.println("===============================");
System.out.println("host= " + host);
System.out.println(header);
return "success";
}
}
完成测试
IDEA:
演示@RequestHeader 使用,修改 ParameterController.java , 完成测试
@RequestHeader 是用于得到 Http请求头 的
修改ParameterController.java,增加下面代码。
@GetMapping("/requestHeader")
public String requestHeader(
@RequestHeader("Host") String host,
@RequestHeader Map<String, String> header
) {
System.out.println("===============================");
System.out.println("host= " + host);
System.out.println(header);
return "success";
}
- 演示@RequestParam 使用,修改 ParameterController.java , 完成测试
√ 修改 index.html
<a href="hi?name= 韩 顺 平 &fruit=apple&fruit=pear">@RequestParam- 获 取 请 求 参 数</a><br/><br/>
√ 修改 ParameterController.java,增加如下代码:
/**
* 如果 fruit 是多个值, 可以使用 List 来接收
*/
@GetMapping("/hi")
public String hi(
@RequestParam("name") String name,
@RequestParam("fruit") List<String> fruit,
@RequestParam Map<String, String> paras) {
System.out.println("===============================");
System.out.println("name= " + name);
System.out.println(fruit);
System.out.println(paras);
return "success";
}
测试:
点击
得到:
- 演示@CookieValue 使用,修改 ParameterController.java , 完成测试
√ 修改 index.html
<a href="cookie">@CookieValue-获取 cookie 值</a><br/><br/>
√ 修改 ParameterController.java
/**
* 如果要测试,可以先写一个方法,在浏览器创建对应的 cookie
* 说明 1. value = "cookie_key" 表示接收名字为 cookie_key 的 cookie
* 2. 如果浏览器携带来对应的 cookie , 那么 后面的参数是 String ,则接收到的是对应对 value
* 3. 后面的参数是 Cookie ,则接收到的是封装好的对应的 cookie
*/
@GetMapping("/cookie")
public String cookie(
@CookieValue(value = "cookie_key", required = false) String cookie_value,
@CookieValue(value = "username", required = false) Cookie cookie) {
System.out.println("username=" + cookie.getName() + "--" + cookie.getValue());
return "success";
}
- 演示@RequestBody 使用,修改 ParameterController.java , 完成测试
@RequestBody 将客户端提交的 json 数据,封装成 JavaBean 对象
√ 修改 index.html
<hr/>
<h1>测试@RequestBody 获取数据: 获取 POST 请求体</h1>
<form action="/save" method="post">
姓名: <input name="name"/> <br>
年龄: <input name="age"/> <br/>
<input type="submit" value="提交"/>
</form>
√ 修改 ParameterController.java
/**
* @RequestBody 是整体取出 Post 请求内容
*/
@PostMapping("/save")
public String postMethod(@RequestBody String content){
System.out.println("content= " + content);
return "success";
}
测试:
7. 演示@RequestAttribute 使用,创建 com/hspedu/web/controller/RequestController.java ,完成测试
√ 修改 index.html
<a href="login">@RequestAttribute-获取 request 域属性-</a>
√ 创建 RequestController.java
package org.example.controller;
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 22:08
*/
@Controller
public class RequestController {
@GetMapping("/login")
public String login(HttpServletRequest request) {
request.setAttribute("user", "LGQ");
return "forward:/ok"; // 服务器内部转发到ok。对比的是 重定向
}
@ResponseBody
@GetMapping("/ok")
public String ok(@RequestAttribute(value = "user", required = false) String user,
HttpServletRequest request) {
System.out.println("request域中的user=" + user);
System.out.println("request域中的user=" + request.getAttribute("user"));
return "success";
}
}
测试:
10.3 复杂参数
- 在开发中,SpringBoot 在响应客户端请求时,也支持复杂参数
- Map、Model、Errors/BindingResult、RedirectAttributes、ServletResponse、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder、HttpSession
- Map、Model 数据会被放在 request 域, 底层 request.setAttribute()。Model
- RedirectAttributes 重定向携带数据
复杂参数应用实例
- 修改 com/hspedu/web/controller/RequestController.java
package org.example.controller;
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 22:08
*/
@Controller
public class RequestController {
@GetMapping("/login")
public String login(HttpServletRequest request) {
request.setAttribute("user", "LGQ");
return "forward:/ok"; // 转发到ok
}
@ResponseBody
@GetMapping("/ok")
public String ok(@RequestAttribute(value = "user", required = false) String user,
HttpServletRequest request) {
System.out.println("request域中的user=" + user);
System.out.println("request域中的user=" + request.getAttribute("user"));
return "success";
}
@GetMapping("/register")
public String register(Map<String, Object> map,
Model model,
HttpServletResponse response) {
map.put("user", "lgq");
map.put("job", "穷学生");
model.addAttribute("sal", 999999.9);
// 可以将cookie加入到response对象,返回给客户端
Cookie cookie = new Cookie("pwd", "487563");
response.addCookie(cookie);
return "forward:/registerOk";
}
@ResponseBody
@GetMapping("/registerOk")
public String registerOk(HttpServletRequest request) {
System.out.println("request 域中 user= " + request.getAttribute("user"));
System.out.println("request 域中 job= " + request.getAttribute("job"));
System.out.println("request 域中 sal= " + request.getAttribute("sal"));
return "success";
}
}
- 完成测试 : 浏览器 http://localhost:8080/register
register方法中为什么用HttpServletResponse,registerOk方法中为什么用HttpServletRequest?
HttpServletResponse 用于修改到客户端的响应。对其进行修改,最终呈现的效果都是在客户端上。
HttpServletRequest修改的是请求,这个请求是到服务器(Tomcat)的请求。这里是在register方法中添加一些属性(通过Map和Model),然后在registerOk方法中取出属性。
10.4 自定义对象参数-自动封装
- 在开发中,SpringBoot 在响应客户端请求时,也支持自定义对象参数
- 完成自动类型转换与格式化
- 支持级联封装
10.4.2 自定义对象参数-应用实例
: 演示自定义对象参数使用,完成自动封装,类型转换
- 创建 public/save.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加妖怪</title>
</head>
<body>
<h1>添加妖怪-坐骑[测试封装 POJO;]</h1>
<form action="/savemonster" method="post">
编号: <input name="id" value="100"><br/>
姓名: <input name="name" value="牛魔王"/> <br/>
年龄: <input name="age" value="120"/> <br/>
婚否: <input name="isMarried" value="true"/> <br/>
生日: <input name="birth" value="2000/11/11"/> <br/>
坐骑:<input name="car.name" value="法拉利"/><br/>
价格:<input name="car.price" value="99999.9"/>
<input type="submit" value="保存"/>
</form>
</body>
</html>
- 创 建 03_web\src\main\java\com\hspedu\web\bean\Car.java 和03_web\src\main\java\com\hspedu\web\bean\Monster.java
package org.example.bean;
import lombok.Data;
import lombok.ToString;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 22:44
*/
@ToString
@Data
public class Car {
private String name;
private Double price;
}
package org.example.bean;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 20:15
*/
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component
@Data
@ConfigurationProperties(prefix="monster") // ConfigurationProperties 是关联application.yml中的数据
@ToString
public class Monster {
private Integer id;
private String name;
private Integer age;
private Boolean isMarried;
private Date birth;
private Car car;
// private String[] skill;
// private List<String> hobby;
// private Map<String, Object> wife;
// private Set<Double> salaries;
}
- 修改 com/hspedu/web/controller/ParameterController.java 增加处理添加请求
@PostMapping("/savemonster")
public String saveMonster(Monster monster) {
System.out.println("monster=" + monster);
return "success";
}
- 完成测试, 浏览器 http://localhost:8080/save.html
11 自定义转换器
11.1 基本介绍
- SpringBoot 在响应客户端请求时,将提交的数据封装成对象时,使用了内置的转换器
- SpringBoot 也支持自定义转换器, 这个内置转换器在 debug 的时候, 可以看到, 后面给小伙伴演示, 提供了 124 个内置转换器. 看下源码 GenericConverter-ConvertiblePair
11.2 自定义转换器-应用实例
1 需求说明 : 演示自定义转换器使用
- 修改 save.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加妖怪</title>
</head>
<body>
<h1>添加妖怪-坐骑[测试封装 POJO;]</h1>
<form action="/savemonster" method="post">
编号: <input name="id" value="100"><br/>
姓名: <input name="name" value="牛魔王"/> <br/>
年龄: <input name="age" value="120"/> <br/>
婚否: <input name="isMarried" value="true"/> <br/>
生日: <input name="birth" value="2000/11/11"/> <br/>
<!-- 坐骑:<input name="car.name" value="法拉利"/><br/>-->
<!-- 价格:<input name="car.price" value="99999.9"/>-->
<!-- 使用自定义转换器关联 car, 字符串整体提交 -->
坐骑: <input name="car" value="保时捷,66666.6">
<input type="submit" value="保存"/>
</form>
</body>
</html>
- 创 建D:\hsp_springboot_temp\03-web\src\main\java\com\hspedu\web\config\WebConfig.java,增加自定义转换器-
过,没学
13 内容协商
- 根据客户端接收能力不同,SpringBoot 返回不同媒体类型的数据
- 比如: 客户端 Http 请求 Accept: application/xml 则返回 xml 数据,客户端 Http 请求Accept: application/json 则返回 json 数据
13.2 内容协商-应用实例
● 需求说明 : 使用Postman发送Http请求,根据请求头不同,返回对应的json数据 或者 xml 数据 , 如图
- 在 pom.xml 增加处理 xml 的依赖
<!--导入支持返回xml数据格式-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
使用Postman发出不同的Http Header , 可以看到返回对应的数据格式
使用浏览器请求,为什么会返回 xml 数据分析,而不是 json?
13.3 注意事项和使用细节
- Postman 可以通过修改 Accept 的值,来返回不同的数据格式
- 对于浏览器,我们无法修改其 Accept 的值,怎么办? 解决方案: 开启支持基于请求参数的内容协商功能
- 修改 application.yml, 开启基于请求参数的内容协商功能
spring: mvc: hiddenmethod: filter: enabled: true # 开启页面表单的Rest功能。 contentnegotiation: favor-parameter: true # 开启基于请求参数的内容协商功能 web: resources: static-locations: [classpath:/public/, classpath:/static/]
- 浏览器访问:
http://localhost:8080/monster?format=xml
json格式:
- 注意,参数 format 是规定好的 , 在开启请求参数的内容协商功能后,SpringBoot 底层ParameterContentNegotiationStrategy 会通过 format 来接收参数,然后返回对应的媒体类型/数据格式 , 当然 format=xx 这个 xx 媒体类型/数据格式 是 SpringBoot 可以处理的才行,不能乱写.
14 Thymeleaf(感觉过时 ) 在复习看这里
基本介绍
- Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,可完全替代 JSP
- Thymeleaf 是一个 java 类库,他是一个 xml/xhtml/html5 的模板引擎,可以作为 mvc 的 web 应用的 view 层
● Thymeleaf 的优点
- 实现 JSTL、 OGNL 表达式效果, 语法相似, java 程序员上手快
- Thymeleaf 模版页面无需服务器渲染,也可以被浏览器运行,页面简洁。
- SpringBoot 支持 FreeMarker、Thymeleaf、veocity 。
● Thymeleaf 的缺点
- 缺点: 并不是一个高性能的引擎,适用于单体应用
- 老韩说明:如果要做一个高并发的应用, 选择前后端分离更好,但是作为 SpringBoot 推荐的模板引擎,老师还是要讲解 Thymeleaf 使用, 这样小伙伴在工作中使用到, 也能搞定
- 后面老韩还要讲 Vue + ElementPlus + Axios + SpringBoot 前后端分离
14.3 Thymeleaf 机制说明
- Thymeleaf 是服务器渲染技术, 页面数据是在服务端进行渲染的
- 比如: manage.html 中一段 thymeleaf 代码, 是在用户请求该页面时,有 thymeleaf 模板引擎完成处理的(在服务端完成), 并将结果页面返回.
- 因此使用了 Thymeleaf , 并不是前后端分离.
14.4 Thymeleaf 语法
14.4.1 表达式
2. 字面量
文本值: ‘hsp edu’ , ‘hello’ ,…数字: 10 , 7 , 36.8 , …布尔值: true , false
空值: null
变量: name,age,… 变量不能有空格
3. 文本操作
文本操作
字符串拼接: +
变量替换: |age= ${age}|
14.4.2 运算符
- 数学运算:运算符: + , - , * , / , %
- 布尔运算
运算符: and , or
一元运算: ! , not - 比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne ) - 条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
14.4.3 th 属性
html 有的属性,Thymeleaf 基本都有,而常用的属性大概有七八个。其中 th 属性执行的优先级从 1~8,数字越低优先级越高
● th:text :设置当前元素的文本内容,相同功能的还有 th:utext,两者的区别在于前者不会转义 html 标签,后者会。优先级不高:order=7
● th:value:设置当前元素的 value 值,类似修改指定属性的还有 th:src,th:href。优先级不高:order=6
● th:each:遍历循环元素,和 th:text 或 th:value 一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
● th:if:条件判断,类似的还有 th:unless,th:switch,th:case。优先级较高:order=3
● th:insert:代码块引入,类似的还有 th:replace,th:include,三者的区别较大,若使用不恰当会破坏 html 结构,常用于公共代码块提取的场景。优先级最高:order=1
● th:fragment:定义代码块,方便被 th:insert 引用。优先级最低:order=8
● th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
● th:attr:修改任意属性,实际开发中用的较少,因为有丰富的其他 th 属性帮忙,类似的还有 th:attrappend,th:attrprepend。优先级一般:order=5
14.5 Thymeleaf 综合案例
思路分析
- 创建项目, 项目名使用 04-springboot-usersys, 老韩还是使用灵活创建项目的方式.
- 说明一下,要支持 Thymeleaf, 需要加入 thymeleaf-starter, 在 pom.xml 配置
<!--导入thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 创建 adminLogin.html 和 manage.html 和静态图片到指定目录,从准备好的拷贝即可, 注意
我将 html 文件放到 templates/ 目录下, 该目录, 不能直接访问.
adminLogin.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>用户登陆</h1>
<form action="#" method="post" th:action="@{/login}"
>
<label style="color: red" th:text="${msg}"></label><br/>
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
密 码:<input type="password" style="width:150px" name="password"/><br/><br/>
<input type="submit" value="登录"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
==============manage.html========================
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理后台</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<a href='#'>返回管理界面</a> <a href='#' th:href="@{/}">安全退出</a> 欢迎您:[[${session.loginAdmin.name}]]
<hr/>
<div style="text-align: center">
<h1>管理雇员~</h1>
<table border="1px" cellspacing="0" bordercolor="green" style="width:800px;margin: auto">
<tr bgcolor="pink">
<td>id</td>
<td>name</td>
<td>pwd</td>
<td>email</td>
<td>age</td>
</tr>
<tr bgcolor="#ffc0cb" th:each="user:${users}">
<td th:text="${user.id}">a</td>
<td th:text="${user.name}">b</td>
<td th:text="${user.password}">c</td>
<td th:text="${user.email}">d</td>
<td th:text="${user.age}">e</td>
</tr>
</table>
<br/>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
- 创建 com/hspedu/usersys/bean/Admin.java
package org.example.bean;
import lombok.Data;
/**
* @author 韩顺平
* @version 1.0
*/
@Data
public class Admin {
private String name;
private String password;
}
- 创建 com/hspedu/usersys/bean/User.java
package org.example.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 韩顺平
* @version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
private String email;
}
- 创建 com/hspedu/usersys/controller/IndexController.java 默认进入登录页面
package org.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @Author: GQLiu
* @DATE: 2024/5/4 19:39
* 这是默认登陆界面。由于不能直接访问thymeleaf下的login.html文件,所以写这一个controller,然后经过请求转发到thymeleaf下。
*/
@Controller
public class IndexController {
// 编写方法,请求转发 到登陆页面
@GetMapping(value = {"/", "/login"})
public String login() {
/*
* 因为这里引入了 starter-thymeleaf
* 这里会直接使用视图解析器到 thymeleaf 下的模板文件adminLogin.html
* 直接访问templates下的文件不能直接访问到。
* */
return "adminLogin";
}
}
- 创建 com/hspedu/usersys/controller/AdminController.java 处理登录请求 完成测试
package org.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.example.bean.Admin;
import org.example.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.jws.WebParam;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
/**
* @Author: GQLiu
* @DATE: 2024/5/4 19:47
* 处理登陆请求
*/
@Controller
@Slf4j
public class AdminController {
// 响应用户的登陆请求
@PostMapping("/login") // 登陆的请求会以post方式打到这里
public String login(Admin admin, HttpSession httpSession, Model model) {
// 验证用户登录是否合法
if(StringUtils.hasText(admin.getName()) && "666".equals(admin.getPassword())) {
// 将登陆用户保存到session
httpSession.setAttribute("loginAdmin", admin);
// 重定向到mamage.html, 不使用请求转发是防止用户刷新页面会重复提交
// return "forward:/manage.html";
return "redirect:/manage.html";
} else {
// 不合法,就重新登陆。
model.addAttribute("msg", "账号/用户错误");
return "adminLogin";
}
}
// 处理用户 的请求 manage.html
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession httpSession) {
//这里老师暂时使用在方法验证,后面我们统一使用拦截器来验证
log.info("进入mainPage()");
//可以这里集合-模拟用户数据, 放入到request域中,并显示
ArrayList<User> users = new ArrayList<>();
users.add(new User(1, "关羽~", "666666", 20, "gy@sohu.com"));
users.add(new User(2, "张飞", "666666", 30, "zf@sohu.com"));
users.add(new User(3, "赵云", "666666", 22, "zy@sohu.com"));
users.add(new User(4, "马超", "666666", 28, "mc@sohu.com"));
users.add(new User(5, "黄忠", "666666", 50, "hz@sohu.com"));
//将数据放入到request域
model.addAttribute("users", users);
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
}
}
//浏览器输入: http://localhost:8080/login
15 拦截器-HandlerInterceptor
15.1 基本介绍
- 在 Spring Boot 项目中, 拦截器是开发中常用手段,要来做登陆验证、性能检查、日志记录等。
- 基本步骤:
√ 编写一个拦截器实现 HandlerInterceptor 接口
√ 拦截器注册到配置类中(实现 WebMvcConfigurer 的 addInterceptors)
√ 指定拦截规则
√ 回顾 SpringMVC 中讲解的 Interceptor
- 创建 com/hspedu/usersys/interceptor/LoginInterceptor.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author 韩顺平
* @version 1.0
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle 拦截的请求路径是{}", requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginAdmin = session.getAttribute("loginAdmin");
if (loginAdmin != null) {
//放行
return true;
}
//拦截
request.setAttribute("msg", "错误/重新登录");
request.getRequestDispatcher("/").forward(request, response);
return false;
}
/**
* 目标方法执行完成以后*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle 执行");
}
/**
* 页面渲染以后*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {
log.info("afterCompletion 执行");
}
}
- 创建 com/hspedu/usersys/config/WebConfig.java
import com.hspedu.usersys.interceptor.LoginInterceptor;
// import org.apache.catalina.connector.Connector;
// import org.apache.coyote.http11.Http11NioProtocol;
// import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
// import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 这里实现 Spring-Boot 定制功能, 加入自己的配置
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/", "/login", "/images/**"); //放行的请求, 可以根据需要增加
}
}
15.2.3 注意事项和细节
1、URI 和 URL 的区别
URI = Universal Resource Identifier
URL = Universal Resource Locator
Identifier:标识符,Locator:定位器 从字面上来看, URI 可以唯一标识一个资源, URL 可以提供找到该资源的路径
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
2、注册拦截器, 依然可以使用如下方式二:
@Configuration
public class WebConfig /*implements WebMvcConfigurer*/ {
//将我们的拦截器, 注入到容器中
//@Override
//public void addInterceptors(InterceptorRegistry registry) {
// System.out.println("addInterceptors...");
// //加入我们的拦截器
// registry.addInterceptor(new LoginInterceptor())
// .addPathPatterns("/**") //拦截所有请求
// .excludePathPatterns("/","/login","/images/**");//
//}
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("addInterceptors...~~~~:):)");
//加入我们的拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/", "/login", "/images/**");//
}
};
}
}
16 文件上传
16.1 应用实例
● 需求: 演示 Spring-Boot 通过表单注册用户,并支持上传图片
● 代码实现-文件上传
- 创建 SpringBootProjects/springbootweb/src/main/resources/public/upload.html , 要求头像只能选择一个, 而宠物可以上传多个图片
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>注册用户~</h1>
<form action="#" method="post" th:action="@{/upload}" enctype="multipart/form-data">
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
电 邮:<input type="text" style="width:150px" name="email"/><br/><br/>
年 龄:<input type="text" style="width:150px" name="age"/><br/><br/>
职 位:<input type="text" style="width:150px" name="job"/><br/><br/>
头 像:<input type="file" style="width:150px" name="header"><br/><br/>
宠 物:<input type="file" style="width:150px" name="photos" multiple><br/><br/>
<input type="submit" value="注册"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
- 编写UploadController.java。
package org.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* @Author: GQLiu
* @DATE: 2024/5/3 15:02
*/
@Controller
@Slf4j
public class UploadController {
// @GetMapping("/upload.html")
// public String uploadPage() {
// System.out.println("进入upload.html");
// return "upload";
// }
@ResponseBody
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("age") Integer age,
@RequestParam("job") String job,
@RequestPart("header") MultipartFile header,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},name={},age={}, job={}, header={},photos={}", email, name, age, job, header.getSize(), photos.length);
// // 自己动态创建文件夹来存放文件。例如在 resources/static/images/upload
// String path = ResourceUtils.getURL("classpath:").getPath();
// File file = new File(path + "static/images/upload/");
// if(!file.exists()) file.mkdirs();
if(!header.isEmpty()) {
// 保存到文件服务器或OSS服务器/需要先创建好目录f:\\temp_upload
String originalFilename = header.getOriginalFilename();
// 方式 1: 指定某个目录存放上传文件
header.transferTo(new File("f:\\temp_upload\\" + originalFilename));
// // 方式 2: 动态创建目录存放文件
// header.transferTo(new File(file.getAbsolutePath() + "/" + originalFilename));
}
if(photos.length > 0) {
for (MultipartFile photo : photos) {
if(!photo.isEmpty()) {
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("f:\\temp_upload\\" + originalFilename));
// //动态创建文件夹
// photo.transferTo(new File(file.getAbsolutePath() + "/" + originalFilename));
}
}
}
return "上传成功~";
}
}
- 在浏览器中输入http://localhost:8080/upload 访问,输入数据
后台即可得到对应的输入的内容:
同时在磁盘中得到对应上传的文件
拓展
1、解决文件覆盖问题, 如果文件名相同, 会出现覆盖问题, 如何解决
2、解决文件分目录存放问题, 如果将文件都上传到一个目录下,当上传文件很多时,会造成访问文件速度变慢,因此 可以将文件上传到不同目录 比如 一天上传的文件,统一放到一个文件夹 年/月/日, 比如 2022/11/11 目录
17 异常处理
- 默认情况下,Spring Boot 提供/error 处理所有错误的映射
- 对于机器客户端,它将生成 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信息。对于浏览器客户端,响应一个"whitelabel"错误视图,以 HTML 格式呈现相同的数据
17.2 拦截器 VS 过滤器
1、使用范围不同
- 过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,Filter 只能在 web 程序中使用
- 拦截器(Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application 等程序中
2、过滤器 和 拦截器的触发时机也不同,看下边这张图
首先经过猫猫,然后再经过过滤器。如果过滤器通过,就再进入servlet。如果再通过,就进入拦截器。如果再通过,则进入最终的处理的controller。如果controller返回成功,就返回最终结果。如果返回不成功,就去走自定义异常或异常的那条线。
- 过滤器 Filter 是在请求进入容器后, 但在进入 servlet 之前进行预处理, 请求结束是在servlet 处理完以后
- 拦截器 Interceptor 是在请求进入 servlet 后, 在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束
- 说明: 过滤器不会处理请求转发, 拦截器会处理请求转发
- 至于过滤器和拦截器的原理和机制, 老韩已经详细讲解过了, 过滤器在 JavaWeb 讲过, 拦
截器在 SpringMVC 讲过,
17.3 自定义异常页面
17.3.1 文 档 :
17.3.2 自定义异常页面说明
如何找到这个文档位置, 看下面一步步的指引
https://docs.spring.io/spring-boot/docs/current/reference/html/index.html => a single page html => 8.web => servlet web application => The “Spring Web MVC Framework” => Error Handling => Custom Error Pages
17.3.3 自定义异常页面-应用实例
需求: 自定义 404.html 500.html 4xx.html 5xx.html 当发生相应错误时,显示自定义的页面信息
- 创建 4 个页面, 这几个页面拷贝即可。随便自己怎么写里面的东西都可以。
- 创建 com/hspedu/usersys/controller/MyErrorController.java , 用于模拟错误
package org.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @Author: GQLiu
* @DATE: 2024/5/3 21:57
*/
@Controller
public class MyErrorController {
// 模拟一个服务器内部错误 500
@GetMapping(value = {"/err"})
public String err() {
int i = 10 / 0;
return "manage";
}
// 用Get请求err2(err2要以Post方式请求)
@PostMapping(value = {"/err2"})
public String err2() {
return "manage";
}
}
- 完成测试
● 需要先登录,再进行测试,否则会被拦截器打回登录页面
● 对于 /err2 , 使用 get 方式去请求,就会生成 400 错误, 可以看到 4xx.html
17.4 全局异常
说明
- @ControllerAdvice+@ExceptionHandler 处理全局异常
- 底层是 ExceptionHandlerExceptionResolver 支持的
- 全局异常是处理Java内部的错误导致的服务器的错误,当这些错误发生时,指定到哪些页面。
全局异常-应用实例
需求: 演示全局异常使用, 当发生 ArithmeticException、NullPointerException 时,不使用默认异常机制匹配的 xxx.html , 而是显示全局异常机制指定的错误页面
- 创建 com/hspedu/usersys/exception/GlobalExceptionHandler.java
package org.example.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.jws.WebParam;
/**
* @Author: GQLiu
* @DATE: 2024/5/5 10:54
*/
@Slf4j
// @ControllerAdvice
public class GlobalExceptionHandler {
// 可以处理多个异常
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String HandleAriException(Exception e ,Model model){
log.error("异常信息为{}", e);
model.addAttribute("msg", e);
return "/error/global";
}
}
- 创建 templates/error/global.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>全局异常</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>全局异常/错误 发生了:)</h1><br/>
异常/错误信息: <h1 th:text="${msg}"></h1><br/>
<a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
17.5 自定义异常
- 如果 Spring Boot 提供的异常不能满足开发需求,程序员也可以自定义异常.
- @ResponseStatus+自定义异常
- 底层是 ResponseStatusExceptionResolver ,底层调用 response.sendError(statusCode,resolvedReason);
- 当抛出自定义异常后,仍然会根据状态码,去匹配使用 x.html 显示
需求:自定义一个异常 AccessException, 当用户访问某个无权访问的路径时,抛出该异常,显示自定义异常状态码
- 创建 com/hspedu/usersys/exception/AccessException.java
package org.example.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @Author: GQLiu
* @DATE: 2024/5/5 15:33
* 自定义一个异常 可以继承Exception 或 RuntimeeeException
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException{
public AccessException() {
}
public AccessException(String message) {
super(message);
}
}
- 修改 MyErrorController.java,添加err3
@GetMapping("/err3")
public String err3(String name) {
if(!"tom".equals(name)) {
throw new AccessException();
}
// return "redirect:/manage.html"; // 可以拿到数据
return "forward:/manage.html"; // 可以拿到数据
// return "manage.html"; // 默认是请求转发(?) 拿不到数据
}
- 完成测试, 浏览器 http://localhost:8080/err3
只要输入的name不是tom,就会报错。输入的是name,就会重定向到manage.html。
18 注入 Servlet、Filter、Listener
18.1.1 文 档 :
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-web-applications.embedded-container.servlets-filters-listeners
18.2 基本介绍
- 考虑到实际开发业务非常复杂和兼容,Spring-Boot 支持将 Servlet、Filter、Listener 注入Spring 容器, 成为 Spring bean
- 也就是说明 Spring-Boot 开放了和原生 WEB 组件(Servlet、Filter、Listener)的兼容
18.3 应用实例 1-使用注解方式注入
需求 : 演示通过注解方式注入 Servlet、Filter、Listener
- 创建 com/hspedu/usersys/servlet/Servlet_.java
package org.example.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: GQLiu
* @DATE: 2024/5/5 19:51
*/
/*
* @WebServlet 表示要注入该 Servlet
* urlPatterns = {"/servlet01", "/servlet02"} 表示映射的路径
* 注意注入的原生Servlet 不会被Springboot拦截器拦截。
* */
@WebServlet(urlPatterns = {"/servlet01", "/servlet02"})
public class Servlet_ extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello, servlet");
}
}
- 修改 com/hspedu/usersys/MainAPP.java , 加入@ServletComponentScan
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @Author: GQLiu
* @DATE: 2024/5/2 20:23
*/
@SpringBootApplication()
// 要求扫描 org.example 包下的原生方式注入的servlet。
@ServletComponentScan(basePackages = {"org.example"})
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
}
}
- 完成测试, 浏览器 http://localhost:8080/servlet01
4. 创建 com/hspedu/usersys/servlet/Filter_.java
package org.example.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Author: GQLiu
* @DATE: 2024/5/5 20:02
*/
/*
* {"/css/*", "/images/*"} 表示访问这些路径时,才会用过滤器进行过滤
* 1. @WebFilter 表示 Filter_ 是一个过滤器,并注入容器
* 2. urlPatterns = {"/css/*", "/images/*"} 表示当请求 /css/目录下的资源或images/目录下的资源时,需要经过该过滤器。(不过这里过滤器直接放行了)
* 3. 经过过滤器(这里是直接放行)后,拦截器是否拦截要根据拦截器的拦截规则确定
* */
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
@Slf4j
public class Filter_ implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("=========Filter init() ==================");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("=========Filter doFilter() ==================");
// 为了方便观察过滤器处理的资源,我们输出一个uri
// 由于 ServletRequest类型的request没有getRequestURI(), 所以需要将其类型转为HttpServletRequest
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String requestURI = httpServletRequest.getRequestURI();
StringBuffer requestURL = httpServletRequest.getRequestURL();
System.out.println("请求的资源的URI=" + requestURI);
System.out.println("请求的资源的URL=" + requestURL);
filterChain.doFilter(servletRequest, servletResponse); // 过滤器直接放行。 doFilter 表示继续走后面
}
@Override
public void destroy() {
log.info("=========Filter destroy().==================");
}
}
- 创建 static/css/t.css, 作为测试文件
- 完成测试 , 注意观察后台, 浏览器 : http://localhost:8080/css/t.css
注意: 过滤器配置的 urlPatterns 也会经过 Spring-Boot 拦截器(根据拦截器的规则)所以为了看到效果,请在拦截器配置放行 /css/**
在 servlet 匹配全部是
/*
, 在 Spring-Boot 是/**
访问:http://localhost:8080/css/t.css 流程分析:
首先浏览器输入http://localhost:8080/css/t.css,将请求达到tomcat。然后tomcat 接收到这个请求,首先经过过滤器。由于过滤器是直接放行,所以没啥过滤的操作。然后经过servlet,经过拦截器。此时可以配置拦截器直接放行,如果不放行,就按照拦截器的逻辑,必须先登录,登陆后保留用户信息的session,才能访问t.css。
7. 创建 com/hspedu/usersys/servlet/Listener_.java
package org.example.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Author: GQLiu
* @DATE: 2024/5/5 20:02
*/
/*
* {"/css/*", "/images/*"} 表示访问这些路径时,才会用过滤器进行过滤
* 1. @WebFilter 表示 Filter_ 是一个过滤器,并注入容器
* 2. urlPatterns = {"/css/*", "/images/*"} 表示当请求 /css/目录下的资源或images/目录下的资源时,需要经过该过滤器。(不过这里过滤器直接放行了)
* 3. 经过过滤器(这里是直接放行)后,拦截器是否拦截要根据拦截器的拦截规则确定
* */
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
@Slf4j
public class Filter_ implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("=========Filter init() ==================");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("=========Filter doFilter() ==================");
// 为了方便观察过滤器处理的资源,我们输出一个uri
// 由于 ServletRequest类型的request没有getRequestURI(), 所以需要将其类型转为HttpServletRequest
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String requestURI = httpServletRequest.getRequestURI();
StringBuffer requestURL = httpServletRequest.getRequestURL();
System.out.println("请求的资源的URI=" + requestURI);
System.out.println("请求的资源的URL=" + requestURL);
filterChain.doFilter(servletRequest, servletResponse); // 过滤器直接放行。 doFilter 表示继续走后面
}
@Override
public void destroy() {
log.info("=========Filter destroy().==================");
}
}
- 完成测试 , 启动项目,观察后台输出
18.4 应用实例 2-使用 RegistrationBean 方式注入
演示使用 RegistrationBean 注入 Servlet、Filter、Listener
- 创建 com/hspedu/usersys/config/RegisterConfig_.java
package org.example.config;
import org.example.servlet.Filter_;
import org.example.servlet.Listener_;
import org.example.servlet.Servlet_;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @Author: GQLiu
* @DATE: 2024/5/6 9:01
* @Configuration 表示当前的 RegisterConfig_ 是一个配置类
*/
@Configuration
public class RegisterConfig_ {
// 以RegistrationBean方式注入Servlet
@Bean
public ServletRegistrationBean Servlet_() {
Servlet_ servlet = new Servlet_();
return new ServletRegistrationBean(servlet, "/servlet01", "/servlet02");
}
// 以RegistrationBean方式注入Filter
@Bean
public FilterRegistrationBean Filter_() {
Filter_ filter = new Filter_();
FilterRegistrationBean<Filter_> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/images/*", "/css/*"));
return filterFilterRegistrationBean;
}
// 以RegistrationBean方式注入Listener
@Bean
public ServletListenerRegistrationBean Listener() {
Listener_ listener = new Listener_();
return new ServletListenerRegistrationBean(listener);
}
}
- 去掉相关的注解,再次完成测试(全部注释掉
18.5 注意事项和细节说明
请求 Servlet 时,为什么不会到达拦截器
原因分析:
√ 注入的 Servlet 会存在 Spring 容器
√ DispatherServlet 也存在 Spring 容器
3. 大家回忆一下我们讲过的 Tomcat 在对 Servlet url 匹配的原则, 多个 servlet 都能处理到同一层路径, 精确优先原则/最长前缀匹配原则.
4. 在 SpringBoot 中, 去调用@Controller 目标方法 是按照 DispatherServlet 分发匹配的机
制,
19 内置 Tomcat 配置和切换
- SpringBoot 支持的 webServer: Tomcat, Jetty, or Undertow
- SpringBoot 应用启动是 Web 应用时。web 场景包-导入 tomcat
- 支持对 Tomcat(也可以是 Jetty 、Undertow)的配置和切换
19.2 内置 Tomcat 的配置
19.2.1 通过 application.yml 完成配置
# 内置tomcat的配置(通过配置文件)
server:
# 端口
port: 9999
tomcat:
threads:
# 最大工作线程数
max: 10
# 最小工作线程数
min-spare: 5
# tomcat 启动的线程数达到最大时,接受排队的请求个数,默认值为 100
accept-count: 200
# 最大连接数
max-connections: 2000
# 还有其它就不一一列举
19.2.2 通过类来配置 Tomcat
- 通过类来配置 Tomcat(说明: 配置文件可配置的更全.),
package org.example.config;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
/**
* @Author: GQLiu
* @DATE: 2024/5/6 10:14
*/
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
// factory 就是 server
factory.setPort(9999);
}
}
19.3 切换 WebServer, 演示如何切换成 Undertow
- 修改 pom.xml , 排除 tomcat , 加入 Undertow 包的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 引入 spring-boot-starter-web 排除 tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
- 老韩说明: 因为去掉了 tomcat 的依赖,所以项目有使用到 tomcat 相关类/接口,就会报错,注销/删除这部分代码即可 , 运行项目,完成测试
20 数据库操作
20.1 JDBC+HikariDataSource
演示 Spring Boot 如何通过 jdbc+HikariDataSource 完成对 Mysql 操作
说明: HikariDataSource : 目前市面上非常优秀的数据源, 是 springboot2 默认数据源
- 创建测试数据库和表
-- 创建 furns_ssm
DROP DATABASE IF EXISTS spring_boot;
CREATE DATABASE spring_boot;
USE spring_boot; -- 创建家居表
CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT, ## id
`name` VARCHAR(64) NOT NULL, ## 家居名
`maker` VARCHAR(64) NOT NULL, ## 厂商
`price` DECIMAL(11,2) NOT NULL, ## 价格
`sales` INT(11) NOT NULL, ## 销量
`stock` INT(11) NOT NULL, ## 库存
`img_path` VARCHAR(256) NOT NULL ## 照片路径
);
-- 初始化家居数据
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`) VALUES (NULL , ' 北 欧 风 格 小 桌 子 ' , ' 熊 猫 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/1.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 简 约 风 格 小 椅 子 ' , ' 熊 猫 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/2.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 典 雅 风 格 小 台 灯 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/3.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 温 馨 风 格 盆 景 架 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/4.jpg');
SELECT * FROM furn;
- 进 行 数 据 库 开 发 , 在 pom.xml 引 入 data-jdbc starter
<!--1. 进行数据库开发,引入 data-jdbc starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
3. Spring Boot 不知道项目要操作 Mysql 还是 Oracle , 需要在 pom.xml 指定导入数据库驱动, 并指定对应版本.
<!-- 1. 引入操作 mysql 的驱动
2. 这个驱动版本要和你实际操作的 mysql 版本对应
3. 我们 spring-boot mysql 区别仲裁版本是<mysql.version>8.0.26</mysql.version>
4. 这个 mysql 驱动版本也可以在 pom.xml properties 指定
<properties>
<mysql.version>8.1.0</mysql.version>
</properties>
-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
- 在 application.yml 配置操作数据源的信息
spring:
servlet:
multipart:
max-file-size: 5MB
max-request-size: 50MB
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML
web:
resources:
# 修改/指定静态资源的访问路径
static-locations: [ classpath:/public/, classpath:/static/ ]
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: liu1457154996
driver-class-name: com.mysql.cj.jdbc.Driver
- 创建 04_springboot_usersys\src\main\java\com\hspedu\usersys\bean\Furn.java
public class Furn {
private Integer id;
private String name;
private String maker;
private BigDecimal price;
private Integer sales;
private Integer stock;
private String imgPath = "assets/images/product-image/1.jpg";
//这里增加无参构造器和有参构造器
20.1.3 应用实例-测试结果
● test 目录下的 com/hspedu/usersys/ApplicationTests.java , 完成测试
package org.example;
import org.example.bean.Furn;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.List;
/**
* @Author: GQLiu
* @DATE: 2024/5/6 23:43
*/
@SpringBootTest
public class ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void contextLoads() {
BeanPropertyRowMapper<Furn> furnBeanPropertyRowMapper = new BeanPropertyRowMapper<>(Furn.class);
List<Furn> furns = jdbcTemplate.query("select * from furn", furnBeanPropertyRowMapper);
for (Furn furn : furns) {
System.out.println(furn);
}
// 打印 数据库类
Class<? extends DataSource> aClass = jdbcTemplate.getDataSource().getClass();
System.out.println(aClass);
}
}
20.2 整合 Druid 到 Spring-Boot
使用手册: https://github.com/alibaba/druid
Druid: 性能优秀,Druid 提供性能卓越的连接池功能外【Java 基础】,还集成了 SQL 监控,黑名单拦截等功能,强大的监控特性,通过 Druid 提供的监控功能,可以清楚知道连接池和 SQL 的工作情况,所以根据项目需要,我们也要掌握 Druid 和 SpringBoot 整合
整合 Druid 到 Spring-Boot 方式
● 自定义方式
● 引入 starter 方式
20.2.3 Durid 基本使用
需求: 将 Spring-Boot 的数据源切换成 Druid
- 修改 pom.xml , 引入 druid 依赖
<!-- 引入 druid 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.22</version>
</dependency>
创建 com/hspedu/usersys/config/DruidDataSourceConfig.java 配置类
@ConfigurationProperties("spring.datasource")
对应:
package org.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
/**
* @Author: GQLiu
* @DATE: 2024/5/7 0:11
* 配置类,用于配置Druid连接池
*/
@Configuration
public class DruidDataSourceConfig {
// 默 认 的 自 动 配 置 是 判 断 容 器 中 没 有 才 会 配
/**
* @ConditionalOnMissingBean(DataSource.class)
* 1. 默认的数据源配置是 @ConditionalOnMissingBean(DataSource.class)
* 2. 也就是当容器中没有 DataSource 组件时,才会注入,如果我们这里配置了
DataSource, 就会使用我们配置的数据源
* 3. "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要
setXxx
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
// "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要setXxx
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
return druidDataSource;
}
}
- 完成测试,运行 ApplicationTests.java , 观察数据源的运行类型
20.2.4 Durid 监控功能-SQL 监控
需求: 配置 Druid 的监控功能,包括 SQL 监控、SQL 防火墙、Web 应用、Session 监控等
20.2.4.2 SQL 监控数据
- 修改 com/hspedu/usersys/config/DruidDataSourceConfig.java , 增加 druid 监控功能 参考地 址 :
package org.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.sql.SQLException;
/**
* @Author: GQLiu
* @DATE: 2024/5/7 0:11
* 配置类,用于配置Druid连接池
*/
@Configuration
public class DruidDataSourceConfig {
// 默 认 的 自 动 配 置 是 判 断 容 器 中 没 有 才 会 配
/**
* @ConditionalOnMissingBean(DataSource.class)
* 1. 默认的数据源配置是 @ConditionalOnMissingBean(DataSource.class)
* 2. 也就是当容器中没有 DataSource 组件时,才会注入,如果我们这里配置了
DataSource, 就会使用我们配置的数据源
* 3. "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要
setXxx
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要setXxx
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
// 加入监控功能
druidDataSource.setFilters("stat");
return druidDataSource;
}
/*
* 配置druid的监控页功能
* 这里是要配置一个StatViewServlet, 是一个标准的HttpServlet,所以这里使用ServletRegistrationBean() 方式注入Servlet Bean
* 除此之外,还可以用@WebServlet注解方式,继承HttpServlet 方式注入Servlet Bean
* */
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*"); // "/druid/*" 是映射路径。
//配置登录监控页面用户名和密码
statViewServletServletRegistrationBean.addInitParameter("loginUsername", "Lgq");
statViewServletServletRegistrationBean.addInitParameter("loginPassword", "123456");
return statViewServletServletRegistrationBean;
}
}
- 完成测试: 访问 http://localhost:10000/druid/index.html(实际上访问druid/*都可以进入该页面) 不会被拦截 , 如果没有问题,小伙伴会看到这个页面
- 修改 com/hspedu/usersys/config/DruidDataSourceConfig.java , 加入监控功能。参考: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
- 创建 com/hspedu/usersys/controller/DruidSqlController.java ,模拟操作 DB 的请求
package org.example.controller;
import org.example.bean.Furn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
/**
* @Author: GQLiu
* @DATE: 2024/5/9 22:20
* 模拟操作DB请求
*/
@Controller
public class DruidSqlController {
@Autowired
private JdbcTemplate jdbcTemplate;
@ResponseBody // 以json格式数据返回
@GetMapping("/sql")
public List<Furn> crudDB() {
BeanPropertyRowMapper<Furn> furnBeanPropertyRowMapper = new BeanPropertyRowMapper<>(Furn.class);
List<Furn> furns = jdbcTemplate.query("select * from furn", furnBeanPropertyRowMapper);
for (Furn furn : furns) {
System.out.println(furn);
}
return furns;
}
}
● 完成测试, 观察 SQL 监控数据, 浏览器 http://localhost:10000/druid/sql.html
此时可以监控到sql的访问:
不过访问得到的数据不美观,并不是@ResponseBody返回的JSON数据:
此时需要配置浏览器的内容协商:在application.yml文件中配置内容协商:
spring:
mvc:
hiddenmethod:
filter:
enabled: true # 开启页面表单的Rest功能。
contentnegotiation:
favor-parameter: true # 开启基于请求参数的内容协商功能
访问: http://localhost:8080/sql?format=json 即可得到JSON格式
20.2.5 Durid 监控功能-Web 关联监控
需求: 配置 Web 关联监控配置:Web 应用、URI 监控
官方文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
- 修 改 com/hspedu/usersys/config/DruidDataSourceConfig.java , 注 入 / 增 加 WebStatFilter 用于采集 web-jdbc 关联监控的数据
/*
* 注入 WebStatFilter 用于采集 web-jdbc 关联监控的数据
* 配置druid的web关联监控配置
* */
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
// StatViewFilter statViewFilter = new StatViewFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
// 配置 默认对所有的URL请求监控
// setUrlPatterns 的参数是Collection, 这里用Arrats.asList() 转成集合。
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
// 排除URL
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
- 为了测试方便,修改 com/hspedu/usersys/config/WebConfig.java, 放行 /sql 请求, 不再对sql的访问使用拦截器拦截。
/**
* @Author: GQLiu
* @DATE: 2024/5/2 23:57
* <p>
* 注册拦截器
* <p>
* 这里实现 Spring-Boot 定制功能, 加入自己的配置
*/
// lite 模式下,直接返回新实例对象
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 方式 一:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/", "/login", "images/**", "upload.html", "/upload", "/css/**", "/sql"); // 放行的请求
}
● 完成测试,重启项目,看看 Web 应用和 URI 监控是否生效
20.2.6 Durid 监控功能-SQL 防火墙
官方文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
配置方式十分简单:
- 修改 com/hspedu/usersys/config/DruidDataSourceConfig.java ,加入防火墙监控
查看Sql防火墙拦截效果:
20.2.7 Durid 监控功能-Session 监控
官方文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
Session监控监控的是哪个会话,哪个系统呢?
监控的不是druid监控页面这个会话,而是家具用户管理系统页面的会话。
20.2.8 Druid Spring Boot Starter
前面使用的是自己引入druid+配置类方式整合Druid和监控
Druid Spring Boot Starter 可以让程序⚪在Spring Boot项目中更加轻松集成Druid和监控
需求: 使用 Druid Spring Boot Starter 方式完成 Druid 集成和监控
修改 pom.xml 注销 druid 的依赖
注销 com/hspedu/usersys/config/DruidDataSourceConfig.java
这时测试,druid 失效
查看 druid 文档 https://github.com/alibaba/druid,引入 druid starter
导入 druid-springb-boot-starter依赖
查看其导入了哪些依赖
修改 resources/application.yml 增加配置参数
spring:
servlet:
multipart:
max-file-size: 5MB
max-request-size: 50MB
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML
mvc:
hiddenmethod:
filter:
enabled: true # 开启页面表单的Rest功能。
contentnegotiation:
favor-parameter: true # 开启基于请求参数的内容协商功能
web:
resources:
# 修改/指定静态资源的访问路径
static-locations: [ classpath:/public/, classpath:/static/ ]
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# druid配置
druid:
# filters: stat, wall, slf4j # 这一行决定哪些活动要开启
# 配置druid以及druid的监控页
stat-view-servlet:
enabled: true
login-username: lgq
login-password: 123456
reset-enable: false
# 配置 web 监控, 用于采集web-jdbc关联的监控数据 是对应查看URI监控
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# 启用filter
filter:
# 配置sql 监控
stat:
slow-sql-millis: 1000 # 慢查询
log-slow-sql: true # 启用慢查询的结果日志
enabled: true
# 防火墙
wall:
enabled: true
# config:
# drop-table-allow: false # 不允许删除表的操作
# select-all-column-allow: false # 不允许select * from xxx
重启项目,完成测试
访问 http://localhost:8080/sql
21 Spring Boot 整合 MyBatis
需求:查询出一条数据
- 创建数据库和表
CREATE DATABASE `springboot_mybatis`
DROP TABLE `monster`
use `springboot_mybatis`
CREATE TABLE `Monster` (
`id` INT NOT NULL AUTO_INCREMENT,
`age` INT NOT NULL,
`birthday` DATE DEFAULT NULL,
`email` VARCHAR(255) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`name` VARCHAR(255) DEFAULT NULL,
`salary` DOUBLE NOT NULL,
PRIMARY KEY (`id`)
) CHARSET=utf8
insert into Monster values(null, 20, '2000-11-11', 'nmw@sohu.com', '男', '牛魔王', 5000.88);
insert into Monster values(null, 10, '2011-11-11', 'bgj@sohu.com', '女', '白骨精', 8000.88);
SELECT * FROM `Monster`
- 创建 05_springboot_mybatis 项目-使用灵活的方式创建 maven
pom.xml
<?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.lgq</groupId>
<artifactId>springboot_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<!--导入SpringBoot父工程,规定的写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<!--
在build种配置resources, 防止我们资源导出失败的问题.
1. 不同idea/maven版本可能提示错误不一样.
2. 以不变应万变, 少什么文件, 就增加相应的配置即可.
3. 含义是将 src/main/java目录和子目录以及 src/main/resources目录和子目录下的资源文件xml和properties在build项目时,导出到对应target目录下
-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.yml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
<!--引入相关的依赖-->
<dependencies>
<!--引入web starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.3</version>
</dependency>
<!--引入mybatis starer-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
<!--引入配置处理器
用于去配置application.yml
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<optional>true</optional>
</dependency>
<!--引入springboot test
为了在测试中能够使用 @SpringBootTest 注解
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入druid依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!--导入mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
</dependencies>
</project>
- 创建 resources/application.yml , 配置数据源参数, 并完成 Spring Boot 项目启动测试
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_mybatisplus?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: liu1457154996 # 实验室密码123456
# 指定mybatis的配置
mybatis:
# 指定 XXXmapper.xml 文件的配置
mapper-locations: classpath:mapper/*.xml
# 配置原来的类型别名
# type-aliases-package: com.lgq.springboot.mybatis.bean
config-location: classpath:mybatis-config.xml
# 什么时候写单独的mybatis-config.xml, 什么时候直接在application.yml中
# 配置文件很多时,单独写一个mybatis-config.xml. 配置文件不多,直接在application.yml文件中写
mybatis-plus:
type-aliases-package: com.lgq.springboot.mybatis.bean
logging:
level:
org.mybatis: DEBUG
- 切换数据源为 druid , 修改 pom.xml(如果没有 mybatis-stater , 加入即可.) , 并加入配置文件 com/hspedu/mybatis/config/DruidDataSourceConfig.java , 完成测试
在pom.xml文件中:
<!--引入druid依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
package com.lgq.springboot.mybatis.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
/**
* @author GQLiu
* @date 2024/5/12 15:00
* 配置类, 用于配置Druid连接池
*/
@Configuration
public class DruidDataSourceConfig {
// 默 认 的 自 动 配 置 是 判 断 容 器 中 没 有 才 会 配
/**
* @ConditionalOnMissingBean(DataSource.class)
* 1. 默认的数据源配置是 @ConditionalOnMissingBean(DataSource.class)
* 2. 也就是当容器中没有 DataSource 组件时,才会注入,如果我们这里配置了
DataSource, 就会使用我们配置的数据源
* 3. "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要
setXxx
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException, SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// "spring.datasource" 会将 druid 数据源的配置绑定到 application.yml, 就不需要setXxx
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
// 加入监控功能
// 添加 ,wall 表示添加sql防火墙功能
// druidDataSource.setFilters("stat, wall");
return druidDataSource;
}
/*
* 配置druid的监控页功能
* 这里是要配置一个StatViewServlet, 是一个标准的HttpServlet,所以这里使用ServletRegistrationBean() 方式注入Servlet Bean
* 除此之外,还可以用@WebServlet注解方式,继承HttpServlet 方式注入Servlet Bean
* */
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
//配置登录监控页面用户名和密码
statViewServletServletRegistrationBean.addInitParameter("loginUsername", "Lgq");
statViewServletServletRegistrationBean.addInitParameter("loginPassword", "123456");
return statViewServletServletRegistrationBean;
}
/*
* 注入 WebStatFilter 用于采集 web-jdbc 关联监控的数据
* 配置druid的web关联监控配置
* */
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
// StatViewFilter statViewFilter = new StatViewFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
// 配置 默认对所有的URL请求监控
// setUrlPatterns 的参数是Collection, 这里用Arrats.asList() 转成集合。
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
// 排除URL
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
测试当前数据源
package com.lgq.springboot.mybatis;
import com.lgq.springboot.mybatis.bean.Monster;
import com.lgq.springboot.mybatis.mapper.MonsterMapper;
import com.lgq.springboot.mybatis.service.MonsterService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
@SpringBootTest
public class ApplicationTest {
@Resource
JdbcTemplate jdbcTemplate;
@Test
public void t1() {
// 输出看看当前的数据源是什么
System.out.println(jdbcTemplate.getDataSource().getClass());
}
}
- 创建com/hspedu/mybatis/bean/Monster.java
package com.lgq.springboot.mybatis.bean;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author GQLiu
* @date 2024/5/12 15:56
*/
@Data
public class Monster {
private Integer id;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date birthday;
private String email;
private String name;
private String gender;
private Double salary;
public Monster() {
}
}
- 创建 com/hspedu/mybatis/mapper/MonsterMapper.java
package com.lgq.springboot.mybatis.mapper;
import com.lgq.springboot.mybatis.bean.Monster;
import org.apache.ibatis.annotations.Mapper;
/**
* @author GQLiu
* @date 2024/5/12 16:10
*/
@Mapper
public interface MonsterMapper {
// 根据 id 得到Monster
public Monster getMonsterById(Integer id);
}
- 创建 05_springboot_mybatis\src\main\resources\mapper\MonsterMapper.xml , 文件模板从 mybatis 官方文档拷贝
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lgq.springboot.mybatis.mapper.MonsterMapper">
<!--在application.yml文件中配置了简写。 这里的resultType可以直接写Monster-->
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `Monster` WHERE id = #{id}
</select>
</mapper>
- 创 建 com/hspedu/mybatis/service/MonsterService.java 和com/hspedu/mybatis/service/impl/MonsterServiceImpl.java
package com.lgq.springboot.mybatis.service;
import com.lgq.springboot.mybatis.bean.Monster;
/**
* @Author: GQLiu
* @DATE: 2024/5/16 22:33
*/
public interface MonsterService{
public Monster getMonsterById(Integer id);
}
package com.lgq.springboot.mybatis.service.impl;
import com.lgq.springboot.mybatis.bean.Monster;
import com.lgq.springboot.mybatis.mapper.MonsterMapper;
import com.lgq.springboot.mybatis.service.MonsterService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Author: GQLiu
* @DATE: 2024/5/16 22:34
*/
@Service // 一定注意 这个 Service 注解要写在实现类上,而不是写在接口上,不然会报错!!
public class MonsterServiceImpl implements MonsterService {
@Resource
private MonsterMapper monsterMapper;
@Override
public Monster getMonsterById(Integer id) {
return monsterMapper.getMonsterById(id);
}
}
测试MonsterMapper
package com.lgq.springboot.mybatis;
import com.lgq.springboot.mybatis.bean.Monster;
import com.lgq.springboot.mybatis.mapper.MonsterMapper;
import com.lgq.springboot.mybatis.service.MonsterService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
@SpringBootTest
public class ApplicationTest {
@Resource
JdbcTemplate jdbcTemplate;
@Resource
private MonsterMapper monsterMapper;
@Test
public void t1() {
// 输出看看当前的数据源是什么
System.out.println(jdbcTemplate.getDataSource().getClass());
}
// 测试MonsterMapper接口
@Test
public void getMonsterById() {
Monster monster = monsterMapper.getMonsterById(1);
System.out.println(monster);
}
}
9. 创建 com/hspedu/mybatis/controller/MonsterController.java(SpringMVC那一套)
package com.lgq.springboot.mybatis.controller;
import com.lgq.springboot.mybatis.bean.Monster;
import com.lgq.springboot.mybatis.service.MonsterService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
/**
* @Author: GQLiu
* @DATE: 2024/5/19 10:13
*/
@Controller
public class MonsterController {
@Resource
private MonsterService monsterService;
@ResponseBody
@GetMapping("/monster")
public Monster getMonsterById(@RequestParam("id") Integer id) {
return monsterService.getMonsterById(id);
}
}
- 修改 resources/application.yml , 指定 mybatis 的配置参数
# 指定mybatis的配置
mybatis:
# 指定 XXXmapper.xml 文件的配置
mapper-locations: classpath:mapper/*.xml
# 配置原来的类型别名
# type-aliases-package: com.lgq.springboot.mybatis.bean
config-location: classpath:mybatis-config.xml
# 什么时候写单独的mybatis-config.xml, 什么时候直接在application.yml中
# 配置文件很多时,单独写一个mybatis-config.xml. 配置文件不多,直接在application.yml文件中写
21.3 注意事项和细节说明
- spring boot 整合 mybatis 取出的日期, 出现 8 小时时差 解决方案
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
22 Spring Boot 整合 MyBatis-Plus
MyBatis-Plus 官网 https://baomidou.com
22.2 基本介绍
- MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
22.3 整合 MyBatis-Plus 实例
需求:查询
- 创建数据库和表
CREATE DATABASE `springboot_mybatisplus`
USE `springboot_mybatisplus`
CREATE TABLE `monster` (
`id` INT NOT NULL AUTO_INCREMENT,
`age` INT NOT NULL,
`birthday` DATE DEFAULT NULL,
`email` VARCHAR(255) DEFAULT NULL,
`gender` CHAR(1) DEFAULT NULL,
`name` VARCHAR(255) DEFAULT NULL,
`salary` DOUBLE NOT NULL,
PRIMARY KEY (`id`)
) CHARSET=utf8
SELECT * FROM `monster`
INSERT INTO monster VALUES(NULL, 20, '2000-11-11', 'xzj@sohu.com', '男', ' 蝎 子 精 ',
15000.88);
INSERT INTO monster VALUES(NULL, 10, '2011-11-11', 'ytj@sohu.com', '女', ' 玉 兔 精 ',
18000.88);
- 创建 06_springboot_mybatisplus 项目
-pom.xml 引入必要的依赖
pom.xml
<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>org.example</groupId>
<artifactId>springboot_mybatisplus</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot_mybatisplus</name>
<url>http://maven.apache.org</url>
<!--导入SpringBoot父工程,规定的写法-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<!--
在build种配置resources, 防止我们资源导出失败的问题.
1. 不同idea/maven版本可能提示错误不一样.
2. 以不变应万变, 少什么文件, 就增加相应的配置即可.
3. 含义是将 src/main/java目录和子目录以及 src/main/resources目录和子目录下的资源文件xml和properties在build项目时,导出到对应target目录下
-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.yml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
<!--引入相关的依赖-->
<dependencies>
<!--引入web starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.3</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
<!--引入配置处理器
用于去配置application.yml
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<optional>true</optional>
</dependency>
<!--引入springboot test
为了在测试中能够使用 @SpringBootTest 注解
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入druid依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!--导入mybatis-plus starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
</dependencies>
</project>
- 创建 resources/application.yml 配置数据源参数
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_mybatisplus?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: liu1457154996 # 实验室密码123456
# 指定 mybatis plus配置
mybatis-plus:
# type-aliases-package: com.lgq.springboot.mybatis.bean
#
# # 指定 XXXmapper.xml 文件的配置
# mapper-locations: classpath:mapper/*.xml
# # 配置原来的类型别名
type-aliases-package: com.lgq.springboot.mybatis.bean
# config-location: classpath:mybatis-config.xml
# 指定配置,输出日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 什么时候写单独的mybatis-config.xml, 什么时候直接在application.yml中
# 配置文件很多时,单独写一个mybatis-config.xml. 配置文件不多,直接在application.yml文件中写
logging:
level:
org.mybatis: DEBUG
切 换 数 据 源 为 druid , 修 改 pom.xml 和 创 建 配 置 文 件com/hspedu/mybatisplus/config/DruidDataSourceConfig.java
同mybatis测试是否能正确启动项目, 注意观察 mybatis-plus 是否引入成功
创建 com/hspedu/mybatisplus/bean/Monster.java
package org.example.mybatisplus.bean;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author GQLiu
* @date 2024/5/12 15:56
* 如果实体类Monster 和 表名 monster 是对应的,可以直接映射
* 如果不一致,需要用 @TableName(value = "表名") 来进行映射。
*/
@Data
// @TableName(value = "monster_")
public class Monster {
private Integer id;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date birthday;
private String email;
private String name;
private String gender;
private Double salary;
public Monster() {
}
}
- 创建 com/hspedu/mybatisplus/mapper/MonsterMapper.java
package org.example.mybatisplus.mapper;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.mybatisplus.bean.Monster;
/**
* @author GQLiu
* @date 2024/5/12 16:10
* 1. BaseMapper 已经默认定义了很多的CRUD方法, 可以直接使用
* 2. 如果BaseMapper 提供的方法不满足业务需求,可以在XXXMapper.java中声明方法,并在XXXMapper.xml文件配置。
*/
@Mapper
public interface MonsterMapper extends BaseMapper<Monster> {
// 添加一个insert方法
int insertSelective(Monster monster);
int delById(@Param("id") Integer id);
}
- 创建 com/hspedu/mybatisplus/service/MonsterService.java
package org.example.mybatisplus.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.example.mybatisplus.bean.Monster;
/**
* @Author: GQLiu
* @DATE: 2024/5/16 22:33
* 1. 传统方式:在接口中,定义方法/声明方法,然后在实现类中进行实现
* 2. 在mybatis-plus中,接口 可以继承 父接口 IService
* 3. 这个IService接口声明了很多方法, 比如crud
* 4. 默认提供的方法不能满足要求,可以再声明需要的方法,然后在实现类中实现
*/
public interface MonsterService extends IService<Monster> {
// 自定义方法
}
- 创建 com/hspedu/mybatisplus/service/impl/MonsterServiceImpl.java
package org.example.mybatisplus.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.mybatisplus.bean.Monster;
import org.example.mybatisplus.mapper.MonsterMapper;
import org.example.mybatisplus.service.MonsterService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Author: GQLiu
* @DATE: 2024/5/16 22:34
* 1. 传统方式:在实现类Impl 直接实现 MonsterService
* 2. 在mybatis-plus中,我们开发 Service 实现类,需要继承 ServiceImpl
* 3. 我们观察到ServiceImpl类实现了IService接口
* 4. MonsterService 接口继承了IService接口
* 5. 所以这里的MonsterServiceImpl 可以认为是实现了 MonsterService 接口 。
* 这样MonsterServiceImpl 就可以实现 IService方法
* 6. 总结: 实现MonsterService是为了在Impl中实现自定义的方法
* 继承ServiceImpl
*/
@Service // 一定注意 这个 Service 注解要写在实现类上,而不是写在接口上,不然会报错!!
public class MonsterServiceImpl extends ServiceImpl<MonsterMapper, Monster>
implements MonsterService {
@Resource
private MonsterMapper monsterMapper;
}
- 创建 com/hspedu/mybatisplus/controller/MonsterController.java
package org.example.mybatisplus.controller;
import org.example.mybatisplus.bean.Monster;
import org.example.mybatisplus.service.MonsterService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author: GQLiu
* @DATE: 2024/5/19 10:13
*/
@Controller
public class MonsterController {
@Resource
private MonsterService monsterService;
@ResponseBody
@GetMapping("/monster")
public Monster getMonsterById(@RequestParam("id") Integer id) {
return monsterService.getById(id);
}
@ResponseBody
@GetMapping("/list")
public List<Monster> listMonster() {
return monsterService.list();
}
}
- 修改 com/hspedu/mybatisplus/Application.java , 加入对 Mapper 的扫描
如果不想每个XxxMapper.java都去写@Mapper注释,可以通过在SpringBoot主程序中编写 @MapperScan 注解,加入对Mapper的扫描
package org.example.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.swing.*;
/**
* @Author: GQLiu
* @DATE: 2024/5/19 11:06
* @MapperScan 可以指定扫描某个包下的所有都当作mapper。不用在每个XXXMapper类中写@Mapper
*/
@MapperScan(basePackages = {"org.example.mybatisplus.mapper"})
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
22.4 整合 MyBatis-Plus 注意事项和细节
- @TableName 作用
@TableName(value = "表名") 来进行映射。
2. MyBatis-Plus starter 到底引入了哪些依赖?
3. 为 了 开 发 方 便 , 可 以 安 装 MyBatisX 插 件 , 参 考 文 档 :https://baomidou.com/guide/mybatisx-idea-plugin.html