SpringBoot
1. SpringBoot 简介
- 官方文档:https://spring.io/projects/spring-boot
- 中文文档:https://www.springcloud.cc/spring-boot.html
SpringBoot 就是一个 javaweb 开发框架,与 SpringMVC 类似,相较于其他框架好处,简化开发,约定大于配置,能够迅速开发 web 应用。
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,只需要进行各种配置而不需要自己去实现,这时候强大的配置功能成了优点;发展到一定程度后,根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架,之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡 约定大于配置,进而衍生出一些一站式的解决方案。
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。SpringBoot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring,更容易的集成各种常用的中间件,开源软件。
Spring Boot 基于Spring 开发,SpringBoot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速,敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。SpringBoot以 约定大于配置的核心思想,默认帮我们进行了很多设置,多数 SpringBoot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置,例如:Redis,MongoDB 等,SpringBoot 应用中这些第三方库几乎可以零配置的开箱即用。
简单来说 SpirngBoot 不是什么新的框架,它默认配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,SpringBoot 整合了所有的框架。生态完善。
主要优点:
- 为所有 Spring 开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化 web 项目
- 没有冗余代码生成和 xml 配置的要求
2. 微服务介绍
2.1 什么是微服务
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用框架已无法应对,分布式框架以及流行计算框架势在必行,需要一个治理系统确保框架有条不紊的演进。
微服务就是一种框架风格,要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合,可以通过 http 的方式进行互通,要说微服务架构,先说单体应用框架。
- 单一应用框架
所谓单体应用框架(all in one)是指我们将一个应用中的所有应用服务封装在一个应用中。
无论什么系统,都把数据库访问,web访问等各个功能放到一个 war 包中
- 优点:易于开发和测试,十分方便部署,当需要扩展时只需要将 war 复制多份然后放到多个服务器上再做个负载均衡就可以了
- 缺点:要修改一个非常小的地方都需要停掉整个服务重新打包,部署这个应用 war 包。特别是对于一个大型应用我们不可能把所有内容都放在一个应用里面,这样维护,分工合作都是问题。
- 垂直应用框架
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 web 框架(MVC)是关键 - 分布式服务框架
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 - 流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 - 微服务架构
all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合。需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
**好处:**节省了调用资源;每个功能元素的服务调度是一个可替换的,可独立升级的软件代码。
这篇文章阐述了什么是微服务:
- 原文地址:https://www.martinfowler.com/articles/microservices.html
- 翻译:
2.2 如何构建微服务
一个大型系统的微服务框架就像一个复杂交织的神经网络,每一个神经元素就是一个功能元素,它们各自完成自己的功能,然后通过 http 相互请求调用。比如一个电商系统,查缓存、连接数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,被微服务了,它们作为一个个微服务共同构建了一个庞大的系统,如果修改其中一个功能,只需要更新升级其中一个功能服务单元就可以。
但是这种庞大的系统架构给部署和运维带来很大的难度,于是 Spring 为我们带来了构建大型分布式微服务的全套,全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用 SpringBoot,可以快速构建一个应用
- 大型分布式网络服务的调用,可以由 SpringCloud 完成,实现分布式
- 在分布式中间,进行流式数据计算、批处理可以使用:SpringCloud data flow
- Spring 想清楚了从开始构建应用到大型分布式应用全流程方案
3. 第一个 SpringBoot 程序
3.1 官网创建项目
下载好的 zip 压缩包解压后,打开 idea import 就可以使用了
删掉无用的文件就行
启动测试访问 8080 端口就可以了,因为是初始化的项目,所以会访问 localhost:8080/error
新建文件夹 controller ,新建 HelloSpringBoot.java
@Controller
public class HelloSpringBoot {
@RequestMapping("/hello")
@ResponseBody
public String hello(){
return "first SprongBoot Project";
}
}
然后启动访问这个接口即可
3.2 idea 创建
至此创建完成
- 项目结构分析:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-01-helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-01-helloworld</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web 场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2.1 测试 HelloSpringBoot
- 在 主程序的同级目录下新建 controller,一定在同级目录下,否则识别不到
- HelloSpringBoot.java
@RestController
public class HelloSpringBoot {
@RequestMapping("/hello")
public String hello(){
return "Hello,World";
}
}
- 测试访问
3.3 将项目打成 jar 包
如果遇到错误,可以配置打包时跳过项目运行测试用例:
<!--
在工作中,很多情况下我们打包是不想执行测试用例的
可能是测试用例不完事,或是测试用例会影响数据库数据
跳过测试用例执
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--跳过项目运行测试用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
打包完成后,会在 target 目录下生成一个 jar 包
3.4 修改项目的端口号
3.5 更改启动时控制台显示的图案
在 resources 目录下新建一个 banner.txt 文件
- https://www.bootschool.net/ascii 这个网站可以生成,也可以上百度搜索拷贝到 banner.txt 中即可
4. 执行原理
4.1 pom.xml 文件中的jar包版本管理
- spring-boot-dependencies:核心依赖在父工程中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KjfKkGT-1628259661085)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617112226390.png)]
按住 Ctrl 点进去就可以
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6FcYB0aH-1628259661086)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617112257888.png)]
同样的方法再次点进去就可以看到大量的 jar 包的版本管理信息了。我们在写或者引入一些 SpringBoot 依赖的时候,不需要指定版本,因为有这些版本仓库。
- 启动器
启动器说白了就是 SpringBoot 的启动场景;比如 spring-boot-starter-web,他就会帮我们自动导入web环境的所有的依赖,springboot会将所有的功能场景都变成一个个的启动器。需要使用什么功能,就需要找到相对应的启动器就可以了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
4.2 启动类
//@SpringBootApplication:标注这个类是一个 springboot 的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
- 按住 Ctrl 点 @SpringBootApplication 进去源码
@SpringBootConfiguration:springboot的配置
@Configuration:spring配置类
@Component:说明这个也是一个 spring 组件
@EnableAutoConfiguration:自动配置
@AutoConfigurationPackage:自动配置包
@Import({Registrar.class}):自动配置`包注册`
@Import({AutoConfigurationImportSelector.class}):自动配置导入选择
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//首先通过加载器加载所有的配置
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S7Wi3eWW-1628259661087)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617123325396.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65yNFEJK-1628259661089)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617123656887.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kwl97iX1-1628259661090)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617124813243.png)]
总结:springboot 所有的自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
- springboot 在启动的时候从类路径下 /META-INF/spring.factories 获取指定的值;
- 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
- 以前我们需要自动配置的东西,现在springboot 帮我们做了!
- 整个 javaEE,解决方案和自动配置的东西都在 spring-boot-autoconfigure-X.X.X.RELEASE.jar这个包下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在很多的 XXXAutoConfiguration 的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置,@Configuration
- 有了自动配置类,就免去了手动编写配置注入功能组件等的工作
4.3 启动类的run方法
在springboot 中启动程序后不只是运行了一个main方法,而是开启了一个服务;
- SpringApplication,这个类做了四件事情
- 推断应用的类型是普通的项目还是 web 项目
- 查找并加载所有的可用的初始化器,设置 initializers 属性中
- 找出所有的应用程序监听器(全局存在),获取上下文处理 Bean,全面接管springmvc的配置设置到 listeners 属性中
- 推断并设置 main 方法的定义类,找到运行的主类
5. yaml 配置注入
在 SpringBoot 中使用一个全局的配置文件,配置文件的名称是固定的
- application.properties
key=value - application.yml
key:空格value
**配置文件作用:**修改 SpringBoot 自动配置的默认值,因为 SpringBoot 在底层都给我们自动配置好了。
5.1 yaml 基础语法
这种语言以数据作为中心,而不是以标记语言为重点。
以前的配置文件大多数都是用 xml 配置:
- xml
<server>
<port>8080<port>
</server>
- yaml
server:
prot: 8080
语法要求严格
- 空格不能省略
- 以缩进来控制层级关系
- 属性和值的大小写敏感
注意:
- 双引号不会转义字符串里面的特殊字符
name: “name \n aaa” 输出:name \n aaa- 单引号会转义特殊字符
name: “name \n aaa” 输出:name 换行 aaa
# 1. 普通值,int,boolean,string ...
字符串默认不用加引号
name: li
# 2. 对象,Map格式
student:
name: li
age: 20
# 行内写法
student: {name: li,age: 20}
# 3. 数组(List,Set)
pets:
- cat
- dog
- pig
# 行内写法
pets: [cat,dog,pig]
#修改SpringBoot默认端口号
server:
port: 8081
5.2 yaml 注入配置文件
创建一个 SpringBoot
- 编写实体类
@Component//注册bean 到容器中
public class Dog {
@Value("小黄")
private String name;
@Value("20")
private Integer age;
//省略 有无参构造,get/set 方法,toString方法
}
- 之前给 bean 注入属性值方法,@Value
@Component//注册bean 到容器中
public class Dog {
@Value("小黄")
private String name;
@Value("20")
private Integer age;
}
- 在 SpringBoot 的测试类下注入测试输出
@SpringBootTest
class Springboot02ConfigApplicationTests {
@Autowired//将 Dog 自动注入进来
Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
}
结果成功输出,这是以前属性注入值的方法 @Value
- 新建 Person.java 类较复杂,引用 Dog 类
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值映射到这个组件中
告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定
参数:prefix = "person":将配置文件中的 person 下面的所有属性一一对应
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
//省略 有无参构造,get/set方法,toString 方法
}
- 在 resources 下创建 application.yaml 配置文件进行注入。
person:
name: li
age: 20
happy: false
birthday: 2001/01/01
maps: {k1: v1,k2: v2}
lists:
- music
- dance
dog:
name: 小黄
age: 1
- 提示:SpringBoot 配置注解处理器没有找到,需要进入官方文档导入依赖
有的之后官网可能进不去
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- 配置好之后测试类测试
@SpringBootTest
class Springboot02ConfigApplicationTests {
@Autowired//将 person 自动注入进来
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7CwBUYt-1628259661093)(D:\notes\note\SpringBoot\SpringBoot.assets\image-20210130122808661.png)]
加载 .properties 配置文件
- 新建 prop.properties 文件
name=小黄
- Dog.java 类中注入
@PropertySource(value = "classpath:prop.properties")
@Component//注册bean 到容器中
public class Dog {
@Value("${name}")
private String name;
private Integer age;
//省略 有无参构造,get/set 方法,toString 方法
}
- 测试
输出中文会有乱码问题
File – setting – FileEncodings 进行设置
- 可以使用占位符
person:
#${random.int}
name: li${random.int}
#${person.birthday}
age: 20${person.birthday}_岁
happy: false
birthday: 2001/01/01
maps: {k1: v1,k2: v2}
lists:
- music
- dance
dog:
name: 小黄
age: 1
5.3 总结
@ConfigurationProperties | @Value | |
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定 | 支持 | 不支持 |
SPEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂性类型封装 | 支持 | 不支持 |
- @ConfigurationProperties只需要写一次即可, @Value每个字段都添加
- 松散绑定:yaml 中写的 last-name(实体类lastName),这个和 lastName 是一样的,- 后面跟着的字母默认是大写的,这就是松散绑定
- JSR303 数据校验,就是在字段增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yaml 可以封装对象,value 不支持
yaml properties 都可以获取值,但是推荐使用yaml
如果需要获取某个文件中的某一个值可以使用 @Value
但是如果编写一个 javaBean 来配置文件进行一一映射,直接使用 @ConfigurationProperties
6. JSR 303 数据校验
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
运行结果在控制台,如果配置文件中 name 不是电子邮件地址
default message [不是一个合法的电子邮件地址];
使用数据校验可以保证数据的正确性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-APCgBLWj-1628259661095)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617160101281.png)]
常见参数:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
7. 多配置文件
在主配置文件中编写的时候,文件名可以是 application-{profile}.properties/yaml,用来指定多个环境版本。
4 个位置优先级:
使用 properties 配置文件需要建立多个文件
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试; #我们启动SpringBoot,就可以看到已经切换到dev下的配置了; spring.profiles.active=dev
- yaml 的多文档
不需要建立多个文档
#选择激活那个模块
spring:
profiles:
active: test
---
server:
port: 8081
spring:
profiles:test
---
server:
port: 8082
spring:
profiles: dev #配置环境的名称
---
server:
port: 8083
spring:
profiles: prod #配置环境的名称
8. yaml配置文件原理
配置文件到底能写什么?
进入源码 spring.factories 文件,找到 HttpEncodingAutoConfiguratin.class 类进入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cLhnv2ae-1628259661099)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617202253592.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ll0LDkL6-1628259661100)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210617202545066.png)]
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
@ConditionalOnJava | 系统的Java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
自动装配的原理:
- SpringBoot 启动会加载大量的自动配置类
- 我们看我们需要的功能有没有在 SpringBoot默认写好的自动配置类当中
- 我们再看这个自动配置类中到底配置了那些组件;(只要我们要用的组件存在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
XXXAutoConfiguration:自动配置类;给容器中添加组件
XXXProperties:封装配置文件中相关属性在我们这配置文件中能配置的东西,都存在一个固定的规律 XXXAutoConfiguration:会有默认值,如果需要使用修改在-->XXXProperties文件中修改 @ConfigurationProperties(prefix = "server") 这里绑定配置文件 这样我们就可以使用自定义的配置了 每一个XXXAutoConfiguration 类都是容器中的一个组件,最后都加入到容器中;用他们来做自动配置; 每一个自动配置类可以进行自动配置功能; 根据当前不同的条件判断,决定这个配置类是否生效 一旦这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties 类中 获取的,这些类里面的每一个属性又是和配置文件绑定的; 所有在配置文件中能配置的属性都是在XXXProperties类中封装;配置文件能配置什么就可以参照某个功能对应的属性类 debug: true 可以通过这个查看哪些配置生效,那些不生效
9. 静态资源
9.1 通过 webjars 得到静态文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ecbRGYh7-1628259661100)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618091620969.png)]
找到WebMvcAutoConfiguration.class 类,并找到方法:
//添加静态资源方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//这个判断,如果在yaml中添加属性则不生效,有(spring.mvc.servlet.path="...")
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
//只要是 "/webjars/**" 都会去 "classpath:/META-INF/resources/webjars/" 这里面找
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
- 在这里可以通过maven的方式引入jquery 等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8zu9xwW-1628259661101)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618094739745.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVXbbBRn-1628259661102)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618095020090.png)]
9.2 通过路径访问resources下的文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03xq9oaO-1628259661103)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618100306688.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7P1kC6W-1628259661105)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618101343005.png)]
优先级:/resources > /static > /public
- 总结
- 在springboot 中,我们可以使用下面方式处理静态资源
- webjars
- public,static,/**,resources
- 优先级:resources > static > public
10. 简单网站开发
10.1 首页定制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aMRuRiya-1628259661106)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618114448364.png)]
10.2 网站图标
2.2.0之前的版本可以在源码中找到 setFavico 方法,之后找不见了,只需要将 favico.ico 文件放到静态资源路径下就可以使用了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rhHtRU3a-1628259661108)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618150128592.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5EmiZTca-1628259661109)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618150138082.png)]
11. 模板引擎
11.1 模板引擎介绍
前端交给我们的页面,是html页面。如果我们以前开发,我们需要把他们转换成jsp页面,jsp好处就是当我们查出一些数据转发到jsp页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写java代码,但是,我们现在的这种情况,SpringBoot 这个项目首先是以jar的方式,不是war,其次,我们用的还是嵌入式的Tomcat,所以,他现在默认是不支持jsp的。
SpringBoot 推荐可以使用模板引擎。其实 jsp 就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot推荐使用的Thymeleaf,模板引擎有很多,但他们的思想都是一样。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4d5l628p-1628259661110)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618150735748.png)]
模板引擎的作用就是我们来写一个页面模板,比如有些值是动态的,我们写一些表达式。而这些值,从哪来呢?我们来组装一些数据,我们把这些数据找到,然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是模板引擎,不管是jsp还是其他的,都是这个思想。只不过是模板引擎之间的语法有点不一样。
11.2 thymeleaf 引入
首先需要引入 thymelead
- Thymeleaf 官网:https://www.thymeleaf.org/
- Thymeleaf 在Github的主页:https://github.com/thymeleaf/thymeleaf
- Spring官方文档:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#using-boot-starter
从github 找到依赖引入到 springboot 项目中
<!--Thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yeHipOU-1628259661111)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618154346358.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1rMYQh0I-1628259661112)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618154519333.png)]
**总结:**只要需要使用thymeleaf,只需要导入对应的依赖就可以了,我们将html放在我们的templates目录
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
11.2 Thymeleaf 基本语法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-keQxKHIu-1628259661113)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618160545349.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qf5PDIw-1628259661114)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618163014887.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m01HLhM1-1628259661114)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210618163108894.png)]
12. 扩展MVC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8lE3dUz-1628259661115)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619080950616.png)]
如果我们要自己定制一个视图解析器,我们要做的就是编写一个 @Configuration 注解类,并且类型要为 WebMvcConfigurer,还不能标注 @EnableWebMvc 注解。
//如果想要自己定制一些功能,只要写这个组件,然后将他交给springboot,springboot会帮我们自动装配
//全部扩展springmvc
//首先注解为一个配置类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ViewResolver 实现了视图解析器接口类,我们就可以把它看作试图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义一个自己的视图解析器MyViewResolver
//实现了 ViewResolver 接口的类,我们就可以叫他视图解析器
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOJHB90v-1628259661116)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619083822991.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sV3WbSgG-1628259661117)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619083917697.png)]
这么多的自动配置,原理都是一样。
SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@Bean),如果有就用用户配置的,如果没有就用自动配置的;如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的结合起来。
在springboot中,有很多的 XXXX Configuration 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意看一下扩展了什么东西。
13. 国际化
- 在resources下建立文件夹 i18n,然后建立配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czMYFrmj-1628259661118)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619123935062.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbWaJ77D-1628259661118)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619124049253.png)]
- 在 application.properties,里面设置本地的国际化的真实位置
# 设置本地国际化文件的真实位置
spring.messages.basename=i18n.login
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omuHN7vx-1628259661119)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619125013389.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9z0KAJv-1628259661120)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619125510074.png)]
- 创建自己的国际化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nrSfmr6-1628259661122)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619125644720.png)]
- 创建一个类 MyLocaleResolver,并且注册到 spring 中使其生效
@Configuration
public class MyLocaleResolver implements LocaleResolver {
//自定义的国际化生效
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
//解析请求,国际化
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取请求中的语言参数
String lang = httpServletRequest.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认的
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(lang)){
String[] split = lang.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rwhvP0Uh-1628259661123)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619132725236.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKr6xDNZ-1628259661124)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210619132742306.png)]
总结:
- 需要配置 i18n 文件
- 如果需要在项目中进行按钮自动切换,我要我们自定义一个组件 LocaleResolver
- 将自己写的组件配置到 spring 容器中:@Bean
14. 使用拦截器
- 创建一个拦截器类实现 HandlerInterceptor 接口
/**
* 定义一个拦截器用于处理用户是否登录
* 首先需要继承 HandlerInterceptor 接口,实现执行前过滤,用于判断session是否为空
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object userInfo = request.getSession().getAttribute("userInfo");
if (userInfo == null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
- 将自己创建的拦截器注册到 spring 中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//接管拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").
excludePathPatterns("/index.html","/","/user/login","/css/**","/font/**","/img/**","/js/**");
}
}
15. 整合数据库
对于数据访问层,无论是 SQL,还是 NOSQL,SpringBoot 底层都是采用 Spring Data 的方式进行统一处理的。
SpringBoot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是Spring 中与SpringBoot、Spring Cloud 等齐名的知名项目。
Spring Data 官网:https://spring.io/projects/spring-data
SpringBoot 所有启动器链接:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools
15.1 JDBC
- 创建好一个 springboot 项目,然后导入需要的依赖,也可以在创建时候选择依赖信息
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 首先需要建立 application.yml 配置文件,修改springboot 默认的配置信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/studentgrade?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHW0kP9Z-1628259661125)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210620123049143.png)]
- 写具体的 Controller业务
@Controller
public class JDBCController {
//导入 JDBCTemplate 模板,可以直接使用
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping("/list")
@ResponseBody
//得到表里面的信息,没有实体类,所以可以存储到 Map 中
public List<Map<String, Object>> list(){
String sql = "select * from course";
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
return result;
}
//删除一条记录,通过 localhost:8080/delete/4 进行删除即可
@RequestMapping("/delete/{cno}")
@ResponseBody
public void delete(@PathVariable("cno") Integer cno){
String sql = "delete from course where cno = ?";
jdbcTemplate.update(sql,cno);
}
}
//在springboot test类中输出数据源的类型
@SpringBootTest
class Springboot05JdbcApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
class com.zaxxer.hikari.HikariDataSource //结果是这个,springboot 默认是 hikari
15.2 DruidDataSource 自定义数据源
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3PO、DBCP、PROXOOL 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 连接池和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
SpringBoot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Druid 都是当前 JavaWeb 上最优秀的数据源。
- 首先要引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 然后修改 application.yml 配置文件的最后一行
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/studentgrade?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
# 修改数据源类型为 Druid
type: com.alibaba.druid.pool.DruidDataSource
- 在 test 类中输出数据源的类型进行验证
@SpringBootTest
class Springboot05JdbcApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
结果:class com.alibaba.druid.pool.DruidDataSource
- 自定义配置 Druid
- 首先要修改 application.yml 文件,配置监听过滤等属性
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/studentgrade?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认是不注入这些的,需要自己绑定
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
#则导入log4j 依赖就行
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- 创建一个 DruidConfig 类
@Configuration
public class DruidConfig {
//绑定 application.yml 配置文件
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
//后台监控功能:web.xml ServletRegistrationBean,这个类会自动监控后台,生成一些可视化东西
//因为 springboot 内置了 servlet 容器,所有没有web.xml,替代方法:servletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet(){
//固定的代码,设置一个访问路径,一般为 /druid/
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录,账号密码配置,都是固定的
Map<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//登录的 key 是固定的,loginUsername,loginPassword
initParameters.put("loginPassword","123456");
//允许谁能访问
initParameters.put("allow","");//如果参数为空表示所有人可以访问,具体的具体能访问
//禁止谁能访问
//initParameters.put("li","192.168.1.1");//不重要
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
// 自己配置过滤器filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求
Map<String,String> initParameters = new HashMap<>();
//这些东西不进行统计
initParameters.put("exclusions","*.js,*.css,/druid/*");
return bean;
}
}
配置好之后可以通过 localhost:8080/druid 可以进入后台监控的页面,需要使用用户名密码进行登录,如果依赖中没有配置 log4j 的坐标是没办法看到效果,所以需要在 pom.xml 文件中进行 log4j 的引入
进入登录页面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v55xG83p-1628259661126)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210620134520843.png)]
这里可以看到刚才执行的sql语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyGtVG9U-1628259661127)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210620134604710.png)]
15.3 Mybatis 整合
- 首先整合mybatis 需要先导入依赖
<!--mybatis 整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--数据驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 创建实体类,mapper接口,service,mapper.xml 文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pfH0arl2-1628259661129)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210620162917946.png)]
- 在 application.properties 文件中配置数据源以及绑定Mapper.xml 文件
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/studentgrade?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 整合 mybatis
# 别名设置
mybatis.type-aliases-package=com.example.pojo
# 扫描 xml 文件
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
- 实体类
public class Course {
private Integer cno;
private String cname;
private Integer classHour;
//省略 get、set方法
}
- mapper接口
//这个注解表示是mybatis 的mapper
@Mapper
@Controller
public interface CourseMapper {
//得到全部
List<Course> getAll();
//删除
Integer delete(Integer cno);
}
- 服务层接口
public interface CourseService {
//得到全部
List<Course> getAll();
//删除
Integer delete(Integer cno);
}
- 服务层实现类
@Service
public class CourseServiceImpl implements CourseService {
@Autowired
CourseMapper courseMapper;
@Override
public List<Course> getAll() {
List<Course> all = courseMapper.getAll();
return all;
}
@Override
public Integer delete(Integer cno) {
return courseMapper.delete(cno);
}
}
- mapper.xml 文件
<?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.example.mapper.CourseMapper">
<select id="getAll" resultType="Course">
select * from course
</select>
<delete id="delete">
delete from course where cno = #{cno}
</delete>
</mapper>
- Controller 层代码
@Controller
public class CourseController {
@Autowired
CourseService courseService;
@RequestMapping("/getAll")
@ResponseBody
public List<Course> getAll(){
List<Course> all = courseService.getAll();
return all;
}
@RequestMapping("/delete/{cno}")
@ResponseBody
public String delete(@PathVariable("cno") Integer cno){
Integer delete = courseService.delete(cno);
if (delete > 0){
return "删除成功";
}
return "失败";
}
}
- 最后使用浏览器通过访问 localhost:8080/getAll 就可以得到表中的信息,通过 localhost:8080/delete/1 就可以实现删除操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3cQUG1rT-1628259661130)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210620163432544.png)]
16. SpringSecurity
主要是为了简化,之前使用过滤器、拦截器比较繁琐,所有衍生出一些框架用于简化开发。
在 Web 开发中,安全是第一位的。
SpringSecurity 是针对 Spring 项目的安全框架,也是 SpringBoot 底层安全模块默认的技术造型,他可以实现强大的 Web 安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security
模块,进行少量的配置,即可实现强大的安全管理。
- WebSecurityConfigurerAdapter:自定义 Security 策略,需要继承
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启 WebSecurity 模式,@EnableXXXXX就是开启某个功能
SpringSecurity 的两个主要目标是“认证(Authentication)”和“授权(访问控制Authorization)”,通用概念
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
There is no PasswordEncoder mapped for the id “null”
16.1 使用–认证授权
- 导入依赖
<!--导入 security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 编写配置类
//1. 开启 WebSecurity 模式
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//采用的链式编程
//2. 授权,重写 WebSecurityConfigurerAdapter 的 configure(HttpSecurity http) 方法
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,但是里面的功能页只有对应有权限的人才能访问
//请求授权的规则
// 采用链式编程来设置访问权限 hasRole 判断权限,permitAll() 表示所有权限
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限需要跳到登录页,需要开启登录的页面
http.formLogin();
//方式网站攻击工具,关闭 csrf 功能,登录失败肯定存在的原因
http.csrf().disable();
//注销,开启了注销功能,注销后回到主页
http.logout().logoutSuccessUrl("/");
}
//3. 认证
// springboot 2.1.x 可以直接使用的
// 如果不加编码会认为密码不安全,报错:There is no PasswordEncoder mapped for the id "null"
// spring security 5.0+ 中新增加了很多的加密方法
// 使用链式编程设置编码格式、用户名、密码、角色权限,可以使用 and() 连接多个
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//数据正常应该从数据库中读取,这里是从内存中读取
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("li").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3").and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
/** auth
* // enable in memory based authentication with a user named
* // "user" and "admin"
* .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }**/ 源码给与说明
16.2 thymeleaf 整合 security 实现界面的分角色显示模块
- 首先引入整合包
<!--整合 thymeleaf 和 security-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
- html 代码中引入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
- 编写代码
<!--如果未登录-->
<!--isAuthenticated() 判断用户是否登录 -->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果登录:用户名,注销-->
<div sec:authorize="isAuthenticated()">
<a class="item">
用户名: <span sec:authentication="name"></span>
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
</div>
<!--菜单根据用户的角色动态的实现-->
<div class="column" sec:authorize="hasRole('vip1')">
16.3 记住我
在 SecurityConfig 类的 onfigure(HttpSecurity http) 方法中添加如下一句代码
//开启记住我功能,有效期2周
http.rememberMe();
这样在登录页面下面会有一个选择框,如果选择则会记录登录的信息到 Cookie 中,就算关闭浏览器,重新访问也会生效的,有效期限 2周左右。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqjqHlk3-1628259661131)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629124158250.png)]
16.4 自定义的首页
在 SecurityConfig 类的 onfigure(HttpSecurity http) 方法中添加如下一句代码
这里的参数修改表示如果页面的name属性不是 username或者 password 则根据修改的值进行接受参数
http.formLogin().loginPage("/toLogin").usernameParameter("name").passwordParameter("word");
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSb5HSGd-1628259661132)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629131525067.png)]
17. Shiro
17.1 Shiro 简介
- 是一个 Java 的安全(权限)框架
- Shiro 可以非常容易的开发足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境
- 可以完成认证、授权、加密、会话管理、web 集成、缓存等
- 下载地址:http://shiro.apache.org/
17.2 功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXKFMfyB-1628259661132)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629160528790.png)]
- Authentication: 身份认证、登录,验证,验证用户是不是拥有相应的身份
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个角色是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限
- Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的 JavaSE 环境,也可以是 Web 环境
- Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储
- Web Support: Web 支持,可以非常容易的集成到 Web 环境
- Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
- Concurrency: Shiro 支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去
- Testing:提供测试的支持
- Run As: 允许一个用户假装另一个用户(如果他们允许) 的身份进行访问
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
17.3 Shiro 架构(外部)
从外部来看 Shiro,即从应用程序角度来观察如何使用 Shiro 完成工作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yROugk3-1628259661133)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629161651339.png)]
- subject:应用代码直接交互的对象是 subject,也就是说 Shiro 的对外 API 核心就是 Subject,Subject 代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等,与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者
- SecurityManager:安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互,并且它管理者所有的 Subject,可以看出它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 的 DispatcherServlet 的角色
- Realm: Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较,来确定用户的身份是否合法;也需要从 Realm 得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把 Realm 看成 DataSource;
17.4 Shiro 架构(内部)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1U9qjowF-1628259661134)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629162312228.png)]
- Subject: 任何可以与应用交互的”用户“
- Security Manager:相当于 SpringMVC 中的 DeispatcherServlet;是 Shiro 的心脏,所有具体的交互都通过 Security Manager 进行控制,它管理这所有的 Subject,且负责进行认证、授权、会话、及缓存的管理。
- Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了
- Authorizer:授权器,即访问控制器,用来注定主题是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
- Realm:可以有一个或者多个 realm,可以认为是安全实体数据源,即用于获取安全实体的,可以使用 JDBC 实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的 realm
- SessionManager:管理 Session 生命周期的组件,而Shiro并不仅仅可以用在web环境,也可以用在普通的 JavaSE 环境中
- CacheManager:缓存控制器,来管理如用户、角色、权限等缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;
- Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密,解密等。
17.5 SpringBoot 集成 Shiro
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sFw8bBmZ-1628259661135)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210629232238244.png)]
- 首先导入依赖
<!--
Subject 用户
SecurityManager 管理所有用户
Realm 链接数据
-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
- 编写 UserRealm 类
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了-----授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了-----认证doGetAuthorizationInfo");
return null;
}
}
- 创建 ShiroConfig 类
@Configuration
public class ShiroConfig {
//3. ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/**
* anno : 无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有 记住我 功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
//添加 Shiro 的内置过滤器
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
return bean;
}
//2. DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联 UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//1. 创建 realm 对象,需要自定义
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
17.6 登录的认证
认证信息主要是在 UserRealm 类中
- 首先在 Controller 中接受前端页面输入的用户名密码信息
@RequestMapping("/login")
public String login(String username,String password, Model model){
//1. 获取当前用户
Subject subject = SecurityUtils.getSubject();
//2. 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token); // 执行登录方法,如果没有异常就ok了
return "index";
} catch (UnknownAccountException e) { //用户名错误
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e){ // 密码错误
model.addAttribute("msg","密码错误");
return "login";
}
}
- 在 UserRealm 类中进行信息的认证,认证的信息自定义变量
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了-----认证doGetAuthorizationInfo");
//1. 用户名,密码 正常从数据库中取出,这里模拟使用
String name = "root";
String password = "123456";
// 这里的 (UsernamePasswordToken) authenticationToken; 对应于 Controller 中的封装好的 token
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(name)){
return null; //抛出异常 UnknownAccountException
}
//密码认证,shiro做,不让接触密码
return new SimpleAuthenticationInfo("",password,"");
}
- 在 UserRealm 类中进行信息的认证,认证的信息从数据库中取出用户名密码
首先需要测试连接好数据库,保证数据库可以使用
然后在认证
修改即可
@Autowired
UserService userService;
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了-----认证doGetAuthorizationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//连接数据库,从数据库中取出
User user = userService.queryUserByName(userToken.getUsername());
if (user == null){ // 登录失败
return null; //抛出异常 UnknownAccountException
}
//密码认证,shiro做,不让接触密码
return new SimpleAuthenticationInfo("",user.getUser_password(),"");
}
17.8 授权
- 首先在 ShiroConfig.java 类中 getShiroFilterFactoryBean() 方法中进行设置访问的权限
//添加 Shiro 的内置过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
// 授权,正常的情况下,没有授权会跳到未授权页面
filterMap.put("/user/add","perms[add]");
filterMap.put("/user/update","perms[update]");
/**
*
**/
// 设置未授权页面
bean.setUnauthorizedUrl("/noauth");
- 然后在 UserRealm.java 中授权部分设置授权的用户信息
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了-----授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();//拿到 User 对象
//设置当前用户的权限,getUser_perms 从数据库中拿到权限信息 用于判断
info.addStringPermission(currentUser.getUser_perms());
return info;
}
前提需要在认证部分返回 principal 的值,用户获得当前用户的信息
return new SimpleAuthenticationInfo(user,user.getUser_password(),"");
17.9 Shiro 整合 thymeleaf
- 首先需要引入整合的依赖
<!--整合 shiro - thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- html 页面中引入命名空间
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
- 然后编写分模块
判断是否登录,如果没有登录过就显示首页,点击首页可以登录
<div th:if="${session.userInfo == null}">
<a th:href="@{/toLogin}"><h1>首页</h1></a>
</div>
<p th:text="${msg}"></p>
<hr>
获得 shiro:hasPermission 权限,进行区分,如果只有 add 权限就显示 add,以此类推
<div shiro:hasPermission="add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="update">
<a th:href="@{/user/update}">update</a>
</div>
18. Swagger
18.1 简介
- 号称世界上最流行的 Api 框架;
- RestFul Api 文档在线自动生成工具 => Api 文档与 API 定义同步更新;
- 直接运行,可以在线测试 API 接口;
- 支持多种语言;
官网:https://swagger.io/
18.2 SpringBoot 集成 Swagger
- 新建 springboot-web 项目
- 导入相关依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
- 编写一个 hello 接口
- 集成Swagger2 的配置文件,开启 Swagger2
@Configuration
@EnableSwagger2 //开启 Swagger2
public class SwaggerConfig {
}
- 测试运行:http://localhost:8080/swagger-ui.html
可以看到一下界面信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YB9a1fsX-1628259661136)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630174307805.png)]
18.3 配置 Swagger 基本信息
@Configuration
@EnableSwagger2
public class SwaggerConfig {
//配置了 swagger 的 Docket 的 bean 实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
// 配置 Swagger 信息 ---- apiInfo
public ApiInfo apiInfo() {
Contact contact = new Contact("lishisen", "www.miss.com", "aaaa@qq.com");
return new ApiInfo(
"SpringBoot Swagger",
"一个接口的文档",
"v1.1",
"www.baidu.com",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>());
}
}
18.4 配置 Swagger 扫描的接口
//配置了 swagger 的 Docket 的 bean 实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
//RequestHandlerSelectors ,配置要扫描接口的方式
//basePackage:指定要扫描的包
//any():扫描全部
//none():不扫描
//withClassAnnotation: 扫描类上的注解,参数是一个注解的反射对象
//withMethodAnnotation:扫描方法上的注解
// .enable(false) 是否启用 swagger,如果为 false 不能访问swagger
.select().apis(RequestHandlerSelectors.basePackage("com.example.controller"))
//paths() 过滤什么路径
.paths(PathSelectors.ant("/com/example/**")).build();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzNDN7S3-1628259661137)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630192010023.png)]
因为设置扫描的 basePackage 和 paths 过滤的冲突了,所以上面没有信息输出。
18.5 配置多环境下使用 Swagger
- 首先创建两套环境
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6kUIIYX-1628259661138)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630213605117.png)] - 设置环境选择的文件,并且判断是否启用 swagger
//配置了 swagger 的 Docket 的 bean 实例
@Bean
public Docket docket(Environment environment) {
// 设置要显示的 swawgger 环境
Profiles profiles = Profiles.of("dev", "pro1");
// 通过 environment.acceptsProfiles 判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.enable(flag)
.select().apis(RequestHandlerSelectors.basePackage("com.example.controller"))
//paths() 过滤什么路径
// .paths(PathSelectors.ant("/com/example/**"))
.build();
}
18.6 配置 API 文档的分组
在 docket 类中进行设置
.groupName("myGroup")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWFQQcST-1628259661139)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630214057788.png)]
- 设置多个分组
// 配置多个 swagger ,查看分组
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("docket1");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("docket2");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("docket3");
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nmxyL0Js-1628259661140)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630214536045.png)]
18.7 Swagger 文档说明
- 编写接口
// 只要接口返回的实体类,就会被记录到 Swagger 的Model中
@GetMapping("/user")
// Operation 接口,放在方法上,说明方法作用
@ApiOperation("hello 控制方法")
// @ApiParam("") 可以放在方法形参上,说明参数
public User user(@ApiParam("用户名") String username){
return new User();
}
还有实体类可以使用
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("用户密码")
private String password;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EguGehQA-1628259661141)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630220448554.png)]
总结:
我们可以通过 Swagger 给一些比较难以理解的属性或者接口,增加注释信息;接口文档实时更新;可以在线测试;
【注意】:在正式发布的时候,关闭 Swagger,处于安全考虑,节省运行的内存;
19. 任务
19.1 异步任务
- 模拟异步数据,分别创建两个类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LnXpO8gC-1628259661142)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630223704453.png)]
- AsynService
@Service
public class AsynService {
public void hello(){
try {
// 休眠 3秒后在执行下面内容
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理中.....");
}
}
- HelloController
@Controller
public class HelloController {
@Autowired
AsynService asynService;
@RequestMapping("/hello")
@ResponseBody
public String hello(){
asynService.hello();
return "OK";
}
}
- 启动运行后 3 秒后才输出内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Za5QMX1c-1628259661143)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210630223905276.png)]
**解决等待时间问题 : **
- 在 Service 方法上添加注解 @Async
@Service
public class AsynService {
@Async //告诉 spring 这是一个异步方法
public void hello(){
- 在启动类上添加注解 @EnableAsync 启动异步任务
@EnableAsync // 开启异步
19.2 邮件任务
- 首先需要导入依赖
<!--邮件发送-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 通过查看源码 MailSenderAutoConfiguration.class,MailProperties.class 得到需要的属性文件,然后编写配置文件
spring.mail.username=1876419736@qq.com
spring.mail.password=kvprsezzhjfbjgbi
# 发送的服务器
spring.mail.host=smtp.qq.com
# 开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuZtDI39-1628259661144)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701091930801.png)]
- 测试
@SpringBootTest
class Springboot12TaskApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
// 一个简单的邮件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("你好");
simpleMailMessage.setText("好好学习 JAVAEE");
simpleMailMessage.setTo("1876419736@qq.com");
simpleMailMessage.setFrom("1876419736@qq.com");
mailSender.send(simpleMailMessage);
}
@Test
void contextLoads2() throws MessagingException {
// 一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("nihao");
helper.setText("<p style='color:red'>好好学习</p>",true);
// 附件
helper.addAttachment("1.jpg",new File("C:\\Users\\lishisen\\Desktop\\1.jpg"));
helper.setTo("1876419736@qq.com");
helper.setFrom("1876419736@qq.com");
mailSender.send(mimeMessage);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAPDQSed-1628259661144)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701093433472.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Buuh6HnR-1628259661145)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701093446651.png)]
19.3 定时任务
TaskExecutor 任务执行者
TaskScheduler 任务调度者
@EnableScheduling //开启定时功能的注解
@Scheduled // 什么时候执行
- 在 Test 类上开启定时功能
@SpringBootApplication
@EnableScheduling//开启定时功能
public class Springboot12TaskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot12TaskApplication.class, args);
}
}
- 编写定时功能的服务
@Service
public class ScheduledService {
// 在一个特定的时间执行这个方法
// cron 表达式 秒 分 时 日 月 周
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello,你被执行了.....");
}
}
20. 分布式
什么是分布式系统?
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统;
分布式系统是由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system) 是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的 CPU) 高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
- Dubbo 文档
官网:https://dubbo.apache.org/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ACZkkCY-1628259661146)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701101949736.png)] - RPC
什么是 RPC?
RPC(Remote Rroducure Call) 是指远程过程调用,是一种进程间通信方式,它是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序显示编程这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器 A,B,一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据,为什么要用 RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC 就是要像调用本地的函数一样去调远程函数;
解释RPC文章:https://www.jianshu.com/p/2accc2840a1b - RPC 基本原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HideiVTa-1628259661147)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701103111263.png)]
- 步骤解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uus4HrZZ-1628259661147)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701103150943.png)]
- RPC 两个核心模块:通讯,序列化
20.1 Dubbo
- 什么是 dubbo?
Apache Dubbo 是一款高性能、轻量级的开源 Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1johoMx-1628259661148)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701104344059.png)]
- 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
- 服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用
- 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
- Dubbo 环境搭建
点进 dubbo 官方文档,推荐我们使用 Zookeeper 注册中心
window 下安装 zookeeper
- 下载 http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/
- 运行 /bin/zkServer.cmd ,初次运行会报错,因为在 conf 中没有 zoo.cfg 配置文件,可能会闪退
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rh7U8gCx-1628259661149)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701125257576.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IVa8skLb-1628259661150)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701125324414.png)]
解决方法**:E:\Environment\zookeeper-3.4.14\conf**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iAgrYwUi-1628259661150)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701125410860.png)]
- zoo.cfg 配置文件
- dataDir=./ 临时数据存储的目录(可写相对路径)
- clientPort=2181 zookeeper 的端口号
- 使用 zkCli.cmd 测试
- ls / :列出 zookeeper 根下保存的所有结点
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
- create -e /li 123 : 创建一个 li 节点,值为 123
[zk: localhost:2181(CONNECTED) 1] create -e /li 123
Created /li
- get /li : 获取 /li 节点的值
[zk: localhost:2181(CONNECTED) 3] get /li
123 // 这里是自己设置的值
cZxid = 0x6
ctime = Thu Jul 01 13:02:52 CST 2021
mZxid = 0x6
mtime = Thu Jul 01 13:02:52 CST 2021
pZxid = 0x6
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x1001ab811750001
dataLength = 3
numChildren = 0
windows 下安装 dubbo-admin
dubbo 本身并不是一个服务软件,它其实就是一个 jar 包,能够帮你的 java 程序连接到 zookeeper,并利用 zookeeper 消费、提供服务。
但是为了让用户更好的管理监控众多的 dubbo 服务,官方提供了一个可视化的监控程序 dubbo-admin,不过这个监控即使不装也不影响使用。
- 下载 dubbo-admin
下载地址:https://github.com/apache/dubbo-admin/tree/master - 解压进入目录
修改 E:\Environment\dubbo-admin-master\dubbo-admin\src\main\resources \application.properties 指定 zookeeper 地址
server.port=7001
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest
# 注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
- 在项目目录下打包 dubbo-admin
mvn clean package -Dmaven.test.skip=true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MI1oLYt-1628259661151)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701132331726.png)]
成功后会在 target 目录下生成 jar 包文件 dubbo-admin-0.0.1-SNAPSHOT.jar
- cmd 中执行命令运行:
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
注意:zookeeper 的服务一定要打开
执行完毕后用浏览器访问 http://localhost:7001/,需要输入登录账户和密码,默认都为 root
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PO8WZOu-1628259661152)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701132914379.png)]
zookeeper : 注册中心
dubbo-admin:是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了
20.2 dubbo+zookeeper
- 首先创建两个项目
provider-service
和consumer-service
,并在 provider-service 写服务接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7r2ZuHtn-1628259661153)(C:\Users\lishisen\AppData\Roaming\Typora\typora-user-images\image-20210701152746409.png)] - 在
provider-service
项目中导入依赖,编写配置文件,编写服务
- 依赖导入
<!--导入依赖:Dubbo + Zookeeper-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--zkclient-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--日志会冲突,所以需要排除一些东西-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个 slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置文件
server.port=8081
# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 哪些服务要被注册
dubbo.scan.base-packages=com.example.service
- 编写服务内容
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
//zookeeper:服务注册与发现
@Service //可以被扫描到,在项目已启动就自动注册到注册中心
@Component //使用了 Dubbo 后尽量不要使用 Service 注解
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "你好啊!!!dubbo,zookeeper";
}
}
- 编写好以上内容后,启动
zkServer.cmd
- 执行 jar 包内容,访问 localhost:7001 查看监控信息,现在是没有的,需要启动一下编写的服务就可以看到服务的信息
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar