SpringBoot 2 快速梳理
入门文档 |
---|
Spring Boot Reference Documentation🎉 |
Spring Boot 中文文档🎉 |
SpringBoot 是为了简化开发, 推出的封神框架(约定优于配置[COC],简化了 Spring 项目 的配置流程)
0、快速入门
0.1 pom.xml
简化了 Maven 项目的 pom.xml 依赖导入, 可以说是一键导入Spring,SpringMVC,Tomcat等web开发所需jar包
<!-- 导入 Spring Boot 父工程,规定的写法 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<dependencies>
<!-- 导入 web 项目场景启动器,会自动导入和 web 开发相关依赖,非常方便 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入Spring Boot配置处理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
0.2 开启
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
System.out.println("启动了");
}
}
0.2.1 测试
建立一个Contller,映射一个接口用于测试,返回一个字符串即可,用于展示返回成功。
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello(){
return "Hello, Spring Boot";
}
}
访问:http://127.0.0.1:8080/hello
显示如下效果表示成功
0.3 梳理
- Spring Boot > Spring > Spring MVC
- Spring MVC 只是 Spring 处理 WEB 层请求的一个模块/组件, Spring MVC 的基石是 Servlet
- Spring 的核心是 IOC 和 AOP, IOC 提供了依赖注入的容器 , AOP 解决了面向切面编程
0.4 约定优于配置
约定优于配置(Convention over Configuration/COC),又称按约定编程,是一种软件设计规范, 本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)
简单来说就是假如你所期待的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待时, 才需要对约定进行替换配置
Spring Boot的每个版本都提供了一个它所支持的依赖的列表。 在实践中,你不需要在构建配置中为这些依赖声明版本,因为Spring Boot会帮你管理这些。 当你升级Spring Boot本身时,这些依赖也会一同升级。你仍然可以指定某个依赖的版本,来覆盖spring boot默认的版本。
想要做到这一功能,首先你只需要将项目配置为继承自spring-boot-starter-parent
,类似这样,就可以只需要在此依赖项上指定 Spring Boot 版本号,在导入其他启动器时,则可以安全地省略版本号。
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>
常见两种情况:
- 因为在
spring-boot-starter-parent
的父项目中,声明了开发中常用的依赖的版本号。即如果程序员没有指定某个依赖 jar 的版本,则以父项目指定的版本为准- 双击
pom.xml
中spring-boot-starter-parent
,再进入它的父项目spring-boot-dependencies
即可查询父项目中默认的版本
- 双击
2. (不推荐)当你引入某个依赖而不想使用父项目默认的版本时,你只需在引入该依赖时赋予其明确的<version>
标签即可,例如:
<!-- 引入 mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
- (推荐)你也可以通过重写自己项目中的属性来重写单个依赖项。例如,要使用不同版本的 SLF4J 库和 Spring Data 发布系列,您需要将以下内容添加到您的
pom.xml
:
<properties>
<slf4j.version>1.7.30</slf4j.version>
<spring-data-releasetrain.version>Moore-SR6</spring-data-releasetrain.version>
</properties>
0.5 Starter
Starter是一系列开箱即用的依赖,你可以在你的应用程序中导入它们。 通过你Starter,可以获得所有你需要的Spring和相关技术的一站式服务,免去了需要到处大量复制粘贴依赖的烦恼。 例如,如果你想开始使用Spring和JPA进行数据库访问,那么可以直接在你的项目中导入 spring-boot-starter-data-jpa
依赖。
Starter含了很多你需要的依赖,以使项目快速启动和运行,并拥有一套一致的、受支持的可管理的过渡性依赖。
关于Starter的命名
所有官方的Starter都遵循一个类似的命名模式;spring-boot-starter-*
,其中 *
是一个特定类型的应用程序。 这种命名结构是为了在你需要寻找Starter时提供帮助。 许多IDE中集成的Maven可以让你按名称搜索依赖。 例如,如果安装了相应的Eclipse或Spring Tools插件,你可以在POM编辑器中按下 ctrl-space
,然后输入 “spring-boot-starter” 来获取完整的列表。
正如 “创建你自己的 Starter” 一节所解释的,第三方启动器不应该以 spring-boot
开头,因为它是留给Spring Boot官方组件的。 相反,第三方启动器通常以项目的名称开始。 例如,一个名为 thirdpartyproject
的第三方启动器项目通常被命名为 thirdpartyproject-spring-boot-starter
。
0.6 修改配置
在resources\application.properties 文件中 修改配置
0.6.0 在配置文件中自定义属性值
@Value(“${pre.pr0perty}”)
在properties 文件中可以自定义配置属性值,通过@Value(“${}”)获取对应属性值。例如:
配置文件:
my:
test: yu
类:
@Controller
public class HelloController {
@Value("${my.test}")
private String s;
@RequestMapping("/hello")
@ResponseBody
public String hello(){
System.out.println(s);
return "Hello, Spring Boot";
}
}
另外,我们还可以使用 @Value("${property.name:defaultValue}")
指定默认值,以注入配置值。
使用类型安全的配置属性绑定@ConfigurationProperties
与其使用 @Value
分别绑定每个属性值,我们可以创建一个包含属性的类,然后让 Spring 框架通过使用,类中属性必须有set方法@ConfigurationProperties
将配置值绑定到该配置属性类的实例中,如下所示:
配置:
jyu:
username: yu
eat: cake
age: 18
sprot: football
类:(使用lombok的@Setter省略了set方法)
@ConfigurationProperties(prefix = "jyu")
@Setter
public class Jyu {
private String username;
private Integer age;
private String sport;
private String eat;
}
定义配置属性类后,我们需要使用 @EnableConfigurationProperties(jyu.class)
启用绑定。这通常在应用程序 main 类中完成。
或者在启动类上添加@ConfigurationPropertiesScan
来扫描所有注解了 @ConfigurationProperties
的类, 并自动注册它们,而不是显式地指定每个配置属性类。如下所示:
@SpringBootApplication
@ConfigurationPropertiesScan
public class FlashSaleApplication {
public static void main(String[] args) {
SpringApplication.run(FlashSaleApplication.class,args);
}
}
想要了解更多操作,推荐阅读这篇文章:@ConfigurationProperties 注解使用姿势,这一篇就够了
0.6.1 常见配置
application.properties:
#tomcat监听端口
server.port=9090
#应用的上下文路径(项目路径)
server.servlet.context-path=/jyu
#开启页面表单的Rest功能
spring.mvc.hiddenmethod.filter.enabled=true
#开启基于请求参数的内容协商功能
spring.mvc.contentnegotiation.favor-parameter=true
#指定一个内容协商的参数名
spring.mvc.contentnegotiation.parameter-name=format
#静态资源路径
spring.mvc.static-path-pattern=/static/**
#配置视图解析器
#这里是需要注意 prefix需要和当前的static-path-pattern一致
spring.mvc.view.suffix=.html
spring.mvc.view.prefix=/static/
application.yml:
server:
port: 10090 #tomcat 监听端口
servlet:
context-path: /jyu #应用的上下文路径
spring:
datasource:
url: jdbc:mysql://localhost:3306/furn_boot?useSSL=true&useUnicode=true&characterEncoding=utf-8
username: root
password: abcdef
driver-class-name: com.mysql.cj.jdbc.Driver
mvc:
static-path-pattern: /** #静态资源访问路径
hiddenmethod:
filter:
enabled: true # 开启页面表单的Rest功能
view: #配置视图解析器
prefix: / #这里是需要注意 prefix需要和当前的static-path-pattern一致
suffix: .html
contentnegotiation: # 内容协商
favor-parameter: true
parameter-name: format
mybatis-plus:
mapper-locations: classpath:mapper/*.mapper
type-aliases-package: com.jyu.furn.bean
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
1、容器
@Component
、``@Controller、
@Service、
@Repository` 依然可以通过注解注入组件,与在Spring中使用方法无异。
1.1 @Configuration
通过配置类注入组件,功能类似于Spring用于注入组件的xml文件,并且配置类兼容xml注入组件的形式。
@Bean
:给容器中添加组件,方法名就是Bean在容器中的默认ID。也可以通过@Bean("xxx")
的方式指定BeanID。
@Scope(value = "prototype")
:指定单例模式还普通模式。
- 推荐阅读:@Scope注解 详细讲解及示例
@Import
:方式注入的组件, 默认组件的名字就是全类名
1.1.1 proxyBeanMethods
proxyBeanMethods:代理 bean 的方法
-
Full:
@Configuration(proxyBeanMethods = true)
:保证每个@Bean
方法被调用多少次返回的组件都是单实例的, 是代理方式。 -
Lite:
@Configuration(proxyBeanMethods = false)
:每个@Bean
方法被调用多少次返回的组件都是新创建的, 是非代理方式。
proxyBeanMethods 是在 调用被@Bean
注解的方法才生效,因此,需要先获取配置类,通过配置类对象再调用方法获取 Bean。
如何选择:
-
组件依赖必须使用 Full 模式默认。
-
如果不需要组件依赖使用 Lite 模 ,Lite 模 也称为轻量级模式,因为不检测依赖关系,运行速度快。
1.2 @Condtional
条件装配:满足 Conditional 指定的条件,则进行组件注入
@Conditional 是一个根注解,下面有很多扩展注
1.2.1 @Conditional
扩展注解
@Conditional 扩展注解 | 作用 |
---|---|
ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
1.3 @ImportResource
原生配置文件引入, 也就是可以直接导入 Spring 传统的 beans.xml ,可以认 为是 SpringBoot 对 Spring 容器文件的兼容.
@ImportResource("classpath:beans.xml")
public class BeanConfig {}
1.4 属性数据绑定
使用 Java 读取到 SpringBoot 核心配置文件 application.properties 的内容, 并且把它封装到 JavaBean 中
0.6.0在配置文件中自定义属性值 已经详细展示
- xxx.属性名=abc
- 在bean类上注解 @ConfigurationProperties(prefix = “xxx”)
- 在配置类上注解 @EnableConfigurationProperties(Furn.class)
如果application.properties 有中文, 需要转成 unicode 编码写入, 否则出现乱码
配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
1.5 lombok
-
简化JavaBean开发, 可以使用Lombok的注解让代码更加简洁。
-
Java 项目中,很多没有技术含量又必须存在的代码:POJO的getter/setter/toString;异常 处理;I/O流的关闭操作等等,这些代码既没有技术含量,又影响着代码的美观,Lombok 应运而生
-
推荐阅读文章:
配置:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
1.6 yaml
YAML 是"YAML Ain’t a Markup Language"(YAML 不是一种标记语言) 的递归缩写。在开发 的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言), 是为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名 【百度百科】
推荐阅读:
1.6.0 修改配置
使用 yaml 配置文件 和 JavaBean进行数据绑定,如 果 application.properties 和 application.yml 有 相 同 的 前 缀 值 绑 定 , 则 application.properties 优先级高, 开发时,应当避免。
server:
port: 10090 #tomcat 监听端口
servlet:
context-path: /jyu #应用的上下文路径
spring:
mvc:
# static-path-pattern: /hspres/** #修改静态资源访问的路径/前缀
#启用了HiddenHttpMethodFilter,开启页面表单的Rest功能
hiddenmethod:
filter:
enabled: true
#配置视图解析器
view:
suffix: .html
prefix: / #这里是需要注意 prefix需要和当前的static-path-pattern一致
contentnegotiation:
favor-parameter: true #开启基于请求参数的内容协商功能
parameter-name: hspformat #指定一个内容协商的参数名
web:
resources:
#修改/指定 静态资源的访问路径/位置
static-locations: ["classpath:/hspimg/","classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/"] #String[] staticLocations
1.6.1 yaml 基本语法
序号 | 语法 |
---|---|
1 | 形式为 key: value;注意: 后面有空格 |
2 | 区分大小写 |
3 | 使用缩进表示层级关系,缩进不允许使用 tab,只允许空格 [有些地方也识别 tab , 推荐使用空格] ,缩进的空格数不重要, 只要相同层级的元素左对齐即可 |
4 | 字符串无需加引号 |
5 | yml 中, 注释使用 # |
1.6.2 数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
对象:键值对的集合, 比如 map、hash、set、objec
1.6.3 案例练习
利用application.yml文件自动向ioc容器中注入bean对象并进行数据绑定。
java
@Data
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "monster")
public class Monster {
private Integer id;
private String name;
private Boolean isMarried;
private Date birth;
private Car car;
private String[] skills;
private List<String> hobby;
private Map<String,String> wife;
}
application.yml
monster:
id: 1
name: 牛魔王
is-married: true
birth: 2013/11/01
car:
name: 避水晶晶兽
price: 1000
skills:
- 牛魔拳
- 芭蕉扇
hobby: [唱歌,洗澡]
wife:
first: 铁扇公主
second: 玉面狐狸
结果显示
Monster(id=1, name=牛魔王, isMarried=true, birth=Fri Nov 01 00:00:00 CST 2013, car=Car(name=避水晶晶兽, price=1000.0), skills=[牛魔拳, 芭蕉扇], hobby=[唱歌, 洗澡], wife={first=铁扇公主, second=玉面狐狸})
2、静态资源访问
- 静态资源放在以下路径下:
/static
、/public
、/resources
、/META-INF/resources
可以被直接访问。- 常见静态资源:
JS
、CSS
、图片(.jpg .png .gif .bmp .svg)、字体文件(Fonts)。 - 访问方式 :(默认)项目根路径/ + 静态资源名。
- 常见静态资源:
- 静态资源访问原理:静态映射是
/**
, 也就是对所有请求拦截,请求进来,先看 Controller 能不能处理,不能处理的请求交给静态资源处理器,如果静态资源找不到则响应 404 页。
以下配置可以修改静态资源的默认存放路径和默认访问路径
#修改静态资源访问的路径/前缀
spring:
mvc:
static-path-pattern: /jyu/**
web:
resources:
static-locations: [classpath:/hspimg/, classpath:/META-INF/resources/, classpath:/static/,classpath:/public/,classpath:/resources/]
视图解析配置
spring:
mvc:
hiddenmethod:
filter:
enabled: true # 启用了 HiddenHttpMethodFilter,开启页面表单的 Rest 功能
view:
prefix: / #这里是需要注意 prefix需要和当前的static-path-pattern一致
suffix: .html
3、接收参数
3.1 Rest风格处理请求
Rest风格 | |
---|---|
GET | 获取操作 |
DELETE | 删除操作 |
PUT | 保存操作 |
POST | 修改操作 |
启用Rest风格配置:application.yml
spring:
mvc:
#启用了HiddenHttpMethodFilter,开启页面表单的Rest功能
hiddenmethod:
filter:
enabled: true
@PathVariable 、 @RequestHeader 、 @ModelAttribute 、 @RequestParam 、 @MatrixVariable、
3.2 常用注解
注解 | 作用 |
---|---|
@RequestParam | 接受到的参数 |
@PathVariable | 映射 URL 获取绑定的占位符的变量 |
@RequestHeader | 获取 http 请求头信息 |
@RequestBody | 将接受的JSON数据,自动类型转换封装成 JavaBean |
@CookieValue | cookie的值绑定到方法的参数上 |
@RequestAttribute | 获取request域中的数据 |
@SessionAttribute | 获取session域中的数据 |
示例:
/**
* @author 韩顺平
* @version 1.0
*/
@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
* 6. 可以看下@PathVariable源码
*/
@GetMapping("/monster/{id}/{name}")
public String pathVariable(@PathVariable("id") Integer id,
@PathVariable("name") String name,
@PathVariable Map<String, String> map) {
System.out.println("id-" + id);
System.out.println("name-" + name);
System.out.println("map-" + map);
return "success";
}
/**
* @RequestHeader("Host") 获取http请求头的 host信息
* @RequestHeader Map<String, String> header: 获取到http请求的所有信息
*/
@GetMapping("/requestHeader")
public String requestHeader(@RequestHeader("host") String host,
@RequestHeader Map<String, String> header,
@RequestHeader("accept") String accept) {
System.out.println("host-" + host);
System.out.println("header-" + header);
System.out.println("accept-" + accept);
return "success";
}
/**
* 老师解读
* 如果我们希望将所有的请求参数的值都获取到,可以通过
*
* @param username
* @param fruits
* @param paras
* @return
* @RequestParam Map<String, String> paras 获取
*/
@GetMapping("/hi")
public String hi(@RequestParam(value = "name") String username,
@RequestParam("fruit") List<String> fruits,
@RequestParam Map<String, String> paras) {
System.out.println("username-" + username);
System.out.println("fruit-" + fruits);
System.out.println("paras-" + paras);
return "success";
}
/**
* 因为我的浏览器目前没有cookie,我们可以自己设置cookie[技巧还是非常有用]
* 如果要测试,可以先写一个方法,在浏览器创建对应的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,
HttpServletRequest request,
@CookieValue(value = "username", required = false) Cookie cookie) {
System.out.println("cookie_value-" + cookie_value);
if (cookie != null) {
System.out.println("username-" + cookie.getName() + "-" + cookie.getValue());
}
System.out.println("-------------------------");
Cookie[] cookies = request.getCookies();
for (Cookie cookie1 : cookies) {
System.out.println(cookie1.getName() + "=>" + cookie1.getValue());
}
return "success";
}
/**
* @RequestBody 是整体取出Post请求内容
*/
@PostMapping("/save")
public String postMethod(@RequestBody String content) {
System.out.println("content-" + content);
return "success";
}
//处理添加monster的方法
@PostMapping("/savemonster")
public String saveMonster(Monster monster) {
System.out.println("monster-" + monster);
return "success";
}
}
4、自定义内容转换器
@RequestBody/@ResponseBody 或 HttpEntity/ResponseEntity 时, Spring 首先根据请求头或响应头的 Accept 属性选择 匹配 的 HttpMessageConverter, 进而 根据参 数类型 或泛型 类型的 过滤得 到匹配 的 HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错
示例:
@Configuration(proxyBeanMethods = true)
public class BeanConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Car>() {
@Override
public Car convert(String s) {
if(!ObjectUtils.isEmpty(s)){
Car car = new Car();
car.setName(s.split(",")[0]);
car.setPrice(Double.parseDouble(s.split(",")[1]));
return car;
}
return null;
}
});
}
};
}
}
5、内容协商
根据请求头不同(不同的 Accept 类型),返回对应的json数据 或者 xml 数据
<!-- 引入支持返回 xml 数据格式 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
修改配置application.yml
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: format
6、Thymeleaf
Thymeleaf 是服务器渲染技术, 页面数据是在服务端进行渲染的,并不是一个高性能的引擎,适用于单体应用。
pom.xml
<!-- 引入支持返回 thymeleaf 数据格式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
6.1 表达式
表达式名字 | 语法 | 用法 |
---|---|---|
变量取值 | ${…} | 获取请求域、session 域、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include 作用,引入公共页面片段 |
6.2 字面量
文本值: ‘hello’ ,…
数字: 10 , 7 , 36.8 , …
布尔值: true , false
空值: null
变量: name,age,… 变量不能有空格
6.3 th 属性
7、拦截器
自定义的拦截器必须实现 HandlerInterceptor 接口
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/", "/login", "/images/**");
//放行的请求, 可以根据需要增加
}
}
7.0 自定义拦截器执行流程图
- 如果 preHandle 方法 返回 false, 则不再执行目标方法, 可以在此指定返回页面。
- postHandle 在 目 标 方 法 被 执 行 后 执 行 . 可 以 在 方 法 中 访 问 到 目 标 方 法 返 回 的ModelAndView 对象。
- 若 preHandle 返回 true, 则 afterCompletion 方法 在渲染视图之后被执行。
- 若 preHandle 返回 false, 则 afterCompletion 方法和postHandler方法不会被调用。
- 在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效。
7.1 多拦截器执行流程
7.2 拦截器与过滤器
过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就 是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,Filter 只能在 web 程序中使用
拦截器(Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application 等程序中
- 过滤器 Filter 是在请求进入容器后, 但在进入servlet 之前进行预处理, 请求结束是在 servlet 处理完以后
- 拦截器 Interceptor 是在请求进入 servlet 后, 在进入 Controller 之前进行预处理的, Controller 中渲染了对应的视图之后请求结束
8、 文件上传和下载
applicartion.yaml
spring:
servlet:
multipart:
max-file-size: 1 #指定文件最大值
max-request-size: 10 #指定每次请求最大值
前端页面:
<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>
上传
@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam(value = "name") String name,
@RequestParam(value = "email") String email,
@RequestParam(value = "age") int age,
@RequestParam(value = "job") String job,
@RequestParam(value = "header") MultipartFile header,
@RequestParam(value = "photos") MultipartFile[] photos) throws IOException {
log.info("name:{} email:{} age:{} job:{}",name,email, age,job);
String classpath = ResourceUtils.getURL("classpath:").getPath();
File dir = new File(classpath + WebUtils.getUploadFileDirectory());
if(!dir.exists()) {
dir.mkdirs();
}
if(!header.isEmpty()) {
String fileName = UUID.randomUUID().toString() + "_"+ System.currentTimeMillis() + "_" +header.getOriginalFilename();
header.transferTo(new File( dir, fileName));
}
if(photos.length > 0) {
for (MultipartFile photo : photos) {
String fileName = UUID.randomUUID().toString() + "_"+ System.currentTimeMillis() + "_"+ photo.getOriginalFilename();
photo.transferTo(new File(dir, fileName));
}
}
return "success";
}
下载
@RequestMapping("/down/{fileName}")
public ResponseEntity<byte[]> down(HttpSession session, @PathVariable(name = "fileName") String fileName) throws Exception {
InputStream is = session.getServletContext().getResourceAsStream("/img/" + fileName);
byte[] bytes = new byte[is.available()];
is.read(bytes);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Disposition","attachment;filename="+fileName);
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
return responseEntity;
}
9、异常处理
默认情况下,Spring Boot 提供/error 处理所有错误的映射,对于机器客户端,它将生成 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信 息。对于浏览器客户端,响应一个"whitelabel"错误视图,以 HTML 格式呈现相同的数据。
9.1 自定义异常页面
如果发生404错误优先返回404.html,如果没有则返回4xx.html,都没有则默认方式返回错误。
9.2 全局异常
@ControllerAdvice+@ExceptionHandler 处理全局异常,底层是 ExceptionHandlerExceptionResolver 支持
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class})
public String handleArithException(){
//自定义处理异常代码
return "error/global";
}
}
9.3 自定异常
- 如果 Spring Boot 提供的异常不能满足开发需求,程序员也可以自定义异常
- @ResponseStatus+自定义异常
- 底层是 ResponseStatusExceptionResolver ,底层调用 response.sendError(statusCode, resolvedReason)
当抛出自定义异常后,仍然会根据状态码,去匹配使用 x.html
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException{
public AccessException() {
}
public AccessException(String mes) {
super(mes);
}
}
10、 注入 Servlet、Filter、Listener
考虑到实际开发业务非常复杂和兼容,Spring-Boot 支持将 Servlet、Filter、Listener 注入 Spring 容器, 成为 Spring bean
@ServletComponentScan(basePackages = "com.jyu.usersys")
10.1 Servlet
@WebServlet(urlPatterns = {"/servlet01", "/servlet02"})
, //不会被 Spring-Boot 拦截器拦截
示例:
/**
* 解读
* 1. 通过继承 HttpServlet 来开发原生的Servlet
* 2. @WebServlet 标识将 Servlet_ 对象/bean注入到容器
* 3. (urlPatterns = {"/servlet01","/servlet02"} ,对servlet配置了url-pattern
* 4. 提示: 注入的原生的Servlet_ 不会被spring-boot的拦截器拦截
* 5. 对于开发的原生的Servlet ,需要使用 @ServletComponentScan指定要扫描的原生Servlet包
* , 才会注入到spring 容器.
*/
@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_!");
}
}
10.2 Filter
@WebFilter(urlPatterns = {"/css/", "/images/"})
,//过滤器配置的 urlPatterns,也会经过 Spring-Boot 拦截器
示例:
/**
* 解读
* 1. @WebFilter 表示Filter_是一个过滤器,并注入容器
* 2. urlPatterns = {"/css/*", "/images/*"} 当请求 /css/目录资源或者 /images/目录下资源的时候,会经过该过滤器
* 3. 是直接放行后,在经过拦截器, 拦截器是否拦截要根据拦截器的拦截规则
*/
@Slf4j
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class Filter_ implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("--Filter_ init--");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("--Filter_ doFilter--");
//为了方便观察过滤器处理的资源,我们输出一个uri
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
log.info("过滤器处理的uri={}", httpServletRequest.getRequestURI());
//我们直接放行-实际开发中,根据自己的业务来决定如何处理
chain.doFilter(request, response);
}
@Override
public void destroy() {
log.info("--Filter_ destroy--");
}
}
10.3 Listener
@WebListener
示例:
@Slf4j
@WebListener
public class Listener_ implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//这里可以加入项目初始化的相关业务代码
log.info("Listener_ contextInitialized 项目初始化OK~");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//这里可以加入相应代码...
log.info("Listener_ contextDestroyed 项目销毁");
}
}
也可以使用 RegistrationBean 方式注入
11、 内置 Tomcat 配置和切换
11.0 application.yml
常见配置以tomcat为例:
server:
# 端口
port: 999
tomcat:
threads:
# 最大工作线程数
max: 10
# 最小工作线程数
min-spare: 5
# tomcat 启动的线程数达到最大时,接受排队的请求个数,默认值为 100
accept-count: 200
# 最大连接数
max-connections: 2000
# 建立连接超时时间毫秒
connection-timeout: 10000
SpringBoot 支持的 webServer: Tomcat, Jetty, or Undertow
11.1 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<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>
12、 数据库操作
12.1 HikariDataSource
12.1.1 pom.xml
<!--进行数据库开发,引入 data-jdbc starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
12.1.2 application.yml
spring:
#数据源配置
datasource:
#说明: 如果没有使用useSSL=true, 会报红警告
url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: ysk
driver-class-name: com.mysql.jdbc.Driver
hikari:
# 连接池名
pool-name: JYU_Hikari_Pol
# 最小空闲连接
minimum-idle: 5
# 空闲连接最大存活时间 默认 600000(10 分钟)
idle-timeout: 600000
# 最大连接数,默认是 10
maximum-pool-size: 10
# 从连接池返回的连接 自动提交
auto-commit: true
# 连接最长存活时间 0 表示永久存活,默认 1800000(30 分钟)
max-lifetime: 1800000
# 连接超时时间,默认30000(30秒)
connection-timeout: 30000
# 测试链接是否可用的查询语句
connection-test-query: select 1
1 JDBC+HikariDataSource
HikariDataSource : 目前市面上非常优秀的数据源, 是 springboot2
12.2 DruidDataSource
常见问题 · alibaba/druid Wiki (github.com)
Druid: 性能优秀,Druid 提供性能卓越的连接池功能外【Java 基础】,还集成了 SQL 监 控,黑名单拦截等功能,强大的监控特性,通过 Druid 提供的监控功能,可以清楚知道连 接池和 SQL 的工作情况,所以根据项目需要,我们也要掌握 Druid 和 SpringBoot 整合
12.2.1 pom.xml
<!--引入druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
12.2.2 DataSource
@Configuration
public class DruidDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
}
12.3 DruidDataSource数据监控功能
12.3.1 使用内置Druid的内置监控页面
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean
= new ServletRegistrationBean<>(statViewServlet, "/druid/*");
registrationBean.addInitParameter("loginUsername","jyu");
registrationBean.addInitParameter("loginPassword","ysk123");
return registrationBean;
}
13.3.2 sql统计监控信息和防火墙
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setFilters("stat, wall");
return druidDataSource;
}
13.3.3 web监控
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter>
registrationBean = new FilterRegistrationBean<>(webStatFilter);
registrationBean.setUrlPatterns(Arrays.asList("/*"));
registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
13、Druid-spring-boot-start
pom.xml
<!-- 引入druid场景启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
application.yml
spring:
servlet:
multipart:
max-request-size: 50MB
max-file-size: 5MB
mvc:
hiddenmethod:
filter:
enabled: true
contentnegotiation:
favor-parameter: true
parameter-name: format
datasource:
username: root
password: ysk
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/furn_ssm?useUnicode=true&characterEncoding=utf-8&useSSL=true
druid:
#启动druid监控页面
stat-view-servlet:
enabled: true
login-username: jyu
login-password: 123ysk
reset-enable: false
#启动druid的web监控
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
#启动druid的sql监控
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
#启动防火墙
wall:
enabled: true
config:
drop-table-allow: false
select-all-column-allow: false
enabled: true
config:
drop-table-allow: false
select-all-column-allow: false
14、整合Mybatis
pom.xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
application.yml
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.jyu.jdbc.bean
configuration:
map-underscore-to-camel-case: true
XML 映射语句的示例
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
@MapperScan() 注解
@MapperScan("com.jyu.furn.mapper")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
15、整合Mybatis-Plus
pom.xml
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
定义了很多CRUD,可用于单表操作,
- Mapper接口可以extend BaseMapper
- Service接口可以extend IService
- Service实现类可以继承ServiceImpl<xxMapper.class,xx.class>
15.1 Result类
/**
* @author xiaoyu
* @version 1.0
*/
public class Result<T>{
private String code; //状态码 200:成功, 400:失败
private String msg; //对状态的说明
private T data; //返回数据
public static <T> Result<T> success(T data) {
Result<T> res = new Result<>(data);
res.setMsg("success");
res.setCode("200");
return res;
}
public static Result success() {
Result result = new Result();
result.setCode("200");
result.setMsg("success");
return result;
}
public static <T> Result<T> error(String code, String msg, T data) {
Result<T> res = new Result<>(data);
res.setMsg(msg);
res.setCode(code);
return res;
}
public static Result error(String code,String msg) {
Result res = new Result();
res.setCode(code);
res.setMsg(msg);
return res;
}
public Result() {
}
public Result(T data) {
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
15.2 分页案例
配置类
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
案例
@GetMapping("/list")
public Result<Page<Furn>> list(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String search) {
Page<Furn> page;
if (StringUtils.hasText(search)) { //判断查询值是否为空
LambdaQueryWrapper<Furn> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(Furn::getName,search);
page = furnService.page(new Page<>(pageNum, pageSize), queryWrapper);
} else {
page = furnService.page(new Page<>(pageNum, pageSize));
}
return Result.success(page);
}
自定义SpringBoot默认MVC配置
自定义SpringBoot默认MVC配置?好几个坑,这篇文章必须珍藏🙇♂️
WebMvcConfigurer
会对Spring Boot默认的MVC配置进行顶替。一旦进行顶替,Spring Boot默认提供的那些约定优于配置的功能可能就会失效,比如静态资源访问不到、返回数据不成功、参数绑定失效的问题。
声明式事务
声明式事务:该事务是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或回滚事务。
@Transtional(propagation=Propagation.REQUIRED, isolation=Isolation.REPLEATED_READER, timeout=2)
-
timeout 表示超时回滚,如果一个事务执行的时间超过某个时间限制,就让该事务回滚
-
propagation表示事务传播机制
-
isolation表示事务隔离级别
事务传播机制
名称 | 意义 |
---|---|
REQUIRED | 如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新事务,并在自己的事务内运行。 |
REQUIRES_NEW | 当前方法必须启动事务,并在自己的事务内运行,如果有事务正在运行,就应该将其挂起。 |
SUPPORTS | 如果有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前方法不应在事务中运行,如果有事务正在运行,将它挂起 |
MANDATORY | 当前方法必须运行在事务中运行,如果没有事务正在运行,就抛出异常 |
NEVER | 当前方法不应该运行在事务中运行,如果有事务正在运行,就抛出异常 |
NESTED | 如果有事务在运行,当前方法就应该在这个事务嵌套的事务内运行,否则就启动一个新事务,并在自己的事务内运行。 |
事务隔离机制
多个连接开启各自事务,操作数据库中数据时,数据库系统负责隔离操作,保证各个连接获取数据的准确性。
——韩顺平循序渐进学java
名字 | 定义 | 纯白话巧记 |
---|---|---|
脏读(dirty read) | 一个事务读取到另一个事务未提交的改变。 | 读取到未提交的数据 |
不可重复读 (norepeatable read) | 一个事务中多次进行查询,由于其他已提交的事务进行修改或删除, 使其每次查询返回结果集不同。 | 前后多次读取,数据内容不一致 |
幻读 (phantom read) | 一个事务中多次进行查询,由于其他已提交的事务进行插入, 使其每次查询返回结果集不同。 | 前后多次读取,数据总量不一致 |
这篇文章推荐给第一次接触事务的同学,该文中主要列举了有关脏读、不可重复读、幻读的相关案例,能帮助新手更好的理解,值得一读。
【数据库】快速理解脏读、不可重复读、幻读_SunAlwaysOnline的博客-CSDN博客
MySQL版本
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(Read uncommitted) | ✔️ | ✔️ | ✔️ | 不加锁 |
读已提交(Read committed) | ❌ | ✔️ | ✔️ | 不加锁 |
可重复读(Repeatable read) | ❌ | ❌ | ❌ | 不加锁 |
可串行化(Serializable) | ❌ | ❌ | ❌ | 加锁 |
注意:SQL92 标准,MySQL数据库改进,解读了不可重复读的幻读问题。