Merge branch 'master' of https://gitea.rikako.cc/rikako/rikako-note
This commit is contained in:
204
css/css.md
204
css/css.md
@@ -1,102 +1,102 @@
|
|||||||
# CSS
|
# CSS
|
||||||
- ## css选择器
|
- ## css选择器
|
||||||
- 根据标签名选择
|
- 根据标签名选择
|
||||||
```css
|
```css
|
||||||
<!--
|
<!--
|
||||||
多个标签类型之间可以用逗号隔开
|
多个标签类型之间可以用逗号隔开
|
||||||
-->
|
-->
|
||||||
h1,p,li {
|
h1,p,li {
|
||||||
color:red;
|
color:red;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 根据class进行选择
|
- 根据class进行选择
|
||||||
- 通过class属性来进行选择
|
- 通过class属性来进行选择
|
||||||
```css
|
```css
|
||||||
.disk-block {
|
.disk-block {
|
||||||
border:1px black dashed;
|
border:1px black dashed;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 通过标签名和class同时来进行选择
|
- 通过标签名和class同时来进行选择
|
||||||
```css
|
```css
|
||||||
li.pink-css,div.pink-css {
|
li.pink-css,div.pink-css {
|
||||||
color:pink;
|
color:pink;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 后代选择器
|
- 后代选择器
|
||||||
- 后代选择其会根据标签的位置来进行选择,多个标签之间通过空格隔开
|
- 后代选择其会根据标签的位置来进行选择,多个标签之间通过空格隔开
|
||||||
```css
|
```css
|
||||||
li em {
|
li em {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
li span.pink-style {
|
li span.pink-style {
|
||||||
color:pink;
|
color:pink;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 相邻兄弟选择器
|
- 相邻兄弟选择器
|
||||||
- 相邻兄弟选择器会选择其相邻的下一个兄弟节点
|
- 相邻兄弟选择器会选择其相邻的下一个兄弟节点
|
||||||
```css
|
```css
|
||||||
li + li {
|
li + li {
|
||||||
color:red;
|
color:red;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 子选择器
|
- 子选择器
|
||||||
- 相比与后代选择器,子选择器只会选择其节点的直接后代,间接的后代不会被选中
|
- 相比与后代选择器,子选择器只会选择其节点的直接后代,间接的后代不会被选中
|
||||||
```css
|
```css
|
||||||
li > em {
|
li > em {
|
||||||
color:pink;
|
color:pink;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## css使用方法
|
- ## css使用方法
|
||||||
- 引用外部css文件
|
- 引用外部css文件
|
||||||
```html
|
```html
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
</head>
|
</head>
|
||||||
```
|
```
|
||||||
- 内部样式
|
- 内部样式
|
||||||
```html
|
```html
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
p {
|
p {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
```
|
```
|
||||||
- 内联样式
|
- 内联样式
|
||||||
```html
|
```html
|
||||||
<p style="font-size:1.5em;color:red;">Hello</p>
|
<p style="font-size:1.5em;color:red;">Hello</p>
|
||||||
```
|
```
|
||||||
- ## 多条样式规则应用于同一个元素
|
- ## 多条样式规则应用于同一个元素
|
||||||
- 多个样式规则的选择器范围相同
|
- 多个样式规则的选择器范围相同
|
||||||
- 在选择其范围相同的情况下,后出现的选择器样式会覆盖先前出现的选择器样式
|
- 在选择其范围相同的情况下,后出现的选择器样式会覆盖先前出现的选择器样式
|
||||||
```css
|
```css
|
||||||
p {
|
p {
|
||||||
color:red;
|
color:red;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
color:blue;
|
color:blue;
|
||||||
}
|
}
|
||||||
/* 后出现的p样式会覆盖之前出现的p样式,最终颜色为blue */
|
/* 后出现的p样式会覆盖之前出现的p样式,最终颜色为blue */
|
||||||
```
|
```
|
||||||
- 多个样式规则的选择器范围不同
|
- 多个样式规则的选择器范围不同
|
||||||
- 如果多个样式的选择器范围不同,那么范围更小更特殊的选择器样式胜出
|
- 如果多个样式的选择器范围不同,那么范围更小更特殊的选择器样式胜出
|
||||||
```css
|
```css
|
||||||
.red-p {
|
.red-p {
|
||||||
color:red;
|
color:red;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
color:blue
|
color:blue
|
||||||
}
|
}
|
||||||
/* 此时类选择器比元素选择器更特殊,故而最终颜色为red */
|
/* 此时类选择器比元素选择器更特殊,故而最终颜色为red */
|
||||||
```
|
```
|
||||||
- 在同一个选择器中重复指定样式
|
- 在同一个选择器中重复指定样式
|
||||||
- 在同一选择器中重复指定样式,位于后面的样式会覆盖位于前面的样式
|
- 在同一选择器中重复指定样式,位于后面的样式会覆盖位于前面的样式
|
||||||
```css
|
```css
|
||||||
p {
|
p {
|
||||||
color:blue;
|
color:blue;
|
||||||
color:red;
|
color:red;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
114
java se/CompletableFuture.md
Normal file
114
java se/CompletableFuture.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# CompletableFuture
|
||||||
|
对于Future对象,需要调用其get方法来获取值,get方法会阻塞当前线程直到该值可获取。
|
||||||
|
而CompletableFuture实现了Future接口,并且其提供了其他机制来获取result。通过CompletableFuture可以注册一个callback,该回调会在result可获取时调用。
|
||||||
|
```java
|
||||||
|
CompletableFuture<String> f = . . .;
|
||||||
|
f.thenAccept(s -> Process the result string s);
|
||||||
|
```
|
||||||
|
**通过这种方法,就无需等待result处于可获取状态之后再对其进行处理。**
|
||||||
|
通常情况下,很少有方法返回类型为CompletableFuture,此时,需要自己指定返回类型。CompletableFuture使用方法如下:
|
||||||
|
```java
|
||||||
|
public CompletableFuture<String> readPage(URL url)
|
||||||
|
{
|
||||||
|
return CompletableFuture.supplyAsync(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new String(url.openStream().readAllBytes(), "UTF-8");
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}, executor);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> Compatable.supplyAsync方法接受一个Supplier而不是一个Callable,虽然它们都没有参数且返回一个T,但是Callable允许抛出Exception,但Supplier不许抛出Checked Exception。
|
||||||
|
> 并且,**再没有显式为supplyAsync方法指定Executor参数时,其会默认采用ForkJoinPool.commonPool()**
|
||||||
|
|
||||||
|
Complatable可以以两种方式执行结束:(1)是否有返回值(2)是否有未捕获异常。要处理以上两种情况,需要使用whenComplete方法。提供给whenComplete函数的方法将会和执行result(如果没有返回值则为null)与抛出异常exception(如果没有则为null)一起被调用。
|
||||||
|
```java
|
||||||
|
f.whenComplete((s, t) -> {
|
||||||
|
if (t == null)
|
||||||
|
{
|
||||||
|
Process the result s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Process the Throwable t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
CompletableFuture可以手动设置completable value。虽然CompletableFuture对象的完成状态在supplyAsync方法的task执行结束之后会被隐式设置为已完成,但是通过手动设置完成状态可以提供更大的灵活性,例如可以同时以两种方式进行计算,任一方式先计算结束则将CompletableFuture对象的完成状态设置为已完成:
|
||||||
|
```java
|
||||||
|
var f = new CompletableFuture<Integer>();
|
||||||
|
executor.execute(() ->
|
||||||
|
{
|
||||||
|
int n = workHard(arg);
|
||||||
|
f.complete(n);
|
||||||
|
});
|
||||||
|
executor.execute(() ->
|
||||||
|
{
|
||||||
|
int n = workSmart(arg);
|
||||||
|
f.complete(n);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
如果想要以抛出异常的方式将CompletableFuture对象的完成状态设置为已完成,可以调用completeExcetpionally方法:
|
||||||
|
```java
|
||||||
|
Throwable t = . . .;
|
||||||
|
f.completeExceptionally(t);
|
||||||
|
```
|
||||||
|
> 在不同的线程中调用同一个对象的complete或completeExceptionally方法是线程安全的,如果该completableFuture对象的完成状态为已完成,那么再次调用complete或completeExceptionally方法无效。
|
||||||
|
|
||||||
|
> ## Caution
|
||||||
|
> 并不像Future对象,CompletableFuture对象在调用其cancel方法时,并不会对其task执行的线程进行中断操作,而是**仅仅将completableFuture对象的完成状态设置为抛出CancellationException的已完成状态。**
|
||||||
|
> 因为对于一个CompletableFuture对象,可能并没有一个唯一的线程对应其task的执行(future对象对应task可能未在任何线程中执行,也可能在多个线程中执行)
|
||||||
|
|
||||||
|
## CompletableFuture组合
|
||||||
|
异步方法的非阻塞调用是通过callback来实现的,调用者注册一个action,该action在task被完成时调用。如果注册的action仍然是异步的,那么该action的下一个action位于另一个完全不同的callback中。
|
||||||
|
```java
|
||||||
|
f1.whenComplete((s,t)->{
|
||||||
|
CompletableFuture<> f2 = ...
|
||||||
|
f2.whencomplete((s,t)->{
|
||||||
|
//...
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
这样可能会造成callback hell的情况,并且异常处理也特别不方便,由此CompletableFuture提供了一种机制来组合多个CompletableFuture:
|
||||||
|
```java
|
||||||
|
CompletableFuture<String> contents = readPage(url);
|
||||||
|
CompletableFuture<List<URL>> imageURLs =
|
||||||
|
contents.thenApply(this::getLinks);
|
||||||
|
```
|
||||||
|
在completableFuture对象上调用thenApply方法,thenApply方法也不会阻塞,其会返回另一个completableFuture对象,并且,当第一个Future对象contents返回之后,返回结果会填充到getLinks方法的参数中。
|
||||||
|
### CompletableFuture常用方法
|
||||||
|
CompletableFuture组合可以有如下常用方法:
|
||||||
|
- thenApply(T -> U):对CompletableFuture对象的返回结果执行操作,并且产生一个返回值
|
||||||
|
- thenAccept(T -> void):类似thenApply,操作上一个future的返回值,但是返回类型为void
|
||||||
|
- handle(T,Throwable)->U:处理上一个future返回的result并且产生一个返回值
|
||||||
|
- thenCompose(T->CompletableFuture\<U\>):将上一个Future返回值作为参数传递,并且返回CompletableFuture\<U\>
|
||||||
|
- whenComplete(T,Throwable)->void:类似于handle,但是不产生返回值
|
||||||
|
- exceptionally(Throwable->T):处理异常并返回一个结果
|
||||||
|
- completeOnTimeout(T,long,TimeUnit):当超时时,将传入的参数T作为返回结果
|
||||||
|
- orTimeOut(long,TimeUnit):当超时时,产生一个TimeoutExcetpion作为结果
|
||||||
|
- thenRun(Runnable):执行该Runnable并且返回一个CompletableFuture<void>
|
||||||
|
如上的每一个方法都有另一个Async版本,如thenApplyAsync:
|
||||||
|
```java
|
||||||
|
CompletableFuture<U> future.thenApply(f);
|
||||||
|
CompletableFuture<U> future.thenApplyAsync(f);
|
||||||
|
```
|
||||||
|
thenApply中,会对future的返回结果执行f操作;而在thenApplyAsync中,对f操作的执行会在另一个线程中。
|
||||||
|
thenCompose通常用来连接两个返回类型都为CompletableFuture的方法。
|
||||||
|
|
||||||
|
### CompletableFuture Combine
|
||||||
|
- thenCombine(CompletableFuture\<U\>,(T,U)->V):执行两个future,并且通过f对两个future的返回值进行combine
|
||||||
|
- thenAcceptBoth(CompletableFuture\<U\>,(T,U)->Void):执行两个future,并通过f处理两个future的返回值,但是返回类型为void
|
||||||
|
- runAfterBoth(CompletableFuture\<?\>,Runnable):当两个future都执行完之后,执行Runnable
|
||||||
|
- applyToEither(CompletableFuture\<T\>,(T)-> U):当任一future执行完成,通过该result产生返回值
|
||||||
|
- acceptEither(CompletableFuture\<T\>,T->Void):类似applyToEither,但是返回值为void
|
||||||
|
- runAfterEither(CompletableFuture\<?\>,Runnable):在任一future执行完成之后,执行Runnable
|
||||||
|
- static allOf(CompletableFuture\<?\>...):在参数中所有future执行完成之后,返回的future处于完成状态
|
||||||
|
- static anyOf(CompletableFuture\<?\>...):在参数中任一future执行完成之后,返回的future处于完成状态
|
||||||
|
|
||||||
|
### completedFuture
|
||||||
|
CompletableFuture.completedFuture会返回一个已经执行完成的CompletableFuture对象,并且该future对象返回值为T
|
||||||
2160
mybatis/mybatis.md
2160
mybatis/mybatis.md
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
# binlog 日志
|
# binlog 日志
|
||||||
* ## binlog(二进制日志)
|
* ## binlog(二进制日志)
|
||||||
* 定义:binlog日志文件也称之为变更日志文件,记录了数据库记录的所有DDL、DML等数据库更新事件的语句,但是不包括任何没有更新数据的语句。
|
* 定义:binlog日志文件也称之为变更日志文件,记录了数据库记录的所有DDL、DML等数据库更新事件的语句,但是不包括任何没有更新数据的语句。
|
||||||
* 用途:
|
* 用途:
|
||||||
* 数据恢复:如果mysql数据库进程意外停止,可以通过binlog中的记录来恢复数据库中的数据
|
* 数据恢复:如果mysql数据库进程意外停止,可以通过binlog中的记录来恢复数据库中的数据
|
||||||
* 数据复制:可以通过binlog来实现mysql数据库的主从一致
|
* 数据复制:可以通过binlog来实现mysql数据库的主从一致
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
# 多版本并发控制
|
# 多版本并发控制
|
||||||
* ## 多版本并发控制解决的问题:
|
* ## 多版本并发控制解决的问题:
|
||||||
* 在多个事务并发访问资源时,可能会发生并发安全问题。为了解决多个事务同时对资进行读写的问题,可以采用如下解决方案:
|
* 在多个事务并发访问资源时,可能会发生并发安全问题。为了解决多个事务同时对资进行读写的问题,可以采用如下解决方案:
|
||||||
* 多事务同时对资源进行读取:并发安全
|
* 多事务同时对资源进行读取:并发安全
|
||||||
* 多事务同时对资源进行写操作:非并发安全,可以对写操作进行加锁来解决并发安全问题
|
* 多事务同时对资源进行写操作:非并发安全,可以对写操作进行加锁来解决并发安全问题
|
||||||
* 多事务同时对资源进行读和写操作:非并发安全
|
* 多事务同时对资源进行读和写操作:非并发安全
|
||||||
1. 对读操作和写操作都加锁:性能较差
|
1. 对读操作和写操作都加锁:性能较差
|
||||||
2. 通过MVCC机制来解决并发读写的问题,对写操作加锁,并用MVCC机制来使读操作读到先前的值
|
2. 通过MVCC机制来解决并发读写的问题,对写操作加锁,并用MVCC机制来使读操作读到先前的值
|
||||||
* ## 多版本并发控的概念:
|
* ## 多版本并发控的概念:
|
||||||
* MVCC通过数据库行的多个版本管理来实现数据库的并发控制。当一个写事务正在更新数据行的值时,通过MVCC机制,可以在另一个事物对数据行进行读操作时读取到被写事务更新之前的值。
|
* MVCC通过数据库行的多个版本管理来实现数据库的并发控制。当一个写事务正在更新数据行的值时,通过MVCC机制,可以在另一个事物对数据行进行读操作时读取到被写事务更新之前的值。
|
||||||
* 通过MVCC机制,可以更好的解决事务在读-写操作同时发生时的性能问题。MVCC可以避免在写事务时另一个读事务必须等待当前写事务释放排他锁,而是可以通过MVCC读取资源被写事务修改之前的值。
|
* 通过MVCC机制,可以更好的解决事务在读-写操作同时发生时的性能问题。MVCC可以避免在写事务时另一个读事务必须等待当前写事务释放排他锁,而是可以通过MVCC读取资源被写事务修改之前的值。
|
||||||
|
|
||||||
* ## mysql中读操作的种类:
|
* ## mysql中读操作的种类:
|
||||||
* 当前读:读取数据库中当前的值,为了解决并发安全问题,需要对读操作进行加锁操作,可以尝试加排他锁或者共享锁,当当前事务对资源进行写操作时,读事务会阻塞直到写事务释放锁:
|
* 当前读:读取数据库中当前的值,为了解决并发安全问题,需要对读操作进行加锁操作,可以尝试加排他锁或者共享锁,当当前事务对资源进行写操作时,读事务会阻塞直到写事务释放锁:
|
||||||
```mysql
|
```mysql
|
||||||
# 为select语句加上共享锁
|
# 为select语句加上共享锁
|
||||||
select * from user where... lock in share mode
|
select * from user where... lock in share mode
|
||||||
|
|
||||||
# 为select语句加上排他锁
|
# 为select语句加上排他锁
|
||||||
select * from user where... for update
|
select * from user where... for update
|
||||||
```
|
```
|
||||||
* 快照读:mysql中默认select语句的读取方式,当当前事务对资源进行读操作时,如果另一个事务正在对资源进行写操作,那么读操作并不会阻塞而是会读取资源被写事务修改之前的值
|
* 快照读:mysql中默认select语句的读取方式,当当前事务对资源进行读操作时,如果另一个事务正在对资源进行写操作,那么读操作并不会阻塞而是会读取资源被写事务修改之前的值
|
||||||
|
|
||||||
* ## MVCC原理
|
* ## MVCC原理
|
||||||
* 隐藏字段:对于使用innodb存储引擎的表,其聚簇索引包含两个隐藏字段:
|
* 隐藏字段:对于使用innodb存储引擎的表,其聚簇索引包含两个隐藏字段:
|
||||||
1. trx_id:每个事务对每条记录进行更改时,该trx_id字段都会记载最后一次修改该行记录的事务id
|
1. trx_id:每个事务对每条记录进行更改时,该trx_id字段都会记载最后一次修改该行记录的事务id
|
||||||
2. roll_pointer:指向undo log中关于修改该条数据的记录
|
2. roll_pointer:指向undo log中关于修改该条数据的记录
|
||||||
* ReadView:ReadView是MVCC机制中事务对数据进行快照读时产生的一个读视图。
|
* ReadView:ReadView是MVCC机制中事务对数据进行快照读时产生的一个读视图。
|
||||||
* ReadView原理:
|
* ReadView原理:
|
||||||
* 当事务开启时,会产生当前数据库系统的一个快照,innodb为每个事务构造了一个数组,用来维护当前系统中活跃事务的ID(活跃事务为当前开启了但是尚未提交的事务id)
|
* 当事务开启时,会产生当前数据库系统的一个快照,innodb为每个事务构造了一个数组,用来维护当前系统中活跃事务的ID(活跃事务为当前开启了但是尚未提交的事务id)
|
||||||
* ReadView中保存了如下信息:
|
* ReadView中保存了如下信息:
|
||||||
* creator_trx_id:创建当前ReadView的事务id
|
* creator_trx_id:创建当前ReadView的事务id
|
||||||
* trx_ids:记录创建失误时当前mysql系统中活跃的事务id集合(已经开始但是还没有被提交的事务id)
|
* trx_ids:记录创建失误时当前mysql系统中活跃的事务id集合(已经开始但是还没有被提交的事务id)
|
||||||
* up_limit_id:trx_ids中最小的事务id
|
* up_limit_id:trx_ids中最小的事务id
|
||||||
* low_limit_id:表示生成ReadView中当前系统应该分配给下一个事务的id
|
* low_limit_id:表示生成ReadView中当前系统应该分配给下一个事务的id
|
||||||
* MVCC仅针对读已提交和可重复读的情况,在读已提交的情况下,事务中每执行一次select操作,ReadView都会重复生成;而在可重复读的隔离级别下,事务仅仅会在第一次select操作时生成ReadView
|
* MVCC仅针对读已提交和可重复读的情况,在读已提交的情况下,事务中每执行一次select操作,ReadView都会重复生成;而在可重复读的隔离级别下,事务仅仅会在第一次select操作时生成ReadView
|
||||||
* MVCC细节:
|
* MVCC细节:
|
||||||
* 当select语句想要对一条记录中的数据进行读取时,首先会查看记录的trx_id是是否对当前事务的读操作是可见的,判断事务是否可见的标准如下所示:
|
* 当select语句想要对一条记录中的数据进行读取时,首先会查看记录的trx_id是是否对当前事务的读操作是可见的,判断事务是否可见的标准如下所示:
|
||||||
* 如果记录的trx_id大于low_limit_id,那么说明在创建ReadView时对记录进行修改的事务还没有被创建,当然修改对当前读事务来说是不可见的
|
* 如果记录的trx_id大于low_limit_id,那么说明在创建ReadView时对记录进行修改的事务还没有被创建,当然修改对当前读事务来说是不可见的
|
||||||
* 如果trx_id小于up_limit_id,那么说明对该记录进行修改的事务id小于创建ReadView时最小的活跃事务id,在创建ReadView时修改记录的事物已经被提交,修改对当前事务来说可见
|
* 如果trx_id小于up_limit_id,那么说明对该记录进行修改的事务id小于创建ReadView时最小的活跃事务id,在创建ReadView时修改记录的事物已经被提交,修改对当前事务来说可见
|
||||||
* 如果trx_id位于up_limit_id和low_limit_id之间,那么:
|
* 如果trx_id位于up_limit_id和low_limit_id之间,那么:
|
||||||
* trx_id如果与trx_ids中保存的某个活跃事务id相同,那么说明在创建ReadView时修改事务的id尚未被提交,修改对当前失误不可见
|
* trx_id如果与trx_ids中保存的某个活跃事务id相同,那么说明在创建ReadView时修改事务的id尚未被提交,修改对当前失误不可见
|
||||||
* 如果trx_id与trx_ids中每个活跃事物id都不相同,那么修改事务在创建ReadView事物之时已经被提交,修改对当前事务可见
|
* 如果trx_id与trx_ids中每个活跃事物id都不相同,那么修改事务在创建ReadView事物之时已经被提交,修改对当前事务可见
|
||||||
* 根据上述规则,如果想要读取的记录trx_id对当前事务来说可见,那么获取该事务id所对应的数据值;如果trx_id对当前ReadView来说不可见,那么沿着roll_pointer沿着undo log向前寻找,知道找到对当前ReadView可见的事务id;如果undo log中所有的事务id对当前ReadView来说都不可见,那么对当前事物来说数据表中该条记录并不可见
|
* 根据上述规则,如果想要读取的记录trx_id对当前事务来说可见,那么获取该事务id所对应的数据值;如果trx_id对当前ReadView来说不可见,那么沿着roll_pointer沿着undo log向前寻找,知道找到对当前ReadView可见的事务id;如果undo log中所有的事务id对当前ReadView来说都不可见,那么对当前事物来说数据表中该条记录并不可见
|
||||||
|
|
||||||
* ## MVCC与幻读问题
|
* ## MVCC与幻读问题
|
||||||
* MVCC仅在读已提交和可重复读的情况下起作用,而关于幻读问题,MVCC仅在可重复读的隔离级别下解决。
|
* MVCC仅在读已提交和可重复读的情况下起作用,而关于幻读问题,MVCC仅在可重复读的隔离级别下解决。
|
||||||
* 在可重复读的隔离级别下,ReadView仅仅在第一次select语句时生成,故而在同一事务中多次读取之间,其他事务插入了新数据,那么该新数据对应的trx_id在readView创建时应该处于活跃或者未创建的状态,故而对ReadView所对应事物来说,即使其他事务插入了新数据,那么新插入的数据也不可见。
|
* 在可重复读的隔离级别下,ReadView仅仅在第一次select语句时生成,故而在同一事务中多次读取之间,其他事务插入了新数据,那么该新数据对应的trx_id在readView创建时应该处于活跃或者未创建的状态,故而对ReadView所对应事物来说,即使其他事务插入了新数据,那么新插入的数据也不可见。
|
||||||
* 而在读已提交的隔离级别下,ReadView在每次读操作的情况下都会被创建,故而在两次读操作之间,如果新的数据被插入,那么新插入的数据对后一次读操作创建的ReadView来说是已经提交的数据,是可见的。故而MVCC在读已提交的隔离级别下并不能够解决幻读的问题。
|
* 而在读已提交的隔离级别下,ReadView在每次读操作的情况下都会被创建,故而在两次读操作之间,如果新的数据被插入,那么新插入的数据对后一次读操作创建的ReadView来说是已经提交的数据,是可见的。故而MVCC在读已提交的隔离级别下并不能够解决幻读的问题。
|
||||||
@@ -1,64 +1,64 @@
|
|||||||
# mysql锁
|
# mysql锁
|
||||||
* ## msyql中锁的分类
|
* ## msyql中锁的分类
|
||||||
* 从数据库操作的类型划分,mysql中的锁可以分为读锁/共享锁和写锁/排他锁
|
* 从数据库操作的类型划分,mysql中的锁可以分为读锁/共享锁和写锁/排他锁
|
||||||
* 读锁(S锁,Share)
|
* 读锁(S锁,Share)
|
||||||
* 写锁(X锁,Exclusive)
|
* 写锁(X锁,Exclusive)
|
||||||
* 写锁和读锁示例
|
* 写锁和读锁示例
|
||||||
```mysql
|
```mysql
|
||||||
# 为语句加上写锁
|
# 为语句加上写锁
|
||||||
select * from table_name for udpate
|
select * from table_name for udpate
|
||||||
|
|
||||||
# 为查询语句加上读锁
|
# 为查询语句加上读锁
|
||||||
select * from table_name lock in share mode
|
select * from table_name lock in share mode
|
||||||
# or
|
# or
|
||||||
select * from table_name for share(mysql 8.0新增语法)
|
select * from table_name for share(mysql 8.0新增语法)
|
||||||
```
|
```
|
||||||
* 在获取读锁或者写锁之后,只有当事务结束之后,获取到的读锁或者写锁才会被释放。在innodb中,for update或者for share语句只会为查询匹配的行数据上锁,并且,当事务结束或者回滚之后,会释放为数据行所加的排他锁或者共享锁
|
* 在获取读锁或者写锁之后,只有当事务结束之后,获取到的读锁或者写锁才会被释放。在innodb中,for update或者for share语句只会为查询匹配的行数据上锁,并且,当事务结束或者回滚之后,会释放为数据行所加的排他锁或者共享锁
|
||||||
* 在对mysql中数据进行写操作(UPDATE、INSERT、DELETE)时,会通过如下方式加锁:
|
* 在对mysql中数据进行写操作(UPDATE、INSERT、DELETE)时,会通过如下方式加锁:
|
||||||
* DELETE操作:对mysql表中数据执行DELETE操作,需要为要删除数据添加排他锁(X锁)
|
* DELETE操作:对mysql表中数据执行DELETE操作,需要为要删除数据添加排他锁(X锁)
|
||||||
* UPDATE操作:对mysql表中数据执行UPDATE操作分如下情况:
|
* UPDATE操作:对mysql表中数据执行UPDATE操作分如下情况:
|
||||||
* update操作不改变记录的键值,且更新的列在更新前后占用的空间未发生变化:
|
* update操作不改变记录的键值,且更新的列在更新前后占用的空间未发生变化:
|
||||||
* 此时会在B+数中定位到该条记录,并且为该条记录添加X锁,在修改完成之后释放X锁
|
* 此时会在B+数中定位到该条记录,并且为该条记录添加X锁,在修改完成之后释放X锁
|
||||||
* update操作没有修改键值,但是更新前后该条记录的某一列占用空间发生了变化:
|
* update操作没有修改键值,但是更新前后该条记录的某一列占用空间发生了变化:
|
||||||
* 此时,会先对原先的记录加上X锁,并进行DELETE操作,并且在DELETE操作后,通过INSERT操作并添加隐式锁来插入修改后的数据
|
* 此时,会先对原先的记录加上X锁,并进行DELETE操作,并且在DELETE操作后,通过INSERT操作并添加隐式锁来插入修改后的数据
|
||||||
* update操作修改了键值:
|
* update操作修改了键值:
|
||||||
* 同样,也会通过先DELETE再INSERT的操作来更新数据
|
* 同样,也会通过先DELETE再INSERT的操作来更新数据
|
||||||
* INSERT操作:通过添加隐式锁的操作来保证mysql中多事务的并发安全
|
* INSERT操作:通过添加隐式锁的操作来保证mysql中多事务的并发安全
|
||||||
* 从mysql数据库的粒度来对锁进行划分
|
* 从mysql数据库的粒度来对锁进行划分
|
||||||
* 表锁:
|
* 表锁:
|
||||||
* 表级锁会锁住mysql中的整张表,其粒度较大,受其影响并发性能较低
|
* 表级锁会锁住mysql中的整张表,其粒度较大,受其影响并发性能较低
|
||||||
* 表级锁是mysql中最基本的加锁策略,并不依赖于存储引擎,不管是innodb还是myisam都支持表级锁
|
* 表级锁是mysql中最基本的加锁策略,并不依赖于存储引擎,不管是innodb还是myisam都支持表级锁
|
||||||
* 表级锁的使用场景:
|
* 表级锁的使用场景:
|
||||||
* 通过的select或者update、delete、insert操作innodb并不会加表级锁,但是对于alter table、drop table这类ddl语句对表的结构进行修改时,需要对其表的元数据进行加锁(MDL),对元数据加锁的过程中,想要对该表中数据进行select、delete、update、insert的操作会被阻塞
|
* 通过的select或者update、delete、insert操作innodb并不会加表级锁,但是对于alter table、drop table这类ddl语句对表的结构进行修改时,需要对其表的元数据进行加锁(MDL),对元数据加锁的过程中,想要对该表中数据进行select、delete、update、insert的操作会被阻塞
|
||||||
* 对表锁进行加锁的语句:
|
* 对表锁进行加锁的语句:
|
||||||
```mysql
|
```mysql
|
||||||
# 对表锁进行加锁
|
# 对表锁进行加锁
|
||||||
# 在对表进行上锁操作后,无法再去读取其他未上锁的表
|
# 在对表进行上锁操作后,无法再去读取其他未上锁的表
|
||||||
lock tables table_name read/write
|
lock tables table_name read/write
|
||||||
|
|
||||||
# 对上锁的表进行释放操作
|
# 对上锁的表进行释放操作
|
||||||
unlock tables;
|
unlock tables;
|
||||||
```
|
```
|
||||||
* 对于myisam存储引擎,读操作之前会为涉及到的表加上读锁,并且在读操作执行完成之后释放上锁的表;而对于写操作,myisam存储引擎会在写操作执行之前为涉及到的表加上写锁。反之,innodb存储引擎在对数据进行查找和修改时不会在表级别加锁,而是会在更细的粒度上为行数据进行加锁,以此来提高程序的并发性能。
|
* 对于myisam存储引擎,读操作之前会为涉及到的表加上读锁,并且在读操作执行完成之后释放上锁的表;而对于写操作,myisam存储引擎会在写操作执行之前为涉及到的表加上写锁。反之,innodb存储引擎在对数据进行查找和修改时不会在表级别加锁,而是会在更细的粒度上为行数据进行加锁,以此来提高程序的并发性能。
|
||||||
* 元数据锁(MDL):对于表结构的修改(DDL操作),会对其元数据锁进行加锁,而对于该表的增删改操作,则是会默认加上元数据的读锁。故而在对表结构进行修改时,想要对表中数据进行増删改操作需要获取其元数据的读锁,此时对表数据的増删改操作会被阻塞。
|
* 元数据锁(MDL):对于表结构的修改(DDL操作),会对其元数据锁进行加锁,而对于该表的增删改操作,则是会默认加上元数据的读锁。故而在对表结构进行修改时,想要对表中数据进行増删改操作需要获取其元数据的读锁,此时对表数据的増删改操作会被阻塞。
|
||||||
* 意向锁:意向锁通常用来简化行级锁和表级锁是否兼容的判断操作。如果为一个表中某条数据加上行级锁,那么innodb存储引擎会自动的为该行记录所在的页和表加上页级和表级的意向锁,那么当接下来有事务想要对该表进行表级加锁操作时,就无需查看该表中所有数据来判断是否存在表中数据的锁和表级锁冲突。只需判断该表的意向锁和想要加上的表级锁是否冲突即可。
|
* 意向锁:意向锁通常用来简化行级锁和表级锁是否兼容的判断操作。如果为一个表中某条数据加上行级锁,那么innodb存储引擎会自动的为该行记录所在的页和表加上页级和表级的意向锁,那么当接下来有事务想要对该表进行表级加锁操作时,就无需查看该表中所有数据来判断是否存在表中数据的锁和表级锁冲突。只需判断该表的意向锁和想要加上的表级锁是否冲突即可。
|
||||||
* 行锁
|
* 行锁
|
||||||
* 相对与表锁,行锁的粒度更小,故而其并发度更高,并发性能更好。但是行锁可能会造成死锁问题,加锁的开销也更大。
|
* 相对与表锁,行锁的粒度更小,故而其并发度更高,并发性能更好。但是行锁可能会造成死锁问题,加锁的开销也更大。
|
||||||
* 是否支持行锁取决与存储引擎,比如myisam不支持行锁但是innodb却支持行锁
|
* 是否支持行锁取决与存储引擎,比如myisam不支持行锁但是innodb却支持行锁
|
||||||
* 行锁种类:
|
* 行锁种类:
|
||||||
* 记录锁:记录锁针对于单条记录,在一个事物中,如果存在update、delete等修改操作,其默认会获得想要修改的那条记录的记录锁,并且在执行修改操作之后才会被释放。若一个事务中对某条记录调用update操作,那么直到当前事务提交或者回滚前,若其他事务想要修改同一行记录,会进入阻塞状态,知道持有记录写锁的退出才能继续执行。
|
* 记录锁:记录锁针对于单条记录,在一个事物中,如果存在update、delete等修改操作,其默认会获得想要修改的那条记录的记录锁,并且在执行修改操作之后才会被释放。若一个事务中对某条记录调用update操作,那么直到当前事务提交或者回滚前,若其他事务想要修改同一行记录,会进入阻塞状态,知道持有记录写锁的退出才能继续执行。
|
||||||
* 间隙锁:间隙锁对记录的范围进行加锁。间隙锁是不冲突的,多个事务可以同时持有某一个范围的间隙锁。用间隙锁或者mvcc机制都能够解决死锁问题。但是间隙锁可能会导致死锁问题,如果多个事务各自持有自己范围的间隙锁,并同时向对方持有间隙锁的范围插入数据,此时两个事务都会等待对方释放间隙锁,在这种情况下会发生死锁问题。
|
* 间隙锁:间隙锁对记录的范围进行加锁。间隙锁是不冲突的,多个事务可以同时持有某一个范围的间隙锁。用间隙锁或者mvcc机制都能够解决死锁问题。但是间隙锁可能会导致死锁问题,如果多个事务各自持有自己范围的间隙锁,并同时向对方持有间隙锁的范围插入数据,此时两个事务都会等待对方释放间隙锁,在这种情况下会发生死锁问题。
|
||||||
* 临键锁(next-key lock):在锁住某条记录的同时也锁定某个范围内的数据,使之无法被其他事务插入数据
|
* 临键锁(next-key lock):在锁住某条记录的同时也锁定某个范围内的数据,使之无法被其他事务插入数据
|
||||||
* 页锁:在页面的粒度上进行上锁
|
* 页锁:在页面的粒度上进行上锁
|
||||||
* 在粒度上,不同层级的所的数量是有上限的,例如innodb,其优先会选用行锁来进行加锁。当行锁数量过多,占用空间超过表空间上限时,会进行锁升级的行为。行锁会升级为页面锁,此时锁的粒度会增加,锁花费的开销也会变小,但是粒度变大后并发性能也会变低。
|
* 在粒度上,不同层级的所的数量是有上限的,例如innodb,其优先会选用行锁来进行加锁。当行锁数量过多,占用空间超过表空间上限时,会进行锁升级的行为。行锁会升级为页面锁,此时锁的粒度会增加,锁花费的开销也会变小,但是粒度变大后并发性能也会变低。
|
||||||
* 通过对待锁的方式进行划分
|
* 通过对待锁的方式进行划分
|
||||||
* 乐观锁:乐观锁假设每次对数据进行修改时,其他事务不会访问数据,故而不会对数据正真的加锁,只是会在修改时检查数据是否被其他事务修改过。
|
* 乐观锁:乐观锁假设每次对数据进行修改时,其他事务不会访问数据,故而不会对数据正真的加锁,只是会在修改时检查数据是否被其他事务修改过。
|
||||||
* 乐观锁的实现方式:
|
* 乐观锁的实现方式:
|
||||||
* cas
|
* cas
|
||||||
* 版本号机制
|
* 版本号机制
|
||||||
* 悲观锁:
|
* 悲观锁:
|
||||||
* 悲观锁假设一个事务在对数据进行修改时,其他事务也会对数据进行修改,故而在每次修改数据时都会对要修改的数据进行加锁,悲观锁是通过mysql内部提供的锁机制来实现的
|
* 悲观锁假设一个事务在对数据进行修改时,其他事务也会对数据进行修改,故而在每次修改数据时都会对要修改的数据进行加锁,悲观锁是通过mysql内部提供的锁机制来实现的
|
||||||
* 通过加锁方式进行划分:
|
* 通过加锁方式进行划分:
|
||||||
* 隐式锁:隐式锁通常用于插入操作。在某一个事务在对所记录进行插入操作时,如果其他事务想要访问该条记录,会查看最后修改该条记录的事务id是否是活跃的事务,如果是活跃事务,那么其会帮助插入事务创建一个X锁并且让自己进入阻塞状态。
|
* 隐式锁:隐式锁通常用于插入操作。在某一个事务在对所记录进行插入操作时,如果其他事务想要访问该条记录,会查看最后修改该条记录的事务id是否是活跃的事务,如果是活跃事务,那么其会帮助插入事务创建一个X锁并且让自己进入阻塞状态。
|
||||||
* 隐式锁是一种延迟加锁的机制,只有当有其他事务想要访问未提交事务插入的记录时,隐式锁才会被创建。该机制能够有效减少创建锁的数量。
|
* 隐式锁是一种延迟加锁的机制,只有当有其他事务想要访问未提交事务插入的记录时,隐式锁才会被创建。该机制能够有效减少创建锁的数量。
|
||||||
* 显示锁:可以通过命令查看到的锁,通常会用for update、lock in share mode 或者 update、delete操作会生成显示锁
|
* 显示锁:可以通过命令查看到的锁,通常会用for update、lock in share mode 或者 update、delete操作会生成显示锁
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# redo log & undo log
|
# redo log & undo log
|
||||||
* ## undo log
|
* ## undo log
|
||||||
* undo log通常用于事务的回滚操作,用来保证事务的原子性。
|
* undo log通常用于事务的回滚操作,用来保证事务的原子性。
|
||||||
* 每当发生数据修改操作时(update、insert、delete),关于当前修改操作的相反操作会被记录到undo log中,通常用于在回滚时将数据回复到修改之前的值。
|
* 每当发生数据修改操作时(update、insert、delete),关于当前修改操作的相反操作会被记录到undo log中,通常用于在回滚时将数据回复到修改之前的值。
|
||||||
* undo log默认被记录到mysql的表空间中,因而对undo log进行追加时,对表中页数据进行修改时也会产生redo log,对undo log的追加会通过fsync操作持久化到redo log中。这样即使在一个事务尚未被提交或是回滚之前,mysql服务器崩溃,下次重启时,也可以通过redo log恢复对数据的修改和undo log的内容,回滚事务时也能将数据恢复到崩溃前尚未被事务修改的状态。
|
* undo log默认被记录到mysql的表空间中,因而对undo log进行追加时,对表中页数据进行修改时也会产生redo log,对undo log的追加会通过fsync操作持久化到redo log中。这样即使在一个事务尚未被提交或是回滚之前,mysql服务器崩溃,下次重启时,也可以通过redo log恢复对数据的修改和undo log的内容,回滚事务时也能将数据恢复到崩溃前尚未被事务修改的状态。
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
# mysql索引
|
# mysql索引
|
||||||
* ## mysql索引的分类
|
* ## mysql索引的分类
|
||||||
* 从功能逻辑上对索引进行分类:
|
* 从功能逻辑上对索引进行分类:
|
||||||
* 普通索引:只是用于提升查询效率,没有任何的附加约束
|
* 普通索引:只是用于提升查询效率,没有任何的附加约束
|
||||||
* 唯一性索引:通过unique关键字可以设定唯一性索引,其会限制该索引值必须是唯一的,但是允许为null
|
* 唯一性索引:通过unique关键字可以设定唯一性索引,其会限制该索引值必须是唯一的,但是允许为null
|
||||||
* 主键索引:特殊的唯一性索引,在唯一性索引的基础上,主键索引还增加了不为空的约束
|
* 主键索引:特殊的唯一性索引,在唯一性索引的基础上,主键索引还增加了不为空的约束
|
||||||
* 单列索引:作用在一个字段上的索引
|
* 单列索引:作用在一个字段上的索引
|
||||||
* 联合索引:作用于多个字段上的索引
|
* 联合索引:作用于多个字段上的索引
|
||||||
* ## 索引的创建、删除操作
|
* ## 索引的创建、删除操作
|
||||||
```mysql
|
```mysql
|
||||||
# 索引的创建方式
|
# 索引的创建方式
|
||||||
alter table table_name add [unique/index] idx_name (col_name...)
|
alter table table_name add [unique/index] idx_name (col_name...)
|
||||||
# or
|
# or
|
||||||
create [unique/index] on table_name(col_name...)
|
create [unique/index] on table_name(col_name...)
|
||||||
|
|
||||||
# 索引的删除方式
|
# 索引的删除方式
|
||||||
alter table table_name drop index idx_name
|
alter table table_name drop index idx_name
|
||||||
# or
|
# or
|
||||||
drop index idx_name on table_name
|
drop index idx_name on table_name
|
||||||
```
|
```
|
||||||
* ## 索引的可见性
|
* ## 索引的可见性
|
||||||
```mysql
|
```mysql
|
||||||
# 通过修改索引的可见性,可以比较创建
|
# 通过修改索引的可见性,可以比较创建
|
||||||
```
|
```
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,96 +1,96 @@
|
|||||||
- [Apache Shiro Authentication](#apache-shiro-authentication)
|
- [Apache Shiro Authentication](#apache-shiro-authentication)
|
||||||
- [Apache Shiro Authentication简介](#apache-shiro-authentication简介)
|
- [Apache Shiro Authentication简介](#apache-shiro-authentication简介)
|
||||||
- [Apache Authentication概念](#apache-authentication概念)
|
- [Apache Authentication概念](#apache-authentication概念)
|
||||||
- [subject](#subject)
|
- [subject](#subject)
|
||||||
- [principals](#principals)
|
- [principals](#principals)
|
||||||
- [credentials](#credentials)
|
- [credentials](#credentials)
|
||||||
- [realms](#realms)
|
- [realms](#realms)
|
||||||
- [Shiro Authentication过程](#shiro-authentication过程)
|
- [Shiro Authentication过程](#shiro-authentication过程)
|
||||||
- [Shiro框架的Authentication过程](#shiro框架的authentication过程)
|
- [Shiro框架的Authentication过程](#shiro框架的authentication过程)
|
||||||
- [收集用户的principals和credentials](#收集用户的principals和credentials)
|
- [收集用户的principals和credentials](#收集用户的principals和credentials)
|
||||||
- [将收集的principals和credentials提交给认证系统](#将收集的principals和credentials提交给认证系统)
|
- [将收集的principals和credentials提交给认证系统](#将收集的principals和credentials提交给认证系统)
|
||||||
- [身份认证后对访问进行allow/retry authentication/block](#身份认证后对访问进行allowretry-authenticationblock)
|
- [身份认证后对访问进行allow/retry authentication/block](#身份认证后对访问进行allowretry-authenticationblock)
|
||||||
- [rememberMe支持](#rememberme支持)
|
- [rememberMe支持](#rememberme支持)
|
||||||
- [remembered和authenticated的区别](#remembered和authenticated的区别)
|
- [remembered和authenticated的区别](#remembered和authenticated的区别)
|
||||||
- [logging out](#logging-out)
|
- [logging out](#logging-out)
|
||||||
|
|
||||||
# Apache Shiro Authentication
|
# Apache Shiro Authentication
|
||||||
## Apache Shiro Authentication简介
|
## Apache Shiro Authentication简介
|
||||||
Authentication是一个对用户进行身份认证的过程,在认证过程中用户需要向应用提供用于证明用户的凭据。
|
Authentication是一个对用户进行身份认证的过程,在认证过程中用户需要向应用提供用于证明用户的凭据。
|
||||||
## Apache Authentication概念
|
## Apache Authentication概念
|
||||||
### subject
|
### subject
|
||||||
在应用的角度,subject即是一个用户
|
在应用的角度,subject即是一个用户
|
||||||
### principals
|
### principals
|
||||||
主体,用于标识一个用户,可以是username、social security nubmer等
|
主体,用于标识一个用户,可以是username、social security nubmer等
|
||||||
### credentials
|
### credentials
|
||||||
凭据,在用户认证过程中用于认证用户的身份,可以是密码、生物识别数据(如指纹、面容等)
|
凭据,在用户认证过程中用于认证用户的身份,可以是密码、生物识别数据(如指纹、面容等)
|
||||||
### realms
|
### realms
|
||||||
专用于security的dao对象,用于和后端的datasource进行沟通。
|
专用于security的dao对象,用于和后端的datasource进行沟通。
|
||||||
## Shiro Authentication过程
|
## Shiro Authentication过程
|
||||||
### Shiro框架的Authentication过程
|
### Shiro框架的Authentication过程
|
||||||
1. 收集用户的principals和credentials
|
1. 收集用户的principals和credentials
|
||||||
2. 向应用的认证系统提交用户的principals和credentials
|
2. 向应用的认证系统提交用户的principals和credentials
|
||||||
3. 认证结束之后,根据认证结果允许访问、重试访问请求或者阻塞访问
|
3. 认证结束之后,根据认证结果允许访问、重试访问请求或者阻塞访问
|
||||||
### 收集用户的principals和credentials
|
### 收集用户的principals和credentials
|
||||||
可以通过UsernamePasswordToken来存储用户提交的username和password,并可以调用UsernamePasswordToken.rememberMe方法来启用Shiro的“remember-me”功能。
|
可以通过UsernamePasswordToken来存储用户提交的username和password,并可以调用UsernamePasswordToken.rememberMe方法来启用Shiro的“remember-me”功能。
|
||||||
```java
|
```java
|
||||||
//Example using most common scenario:
|
//Example using most common scenario:
|
||||||
//String username and password. Acquire in
|
//String username and password. Acquire in
|
||||||
//system-specific manner (HTTP request, GUI, etc)
|
//system-specific manner (HTTP request, GUI, etc)
|
||||||
UsernamePasswordToken token = new UsernamePasswordToken( username, password );
|
UsernamePasswordToken token = new UsernamePasswordToken( username, password );
|
||||||
|
|
||||||
//”Remember Me” built-in, just do this:
|
//”Remember Me” built-in, just do this:
|
||||||
token.setRememberMe(true);
|
token.setRememberMe(true);
|
||||||
```
|
```
|
||||||
### 将收集的principals和credentials提交给认证系统
|
### 将收集的principals和credentials提交给认证系统
|
||||||
在收集完用户的principals和credentials之后,需要将其提交给应用的认证系统。
|
在收集完用户的principals和credentials之后,需要将其提交给应用的认证系统。
|
||||||
在Shiro中,代表认证系统的是Realm,其从存放安全数据的datasource中获取数据,并且对用户提交的principals和credentials进行校验。
|
在Shiro中,代表认证系统的是Realm,其从存放安全数据的datasource中获取数据,并且对用户提交的principals和credentials进行校验。
|
||||||
在Shiro中,该过程用如下代码表示:
|
在Shiro中,该过程用如下代码表示:
|
||||||
```java
|
```java
|
||||||
//With most of Shiro, you'll always want to make sure you're working with the currently
|
//With most of Shiro, you'll always want to make sure you're working with the currently
|
||||||
//executing user, referred to as the subject
|
//executing user, referred to as the subject
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//Authenticate the subject by passing
|
//Authenticate the subject by passing
|
||||||
//the user name and password token
|
//the user name and password token
|
||||||
//into the login method
|
//into the login method
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
```
|
```
|
||||||
> 在Shiro中,subject可以被看做是用户,**在当前执行的线程中永远有一个subject与其相关联。**
|
> 在Shiro中,subject可以被看做是用户,**在当前执行的线程中永远有一个subject与其相关联。**
|
||||||
> **可以通过SecurityUtils.getSubject()方法来获取当前执行线程相关联的subject。**
|
> **可以通过SecurityUtils.getSubject()方法来获取当前执行线程相关联的subject。**
|
||||||
|
|
||||||
> 在获取当前执行线程关联subject之后,需要对当前subject进行身份认证,通过subject.login(token)来对用户提交的principals和credentials进行Authentication
|
> 在获取当前执行线程关联subject之后,需要对当前subject进行身份认证,通过subject.login(token)来对用户提交的principals和credentials进行Authentication
|
||||||
|
|
||||||
### 身份认证后对访问进行allow/retry authentication/block
|
### 身份认证后对访问进行allow/retry authentication/block
|
||||||
在调用subject.login(token)之后,如果身份认证成功,用户将在seesion的生命周期内维持他们的identity。但是如果身份认证失败,可以为抛出的异常指定不同的异常处理逻辑来定义登录失败之后的行为。
|
在调用subject.login(token)之后,如果身份认证成功,用户将在seesion的生命周期内维持他们的identity。但是如果身份认证失败,可以为抛出的异常指定不同的异常处理逻辑来定义登录失败之后的行为。
|
||||||
```java
|
```java
|
||||||
try {
|
try {
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
} catch ( UnknownAccountException uae ) { ...
|
} catch ( UnknownAccountException uae ) { ...
|
||||||
} catch ( IncorrectCredentialsException ice ) { ...
|
} catch ( IncorrectCredentialsException ice ) { ...
|
||||||
} catch ( LockedAccountException lae ) { ...
|
} catch ( LockedAccountException lae ) { ...
|
||||||
} catch ( ExcessiveAttemptsException eae ) { ...
|
} catch ( ExcessiveAttemptsException eae ) { ...
|
||||||
} ... your own ...
|
} ... your own ...
|
||||||
} catch ( AuthenticationException ae ) {
|
} catch ( AuthenticationException ae ) {
|
||||||
//unexpected error?
|
//unexpected error?
|
||||||
}
|
}
|
||||||
//No problems, show authenticated view…
|
//No problems, show authenticated view…
|
||||||
```
|
```
|
||||||
## rememberMe支持
|
## rememberMe支持
|
||||||
Apache Shiro除了正常的Authentication流程外,还支持rememberMe功能。
|
Apache Shiro除了正常的Authentication流程外,还支持rememberMe功能。
|
||||||
Shiro中Subject对象拥有两个方法,isRemembered()和isAuthenticated()。
|
Shiro中Subject对象拥有两个方法,isRemembered()和isAuthenticated()。
|
||||||
> - 一个remembered subject,其identity和principals自上次session成功认证后就被记住
|
> - 一个remembered subject,其identity和principals自上次session成功认证后就被记住
|
||||||
> - 一个authenticated subject,其identity只在本次会话中有效
|
> - 一个authenticated subject,其identity只在本次会话中有效
|
||||||
|
|
||||||
### remembered和authenticated的区别
|
### remembered和authenticated的区别
|
||||||
在Shiro中,一个remembered subject并不代表该subject已经被authenticated。如果一个subject被remembered,仅仅会向系统提示该subject可能是系统的某个用户,但是不会对subject的身份提供保证。但是如果subject被authenticated,该subject的identity在当前会话中已经被认证。
|
在Shiro中,一个remembered subject并不代表该subject已经被authenticated。如果一个subject被remembered,仅仅会向系统提示该subject可能是系统的某个用户,但是不会对subject的身份提供保证。但是如果subject被authenticated,该subject的identity在当前会话中已经被认证。
|
||||||
> 故而,isRemembered校验可以用来执行一些非敏感的操作,如用户自定义界面视图等。但是,敏感性操作如金额信息和变动操作等,必须通过isAuthenticated校验而不是isRemembered校验,敏感性操作的用户身份必须得到认证。
|
> 故而,isRemembered校验可以用来执行一些非敏感的操作,如用户自定义界面视图等。但是,敏感性操作如金额信息和变动操作等,必须通过isAuthenticated校验而不是isRemembered校验,敏感性操作的用户身份必须得到认证。
|
||||||
|
|
||||||
## logging out
|
## logging out
|
||||||
在Shiro中,登出操作可以通过如下代码实现
|
在Shiro中,登出操作可以通过如下代码实现
|
||||||
```java
|
```java
|
||||||
currentUser.logout(); //removes all identifying information and invalidates their session too.
|
currentUser.logout(); //removes all identifying information and invalidates their session too.
|
||||||
```
|
```
|
||||||
当执行登出操作时,Shiro会关闭当前session,并且会移除当前subject的任何identity。如果在web环境中使用rememberMe,logout默认会从浏览器中删除rememberMe cookie。
|
当执行登出操作时,Shiro会关闭当前session,并且会移除当前subject的任何identity。如果在web环境中使用rememberMe,logout默认会从浏览器中删除rememberMe cookie。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +1,94 @@
|
|||||||
- [Apache Shiro Authorization](#apache-shiro-authorization)
|
- [Apache Shiro Authorization](#apache-shiro-authorization)
|
||||||
- [Authorization简介](#authorization简介)
|
- [Authorization简介](#authorization简介)
|
||||||
- [Authorization的核心元素](#authorization的核心元素)
|
- [Authorization的核心元素](#authorization的核心元素)
|
||||||
- [Permission](#permission)
|
- [Permission](#permission)
|
||||||
- [权限粒度级别](#权限粒度级别)
|
- [权限粒度级别](#权限粒度级别)
|
||||||
- [Roles](#roles)
|
- [Roles](#roles)
|
||||||
- [Role分类](#role分类)
|
- [Role分类](#role分类)
|
||||||
- [User](#user)
|
- [User](#user)
|
||||||
- [在Apache Shiro中实行Authorization](#在apache-shiro中实行authorization)
|
- [在Apache Shiro中实行Authorization](#在apache-shiro中实行authorization)
|
||||||
- [通过java code实现authorization](#通过java-code实现authorization)
|
- [通过java code实现authorization](#通过java-code实现authorization)
|
||||||
- [基于String的权限鉴定](#基于string的权限鉴定)
|
- [基于String的权限鉴定](#基于string的权限鉴定)
|
||||||
- [通过注解实现Authorization](#通过注解实现authorization)
|
- [通过注解实现Authorization](#通过注解实现authorization)
|
||||||
|
|
||||||
# Apache Shiro Authorization
|
# Apache Shiro Authorization
|
||||||
## Authorization简介
|
## Authorization简介
|
||||||
Authorization(访问控制),分配访问某资源的特定权限。
|
Authorization(访问控制),分配访问某资源的特定权限。
|
||||||
## Authorization的核心元素
|
## Authorization的核心元素
|
||||||
### Permission
|
### Permission
|
||||||
Permission是最原子级别的安全策略,用来控制用户与应用进行交互时可以执行哪些操作。**格式良好的permission描述了资源的类型和与该资源交互时可以执行的操作。**
|
Permission是最原子级别的安全策略,用来控制用户与应用进行交互时可以执行哪些操作。**格式良好的permission描述了资源的类型和与该资源交互时可以执行的操作。**
|
||||||
对于与数据相关的资源,权限通常有create、read、update、delete(CRUD)。
|
对于与数据相关的资源,权限通常有create、read、update、delete(CRUD)。
|
||||||
#### 权限粒度级别
|
#### 权限粒度级别
|
||||||
在Shiro中,可以在任何粒度对permission进行定义。如下是permission粒度的一些定义:
|
在Shiro中,可以在任何粒度对permission进行定义。如下是permission粒度的一些定义:
|
||||||
1. Resource级别:该级别是最广泛和最容易构建的粒度级别,在该级别用户可以对资源执行特定的操作。**在Resource级别,该资源类型被指定,但是没有限制用户操作特定的资源实例(即用户可以对该Resource类型的所有实例进行操作)**
|
1. Resource级别:该级别是最广泛和最容易构建的粒度级别,在该级别用户可以对资源执行特定的操作。**在Resource级别,该资源类型被指定,但是没有限制用户操作特定的资源实例(即用户可以对该Resource类型的所有实例进行操作)**
|
||||||
2. Instance级别:该级别限定了Permission可以操作的Resource Instance,在该级别用户只能够对特定的Resource实例进行操作。
|
2. Instance级别:该级别限定了Permission可以操作的Resource Instance,在该级别用户只能够对特定的Resource实例进行操作。
|
||||||
3. Attribute级别:该级别比限定了Permission可以操作Resouce类型或Resource实例的某个属性
|
3. Attribute级别:该级别比限定了Permission可以操作Resouce类型或Resource实例的某个属性
|
||||||
### Roles
|
### Roles
|
||||||
Roles是一个Permission的集合,用于简化权限和用户管理过程。用户可以被授予特定的角色来获得操作某些资源的权限。
|
Roles是一个Permission的集合,用于简化权限和用户管理过程。用户可以被授予特定的角色来获得操作某些资源的权限。
|
||||||
#### Role分类
|
#### Role分类
|
||||||
1. Role不实际关联具体的Permission,当你具有banker的角色时,其角色隐含你可以对账户进行操作的权限;当你具有waiter的角色时,默认可以对厨房的door进行open/close操作
|
1. Role不实际关联具体的Permission,当你具有banker的角色时,其角色隐含你可以对账户进行操作的权限;当你具有waiter的角色时,默认可以对厨房的door进行open/close操作
|
||||||
2. Role实际关联具体的Permission,在该情况下Role即为一系列Permission的集合,你可以对银行账号进行create、delete操作,因为操作银行账号是你已分配的admin角色的一个下属权限
|
2. Role实际关联具体的Permission,在该情况下Role即为一系列Permission的集合,你可以对银行账号进行create、delete操作,因为操作银行账号是你已分配的admin角色的一个下属权限
|
||||||
### User
|
### User
|
||||||
在Shiro中,User即是一个Subject实例。在Shiro中,Subject可以是任何与系统进行交互的主体,可以是浏览器、客户端、crond定时任务等。
|
在Shiro中,User即是一个Subject实例。在Shiro中,Subject可以是任何与系统进行交互的主体,可以是浏览器、客户端、crond定时任务等。
|
||||||
## 在Apache Shiro中实行Authorization
|
## 在Apache Shiro中实行Authorization
|
||||||
在Apache Shiro中,Authorization可以通过如下方式执行:
|
在Apache Shiro中,Authorization可以通过如下方式执行:
|
||||||
1. 通过代码实现:即在java程序中通过代码实现访问控制
|
1. 通过代码实现:即在java程序中通过代码实现访问控制
|
||||||
2. jdk注解:可以在你的方法上加上authorization注解
|
2. jdk注解:可以在你的方法上加上authorization注解
|
||||||
3. jsp/gsp taglibs
|
3. jsp/gsp taglibs
|
||||||
### 通过java code实现authorization
|
### 通过java code实现authorization
|
||||||
可以通过如下代码进行角色鉴定:
|
可以通过如下代码进行角色鉴定:
|
||||||
```java
|
```java
|
||||||
//get the current Subject
|
//get the current Subject
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
|
|
||||||
if (currentUser.hasRole("administrator")) {
|
if (currentUser.hasRole("administrator")) {
|
||||||
//show a special button
|
//show a special button
|
||||||
} else {
|
} else {
|
||||||
//don’t show the button?)
|
//don’t show the button?)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
可以通过如下代码实现对权限的鉴定操作:
|
可以通过如下代码实现对权限的鉴定操作:
|
||||||
```java
|
```java
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
|
|
||||||
Permission printPermission = new PrinterPermission("laserjet3000n","print");
|
Permission printPermission = new PrinterPermission("laserjet3000n","print");
|
||||||
|
|
||||||
If (currentUser.isPermitted(printPermission)) {
|
If (currentUser.isPermitted(printPermission)) {
|
||||||
//do one thing (show the print button?)
|
//do one thing (show the print button?)
|
||||||
} else {
|
} else {
|
||||||
//don’t show the button?
|
//don’t show the button?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### 基于String的权限鉴定
|
#### 基于String的权限鉴定
|
||||||
如果不想构造Permission对象,可以通过构造一个字符串来代表权限。该字符串可以是任何格式,只要你的Realm能够识别该格式并且与权限进行交互。
|
如果不想构造Permission对象,可以通过构造一个字符串来代表权限。该字符串可以是任何格式,只要你的Realm能够识别该格式并且与权限进行交互。
|
||||||
```java
|
```java
|
||||||
String perm = "printer:print:laserjet4400n";
|
String perm = "printer:print:laserjet4400n";
|
||||||
|
|
||||||
if(currentUser.isPermitted(perm)){
|
if(currentUser.isPermitted(perm)){
|
||||||
//show the print button?
|
//show the print button?
|
||||||
} else {
|
} else {
|
||||||
//don’t show the button?
|
//don’t show the button?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 通过注解实现Authorization
|
### 通过注解实现Authorization
|
||||||
可以通过java注解来实现Authorization过程,**在使用注解之前,必须先开启aop**。
|
可以通过java注解来实现Authorization过程,**在使用注解之前,必须先开启aop**。
|
||||||
如果在执行openAccount之前,当前Subject必须拥有account:create权限,那么可以通过如下方式来实现权限鉴定。如果当前用户未被直接授予或通过role间接授予该权限,那么会抛出AuthorizationException异常。
|
如果在执行openAccount之前,当前Subject必须拥有account:create权限,那么可以通过如下方式来实现权限鉴定。如果当前用户未被直接授予或通过role间接授予该权限,那么会抛出AuthorizationException异常。
|
||||||
```java
|
```java
|
||||||
//Will throw an AuthorizationException if none
|
//Will throw an AuthorizationException if none
|
||||||
//of the caller’s roles imply the Account
|
//of the caller’s roles imply the Account
|
||||||
//'create' permission
|
//'create' permission
|
||||||
@RequiresPermissions("account:create")
|
@RequiresPermissions("account:create")
|
||||||
public void openAccount( Account acct ) {
|
public void openAccount( Account acct ) {
|
||||||
//create the account
|
//create the account
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
如果要在执行方法之前进行角色校验,可以通过如下方式加上注解达到预期功能。
|
如果要在执行方法之前进行角色校验,可以通过如下方式加上注解达到预期功能。
|
||||||
```java
|
```java
|
||||||
//Throws an AuthorizationException if the caller
|
//Throws an AuthorizationException if the caller
|
||||||
//doesn’t have the ‘teller’ role:
|
//doesn’t have the ‘teller’ role:
|
||||||
@RequiresRoles( "teller" )
|
@RequiresRoles( "teller" )
|
||||||
public void openAccount( Account acct ) {
|
public void openAccount( Account acct ) {
|
||||||
//do something in here that only a teller
|
//do something in here that only a teller
|
||||||
//should do
|
//should do
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -1,92 +1,92 @@
|
|||||||
- [Apache Shiro Quick Start](#apache-shiro-quick-start)
|
- [Apache Shiro Quick Start](#apache-shiro-quick-start)
|
||||||
- [Apache Shiro常用API](#apache-shiro常用api)
|
- [Apache Shiro常用API](#apache-shiro常用api)
|
||||||
- [获取当前用户](#获取当前用户)
|
- [获取当前用户](#获取当前用户)
|
||||||
- [设置用户Session](#设置用户session)
|
- [设置用户Session](#设置用户session)
|
||||||
- [通过用户名和密码对用户进行身份认证](#通过用户名和密码对用户进行身份认证)
|
- [通过用户名和密码对用户进行身份认证](#通过用户名和密码对用户进行身份认证)
|
||||||
- [对身份认证失败的情况进行异常处理](#对身份认证失败的情况进行异常处理)
|
- [对身份认证失败的情况进行异常处理](#对身份认证失败的情况进行异常处理)
|
||||||
- [对已经登录的用户进行role检验](#对已经登录的用户进行role检验)
|
- [对已经登录的用户进行role检验](#对已经登录的用户进行role检验)
|
||||||
- [检测某用户是否具有某项特定权限](#检测某用户是否具有某项特定权限)
|
- [检测某用户是否具有某项特定权限](#检测某用户是否具有某项特定权限)
|
||||||
- [在实例级别对用户的权限进行检测](#在实例级别对用户的权限进行检测)
|
- [在实例级别对用户的权限进行检测](#在实例级别对用户的权限进行检测)
|
||||||
- [用户登出](#用户登出)
|
- [用户登出](#用户登出)
|
||||||
|
|
||||||
# Apache Shiro Quick Start
|
# Apache Shiro Quick Start
|
||||||
## Apache Shiro常用API
|
## Apache Shiro常用API
|
||||||
### 获取当前用户
|
### 获取当前用户
|
||||||
在任何环境中,都可以通过如下代码来获取当前执行的用户:
|
在任何环境中,都可以通过如下代码来获取当前执行的用户:
|
||||||
```java
|
```java
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
```
|
```
|
||||||
### 设置用户Session
|
### 设置用户Session
|
||||||
可以通过如下代码获取用户的Shiro Session,并可以向Session中设置属性和值,设置的值在用户会话期间内都可以使用。
|
可以通过如下代码获取用户的Shiro Session,并可以向Session中设置属性和值,设置的值在用户会话期间内都可以使用。
|
||||||
**Shiro Session在使用时并不要求当前位于HTTP环境下**
|
**Shiro Session在使用时并不要求当前位于HTTP环境下**
|
||||||
```java
|
```java
|
||||||
Session session = currentUser.getSession();
|
Session session = currentUser.getSession();
|
||||||
session.setAttribute( "someKey", "aValue" );
|
session.setAttribute( "someKey", "aValue" );
|
||||||
```
|
```
|
||||||
> 如果当前应用部署于Web环境下,那么Shiro Session默认会使用HttpSession,但是如果当前应用部署在非Web环境下时,Shiro Session会使用其Enterprise Session Management。
|
> 如果当前应用部署于Web环境下,那么Shiro Session默认会使用HttpSession,但是如果当前应用部署在非Web环境下时,Shiro Session会使用其Enterprise Session Management。
|
||||||
|
|
||||||
### 通过用户名和密码对用户进行身份认证
|
### 通过用户名和密码对用户进行身份认证
|
||||||
通过如下代码,可以通过UsernamePasswordToken来对未认证的用户进行身份认证。
|
通过如下代码,可以通过UsernamePasswordToken来对未认证的用户进行身份认证。
|
||||||
```java
|
```java
|
||||||
if ( !currentUser.isAuthenticated() ) {
|
if ( !currentUser.isAuthenticated() ) {
|
||||||
//collect user principals and credentials in a gui specific manner
|
//collect user principals and credentials in a gui specific manner
|
||||||
//such as username/password html form, X509 certificate, OpenID, etc.
|
//such as username/password html form, X509 certificate, OpenID, etc.
|
||||||
//We'll use the username/password example here since it is the most common.
|
//We'll use the username/password example here since it is the most common.
|
||||||
//(do you know what movie this is from? ;)
|
//(do you know what movie this is from? ;)
|
||||||
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
|
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
|
||||||
//this is all you have to do to support 'remember me' (no config - built in!):
|
//this is all you have to do to support 'remember me' (no config - built in!):
|
||||||
token.setRememberMe(true);
|
token.setRememberMe(true);
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 对身份认证失败的情况进行异常处理
|
### 对身份认证失败的情况进行异常处理
|
||||||
如果在身份认证的过程中失败,可以通过如下代码捕获认证失败抛出的异常,并对异常进行异常处理
|
如果在身份认证的过程中失败,可以通过如下代码捕获认证失败抛出的异常,并对异常进行异常处理
|
||||||
```java
|
```java
|
||||||
try {
|
try {
|
||||||
currentUser.login( token );
|
currentUser.login( token );
|
||||||
//if no exception, that's it, we're done!
|
//if no exception, that's it, we're done!
|
||||||
} catch ( UnknownAccountException uae ) {
|
} catch ( UnknownAccountException uae ) {
|
||||||
//username wasn't in the system, show them an error message?
|
//username wasn't in the system, show them an error message?
|
||||||
} catch ( IncorrectCredentialsException ice ) {
|
} catch ( IncorrectCredentialsException ice ) {
|
||||||
//password didn't match, try again?
|
//password didn't match, try again?
|
||||||
} catch ( LockedAccountException lae ) {
|
} catch ( LockedAccountException lae ) {
|
||||||
//account for that username is locked - can't login. Show them a message?
|
//account for that username is locked - can't login. Show them a message?
|
||||||
}
|
}
|
||||||
... more types exceptions to check if you want ...
|
... more types exceptions to check if you want ...
|
||||||
} catch ( AuthenticationException ae ) {
|
} catch ( AuthenticationException ae ) {
|
||||||
//unexpected condition - error?
|
//unexpected condition - error?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 对已经登录的用户进行role检验
|
### 对已经登录的用户进行role检验
|
||||||
如果用户已经登录,如果要检测该用户是否被授予某role权限,可以通过如下代码进行检验
|
如果用户已经登录,如果要检测该用户是否被授予某role权限,可以通过如下代码进行检验
|
||||||
```java
|
```java
|
||||||
if ( currentUser.hasRole( "schwartz" ) ) {
|
if ( currentUser.hasRole( "schwartz" ) ) {
|
||||||
log.info("May the Schwartz be with you!" );
|
log.info("May the Schwartz be with you!" );
|
||||||
} else {
|
} else {
|
||||||
log.info( "Hello, mere mortal." );
|
log.info( "Hello, mere mortal." );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 检测某用户是否具有某项特定权限
|
### 检测某用户是否具有某项特定权限
|
||||||
如果要对已经登录的用户执行检测,检测其是否被授予某项特定的前线,可以通过如下方式进行检测。
|
如果要对已经登录的用户执行检测,检测其是否被授予某项特定的前线,可以通过如下方式进行检测。
|
||||||
```java
|
```java
|
||||||
if ( currentUser.isPermitted( "lightsaber:wield" ) ) {
|
if ( currentUser.isPermitted( "lightsaber:wield" ) ) {
|
||||||
log.info("You may use a lightsaber ring. Use it wisely.");
|
log.info("You may use a lightsaber ring. Use it wisely.");
|
||||||
} else {
|
} else {
|
||||||
log.info("Sorry, lightsaber rings are for schwartz masters only.");
|
log.info("Sorry, lightsaber rings are for schwartz masters only.");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 在实例级别对用户的权限进行检测
|
### 在实例级别对用户的权限进行检测
|
||||||
在Shiro中,可以检测用户是否对某实例具有特定权限,通过如下代码:
|
在Shiro中,可以检测用户是否对某实例具有特定权限,通过如下代码:
|
||||||
```java
|
```java
|
||||||
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
|
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
|
||||||
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
|
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
|
||||||
"Here are the keys - have fun!");
|
"Here are the keys - have fun!");
|
||||||
} else {
|
} else {
|
||||||
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
|
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 用户登出
|
### 用户登出
|
||||||
如果已经登录的用户想要执行登出操作,可以通过如下代码进行登录:
|
如果已经登录的用户想要执行登出操作,可以通过如下代码进行登录:
|
||||||
```java
|
```java
|
||||||
currentUser.logout();
|
currentUser.logout();
|
||||||
```
|
```
|
||||||
@@ -1,133 +1,133 @@
|
|||||||
- [Apache Shiro Realm](#apache-shiro-realm)
|
- [Apache Shiro Realm](#apache-shiro-realm)
|
||||||
- [Realm简介](#realm简介)
|
- [Realm简介](#realm简介)
|
||||||
- [Realm配置](#realm配置)
|
- [Realm配置](#realm配置)
|
||||||
- [Realm Authentication](#realm-authentication)
|
- [Realm Authentication](#realm-authentication)
|
||||||
- [支持Authentication](#支持authentication)
|
- [支持Authentication](#支持authentication)
|
||||||
- [处理AuthenticationToken](#处理authenticationtoken)
|
- [处理AuthenticationToken](#处理authenticationtoken)
|
||||||
- [credentials匹配](#credentials匹配)
|
- [credentials匹配](#credentials匹配)
|
||||||
- [简单比较是否相等](#简单比较是否相等)
|
- [简单比较是否相等](#简单比较是否相等)
|
||||||
- [Hash Credentials](#hash-credentials)
|
- [Hash Credentials](#hash-credentials)
|
||||||
- [通过sha-256算法来生成账户信息](#通过sha-256算法来生成账户信息)
|
- [通过sha-256算法来生成账户信息](#通过sha-256算法来生成账户信息)
|
||||||
- [指定HashedCredentialsMatcher](#指定hashedcredentialsmatcher)
|
- [指定HashedCredentialsMatcher](#指定hashedcredentialsmatcher)
|
||||||
- [SaltedAuthenticationInfo](#saltedauthenticationinfo)
|
- [SaltedAuthenticationInfo](#saltedauthenticationinfo)
|
||||||
- [关闭Realm的Authentication](#关闭realm的authentication)
|
- [关闭Realm的Authentication](#关闭realm的authentication)
|
||||||
- [Realm Authorization](#realm-authorization)
|
- [Realm Authorization](#realm-authorization)
|
||||||
- [基于role的authorization](#基于role的authorization)
|
- [基于role的authorization](#基于role的authorization)
|
||||||
- [基于permission的authorization](#基于permission的authorization)
|
- [基于permission的authorization](#基于permission的authorization)
|
||||||
|
|
||||||
# Apache Shiro Realm
|
# Apache Shiro Realm
|
||||||
## Realm简介
|
## Realm简介
|
||||||
Realm是一个组件,用来访问针对特定应用的安全数据,例如user、role或permission。Realm负责将这些安全信息翻译为Shiro能够理解的格式。
|
Realm是一个组件,用来访问针对特定应用的安全数据,例如user、role或permission。Realm负责将这些安全信息翻译为Shiro能够理解的格式。
|
||||||
由于大多数数据源都会同时存储authentication和authorization信息,故而Realm能够同时执行authentication和authorization操作。
|
由于大多数数据源都会同时存储authentication和authorization信息,故而Realm能够同时执行authentication和authorization操作。
|
||||||
## Realm配置
|
## Realm配置
|
||||||
对于Realm的配置,可以在ini文件中进行如下配置:
|
对于Realm的配置,可以在ini文件中进行如下配置:
|
||||||
```ini
|
```ini
|
||||||
fooRealm = com.company.foo.Realm
|
fooRealm = com.company.foo.Realm
|
||||||
barRealm = com.company.another.Realm
|
barRealm = com.company.another.Realm
|
||||||
bazRealm = com.company.baz.Realm
|
bazRealm = com.company.baz.Realm
|
||||||
|
|
||||||
; 如下指定的顺序会影响Authentication/Authorization过程中的顺序
|
; 如下指定的顺序会影响Authentication/Authorization过程中的顺序
|
||||||
securityManager.realms = $fooRealm, $barRealm, $bazRealm
|
securityManager.realms = $fooRealm, $barRealm, $bazRealm
|
||||||
```
|
```
|
||||||
## Realm Authentication
|
## Realm Authentication
|
||||||
### 支持Authentication
|
### 支持Authentication
|
||||||
在Realm被询问去执行Authentication时,首先会调用该Realm的supports方法,如果supports方法的返回值是true时getAuthenticationInfo方法才会被调用。
|
在Realm被询问去执行Authentication时,首先会调用该Realm的supports方法,如果supports方法的返回值是true时getAuthenticationInfo方法才会被调用。
|
||||||
通常情况下,Realm会对提交的Token类型进行检测,并查看当前Realm是否能对该类型Token进行处理。
|
通常情况下,Realm会对提交的Token类型进行检测,并查看当前Realm是否能对该类型Token进行处理。
|
||||||
### 处理AuthenticationToken
|
### 处理AuthenticationToken
|
||||||
如果Realm支持该提交的Token类型,那么Authenticator会调用Realm的getAuthenticationInfo方法,该方法代表了通过Realm数据库来进行认证尝试。
|
如果Realm支持该提交的Token类型,那么Authenticator会调用Realm的getAuthenticationInfo方法,该方法代表了通过Realm数据库来进行认证尝试。
|
||||||
该方法会按照如下顺序进行执行:
|
该方法会按照如下顺序进行执行:
|
||||||
1. 查看Token中存储的principals信息
|
1. 查看Token中存储的principals信息
|
||||||
2. 根据Token中的principals,在data source中查找对应的账户信息
|
2. 根据Token中的principals,在data source中查找对应的账户信息
|
||||||
3. 确保提交Token中的credentials和data source中查找出的credentials相匹配
|
3. 确保提交Token中的credentials和data source中查找出的credentials相匹配
|
||||||
4. 如果credentials匹配,那么会将用户账户的信息封装到AuthenticationInfo中并返回
|
4. 如果credentials匹配,那么会将用户账户的信息封装到AuthenticationInfo中并返回
|
||||||
5. 如果credentials不匹配,会抛出AuthenticationException
|
5. 如果credentials不匹配,会抛出AuthenticationException
|
||||||
### credentials匹配
|
### credentials匹配
|
||||||
为了确保在credentials匹配的过程中,该过程是可插入(pluggable)和可自定义的(customizable)的,AuthenticationRealm支持CredentialsMatcher的概念,通过CredentialsMatcher来进行credentials的比较。
|
为了确保在credentials匹配的过程中,该过程是可插入(pluggable)和可自定义的(customizable)的,AuthenticationRealm支持CredentialsMatcher的概念,通过CredentialsMatcher来进行credentials的比较。
|
||||||
在从data source中查询到账户数据之后,会将其和提交Token中的credentials一起传递给CredentialsMatcher,由CredentialsMatcher来判断credentials是否相等。
|
在从data source中查询到账户数据之后,会将其和提交Token中的credentials一起传递给CredentialsMatcher,由CredentialsMatcher来判断credentials是否相等。
|
||||||
可以通过如下方式来定义CredentialsMatcher的比较逻辑:
|
可以通过如下方式来定义CredentialsMatcher的比较逻辑:
|
||||||
```java
|
```java
|
||||||
Realm myRealm = new com.company.shiro.realm.MyRealm();
|
Realm myRealm = new com.company.shiro.realm.MyRealm();
|
||||||
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
|
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
|
||||||
myRealm.setCredentialsMatcher(customMatcher);
|
myRealm.setCredentialsMatcher(customMatcher);
|
||||||
```
|
```
|
||||||
或
|
或
|
||||||
```ini
|
```ini
|
||||||
[main]
|
[main]
|
||||||
...
|
...
|
||||||
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
|
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
|
||||||
myRealm = com.company.shiro.realm.MyRealm
|
myRealm = com.company.shiro.realm.MyRealm
|
||||||
myRealm.credentialsMatcher = $customMatcher
|
myRealm.credentialsMatcher = $customMatcher
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
### 简单比较是否相等
|
### 简单比较是否相等
|
||||||
Shiro中所有开箱即用的Realm,其实现都默认使用SimpleCredentialsMatcher,SimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。
|
Shiro中所有开箱即用的Realm,其实现都默认使用SimpleCredentialsMatcher,SimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。
|
||||||
### Hash Credentials
|
### Hash Credentials
|
||||||
将用户提交的principals不做任何转换直接存储在data source中是一种不安全的做法,通常是将其进行单向hash之后再存入数据库。
|
将用户提交的principals不做任何转换直接存储在data source中是一种不安全的做法,通常是将其进行单向hash之后再存入数据库。
|
||||||
这样可以确保用户的credentials不会以raw text的方式存储再data source中,即使数据库数据被泄露,用户credentials的原始值也不会被任何人知道。
|
这样可以确保用户的credentials不会以raw text的方式存储再data source中,即使数据库数据被泄露,用户credentials的原始值也不会被任何人知道。
|
||||||
为了支持Token中credentials和data source中hash之后credentials的比较,Shiro提供了HashedCredentialsMatcher实现,可以通过配置HashedCredentialsMatcher来取代SimpleCredentialsMatcher。
|
为了支持Token中credentials和data source中hash之后credentials的比较,Shiro提供了HashedCredentialsMatcher实现,可以通过配置HashedCredentialsMatcher来取代SimpleCredentialsMatcher。
|
||||||
#### 通过sha-256算法来生成账户信息
|
#### 通过sha-256算法来生成账户信息
|
||||||
```java
|
```java
|
||||||
import org.apache.shiro.crypto.hash.Sha256Hash;
|
import org.apache.shiro.crypto.hash.Sha256Hash;
|
||||||
import org.apache.shiro.crypto.RandomNumberGenerator;
|
import org.apache.shiro.crypto.RandomNumberGenerator;
|
||||||
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
|
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
|
||||||
...
|
...
|
||||||
|
|
||||||
//We'll use a Random Number Generator to generate salts. This
|
//We'll use a Random Number Generator to generate salts. This
|
||||||
//is much more secure than using a username as a salt or not
|
//is much more secure than using a username as a salt or not
|
||||||
//having a salt at all. Shiro makes this easy.
|
//having a salt at all. Shiro makes this easy.
|
||||||
//
|
//
|
||||||
//Note that a normal app would reference an attribute rather
|
//Note that a normal app would reference an attribute rather
|
||||||
//than create a new RNG every time:
|
//than create a new RNG every time:
|
||||||
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
|
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
|
||||||
Object salt = rng.nextBytes();
|
Object salt = rng.nextBytes();
|
||||||
|
|
||||||
//Now hash the plain-text password with the random salt and multiple
|
//Now hash the plain-text password with the random salt and multiple
|
||||||
//iterations and then Base64-encode the value (requires less space than Hex):
|
//iterations and then Base64-encode the value (requires less space than Hex):
|
||||||
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();
|
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();
|
||||||
|
|
||||||
User user = new User(username, hashedPasswordBase64);
|
User user = new User(username, hashedPasswordBase64);
|
||||||
//save the salt with the new account. The HashedCredentialsMatcher
|
//save the salt with the new account. The HashedCredentialsMatcher
|
||||||
//will need it later when handling login attempts:
|
//will need it later when handling login attempts:
|
||||||
user.setPasswordSalt(salt);
|
user.setPasswordSalt(salt);
|
||||||
userDAO.create(user);
|
userDAO.create(user);
|
||||||
```
|
```
|
||||||
#### 指定HashedCredentialsMatcher
|
#### 指定HashedCredentialsMatcher
|
||||||
可以通过如下方式来指定特定HashedCredentialsMatcher实现类。
|
可以通过如下方式来指定特定HashedCredentialsMatcher实现类。
|
||||||
```ini
|
```ini
|
||||||
[main]
|
[main]
|
||||||
...
|
...
|
||||||
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
|
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
|
||||||
# base64 encoding, not hex in this example:
|
# base64 encoding, not hex in this example:
|
||||||
credentialsMatcher.storedCredentialsHexEncoded = false
|
credentialsMatcher.storedCredentialsHexEncoded = false
|
||||||
credentialsMatcher.hashIterations = 1024
|
credentialsMatcher.hashIterations = 1024
|
||||||
# This next property is only needed in Shiro 1.0\. Remove it in 1.1 and later:
|
# This next property is only needed in Shiro 1.0\. Remove it in 1.1 and later:
|
||||||
credentialsMatcher.hashSalted = true
|
credentialsMatcher.hashSalted = true
|
||||||
|
|
||||||
...
|
...
|
||||||
myRealm = com.company.....
|
myRealm = com.company.....
|
||||||
myRealm.credentialsMatcher = $credentialsMatcher
|
myRealm.credentialsMatcher = $credentialsMatcher
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
#### SaltedAuthenticationInfo
|
#### SaltedAuthenticationInfo
|
||||||
如果制定了HashedCredentialsMatcher,那么Realm.getAuthenticationInfo必须返回一个SaltedAuthenticationInfo实例而不是普通的Authentication实例。该SaltedAuthenticationInfo确保在创建用户信息时使用的salt可以在CredentialsMatcher中被使用。
|
如果制定了HashedCredentialsMatcher,那么Realm.getAuthenticationInfo必须返回一个SaltedAuthenticationInfo实例而不是普通的Authentication实例。该SaltedAuthenticationInfo确保在创建用户信息时使用的salt可以在CredentialsMatcher中被使用。
|
||||||
HashedCredentialsMatcher在对Token中提交的credentials进行hash时,需要使用到salt值来将用户提交的credentials进行和创建用户时相同的散列。
|
HashedCredentialsMatcher在对Token中提交的credentials进行hash时,需要使用到salt值来将用户提交的credentials进行和创建用户时相同的散列。
|
||||||
### 关闭Realm的Authentication
|
### 关闭Realm的Authentication
|
||||||
如果对某个Realm,想要对该realm不执行Authentication,可以将其实现类的supports方法只返回false,此时该realm在authentication过程中绝对不会被询问。
|
如果对某个Realm,想要对该realm不执行Authentication,可以将其实现类的supports方法只返回false,此时该realm在authentication过程中绝对不会被询问。
|
||||||
## Realm Authorization
|
## Realm Authorization
|
||||||
SecurityManager将校验permission和role的工作委托给了Authorizer,默认是ModularRealmAuthorizer。
|
SecurityManager将校验permission和role的工作委托给了Authorizer,默认是ModularRealmAuthorizer。
|
||||||
### 基于role的authorization
|
### 基于role的authorization
|
||||||
当subject的hasRoles或checkRoles被调用,其具体的执行流程如下:
|
当subject的hasRoles或checkRoles被调用,其具体的执行流程如下:
|
||||||
1. subject将校验role的任务委托给SecurityManager
|
1. subject将校验role的任务委托给SecurityManager
|
||||||
2. SecurityManager将任务委托给Authorizer
|
2. SecurityManager将任务委托给Authorizer
|
||||||
3. Authorizier会调用所有的Authorizing Realm直到该role被分配给subject。如果所有realm都没有授予subject该role,那么访问失败,返回false。
|
3. Authorizier会调用所有的Authorizing Realm直到该role被分配给subject。如果所有realm都没有授予subject该role,那么访问失败,返回false。
|
||||||
4. Authorizing Realm的AuthorizationInfo.getRoles方法会获取所有分配给该subject的role
|
4. Authorizing Realm的AuthorizationInfo.getRoles方法会获取所有分配给该subject的role
|
||||||
5. 如果待检测的role在getRoles返回的role list中,那么授权成功,subject可以对该资源进行访问
|
5. 如果待检测的role在getRoles返回的role list中,那么授权成功,subject可以对该资源进行访问
|
||||||
### 基于permission的authorization
|
### 基于permission的authorization
|
||||||
当subject的isPermitted或checkPermission方法被调用时,其执行流程如下:
|
当subject的isPermitted或checkPermission方法被调用时,其执行流程如下:
|
||||||
1. subject将检测Permission的任务委托给SecurityManager
|
1. subject将检测Permission的任务委托给SecurityManager
|
||||||
2. SecurityManager将该任务委托给Authorizer
|
2. SecurityManager将该任务委托给Authorizer
|
||||||
3. Authorizer会以此访问所有的Authorizer Realm直到Permission被授予。如果所有的realm都没有授予该subject权限,那么subject授权失败。
|
3. Authorizer会以此访问所有的Authorizer Realm直到Permission被授予。如果所有的realm都没有授予该subject权限,那么subject授权失败。
|
||||||
4. Realm按照如下顺序来检测Permission是否被授予:
|
4. Realm按照如下顺序来检测Permission是否被授予:
|
||||||
1. 其会调用AuthorizationInfo的getObjectPermissions方法和getStringPermissions方法并聚合结果,从而获取直接分配给该subject的所有权限
|
1. 其会调用AuthorizationInfo的getObjectPermissions方法和getStringPermissions方法并聚合结果,从而获取直接分配给该subject的所有权限
|
||||||
2. 如果RolePermissionRegister被注册,那么会根据subject被授予的role来获取role相关的permission,根据RolePermissionResolver.resolvePermissionsInRole()方法
|
2. 如果RolePermissionRegister被注册,那么会根据subject被授予的role来获取role相关的permission,根据RolePermissionResolver.resolvePermissionsInRole()方法
|
||||||
3. 对于上述返回的权限集合,implies方法会被调用,用来检测待检测权限是否隐含在其中
|
3. 对于上述返回的权限集合,implies方法会被调用,用来检测待检测权限是否隐含在其中
|
||||||
|
|||||||
@@ -1,175 +1,175 @@
|
|||||||
- [Apache Shiro](#apache-shiro)
|
- [Apache Shiro](#apache-shiro)
|
||||||
- [Shiro简介](#shiro简介)
|
- [Shiro简介](#shiro简介)
|
||||||
- [Shiro中常用的概念](#shiro中常用的概念)
|
- [Shiro中常用的概念](#shiro中常用的概念)
|
||||||
- [Subject](#subject)
|
- [Subject](#subject)
|
||||||
- [SecurityManager](#securitymanager)
|
- [SecurityManager](#securitymanager)
|
||||||
- [realms](#realms)
|
- [realms](#realms)
|
||||||
- [Authentication](#authentication)
|
- [Authentication](#authentication)
|
||||||
- [Authorization](#authorization)
|
- [Authorization](#authorization)
|
||||||
- [Session Management](#session-management)
|
- [Session Management](#session-management)
|
||||||
- [Shiro Session可在任何应用中使用](#shiro-session可在任何应用中使用)
|
- [Shiro Session可在任何应用中使用](#shiro-session可在任何应用中使用)
|
||||||
- [Shiro加密](#shiro加密)
|
- [Shiro加密](#shiro加密)
|
||||||
- [shiro hash](#shiro-hash)
|
- [shiro hash](#shiro-hash)
|
||||||
- [Shiro Ciphers](#shiro-ciphers)
|
- [Shiro Ciphers](#shiro-ciphers)
|
||||||
- [Shiro框架的Web支持](#shiro框架的web支持)
|
- [Shiro框架的Web支持](#shiro框架的web支持)
|
||||||
- [Web Session管理](#web-session管理)
|
- [Web Session管理](#web-session管理)
|
||||||
- [Shiro Native Session](#shiro-native-session)
|
- [Shiro Native Session](#shiro-native-session)
|
||||||
|
|
||||||
# Apache Shiro
|
# Apache Shiro
|
||||||
## Shiro简介
|
## Shiro简介
|
||||||
Shiro是一个简单易用且功能强大的Java安全框架,用于实现认证、授权、加密、session管理等场景,并且Shiro可以被用于任何应用,包括命令行应用、移动应用、大型web应用或是企业应用。
|
Shiro是一个简单易用且功能强大的Java安全框架,用于实现认证、授权、加密、session管理等场景,并且Shiro可以被用于任何应用,包括命令行应用、移动应用、大型web应用或是企业应用。
|
||||||
Shiro在如下方面提供Security API:
|
Shiro在如下方面提供Security API:
|
||||||
- Authentication:为用户提供身份认证
|
- Authentication:为用户提供身份认证
|
||||||
- Authorization:为应用提供用户的访问控制
|
- Authorization:为应用提供用户的访问控制
|
||||||
- 加密:避免应用数据处于明文状态
|
- 加密:避免应用数据处于明文状态
|
||||||
- session管理:每个用户的时间敏感状态
|
- session管理:每个用户的时间敏感状态
|
||||||
## Shiro中常用的概念
|
## Shiro中常用的概念
|
||||||
Shiro框架的结构主要分为三个部分:Subject、SecurityManager、Realms
|
Shiro框架的结构主要分为三个部分:Subject、SecurityManager、Realms
|
||||||
### Subject
|
### Subject
|
||||||
Subject:Subject是一个安全术语,通常意味着当前执行的用户。
|
Subject:Subject是一个安全术语,通常意味着当前执行的用户。
|
||||||
```java
|
```java
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
// 获取Subject对象
|
// 获取Subject对象
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
```
|
```
|
||||||
### SecurityManager
|
### SecurityManager
|
||||||
相对于Subject代表对于当前用户的安全操作,SecurityManager代表对所有用户的安全操作。SecurityManager是Shiro结构的核心,其由多个security component嵌套组成。
|
相对于Subject代表对于当前用户的安全操作,SecurityManager代表对所有用户的安全操作。SecurityManager是Shiro结构的核心,其由多个security component嵌套组成。
|
||||||
> 一旦SecurityManager和其嵌套的security component被配置完成,那么用户将不再使用SecurityManager而是调用Subject API。
|
> 一旦SecurityManager和其嵌套的security component被配置完成,那么用户将不再使用SecurityManager而是调用Subject API。
|
||||||
|
|
||||||
对每个应用中,只存在一个SecurityManager,SecurityManager是应用范围内的单例。默认SecurityManager的实现是POJO,可以通过java代码、Spring xml、yaml、properties等方式来进行配置。
|
对每个应用中,只存在一个SecurityManager,SecurityManager是应用范围内的单例。默认SecurityManager的实现是POJO,可以通过java代码、Spring xml、yaml、properties等方式来进行配置。
|
||||||
|
|
||||||
### realms
|
### realms
|
||||||
realms是shiro和应用中security data(如账户登录的登录数据或授权管理的权限数据)之间的连接器。当Shiro和security data进行交互时,shiro会从配置好的一个或者多个realm中获取security data。
|
realms是shiro和应用中security data(如账户登录的登录数据或授权管理的权限数据)之间的连接器。当Shiro和security data进行交互时,shiro会从配置好的一个或者多个realm中获取security data。
|
||||||
> 在上述情况下,realm类似于一个安全数据的特定DAO,其封装了到数据源连接的详细信息并且为Shiro提供安全数据。当配置Shiro时,必须指定至少一个Realm用于身份认证和权限认证。
|
> 在上述情况下,realm类似于一个安全数据的特定DAO,其封装了到数据源连接的详细信息并且为Shiro提供安全数据。当配置Shiro时,必须指定至少一个Realm用于身份认证和权限认证。
|
||||||
|
|
||||||
> Shiro提供了开箱即用的Realm来连接很多种security data source,比如LDAP,关系型数据库(JDBC),基于文本配置的data source例如ini或properties文件。可以通过自定义Realm的实现来访问自定义的data source,如果默认的Realm不能满足需求。
|
> Shiro提供了开箱即用的Realm来连接很多种security data source,比如LDAP,关系型数据库(JDBC),基于文本配置的data source例如ini或properties文件。可以通过自定义Realm的实现来访问自定义的data source,如果默认的Realm不能满足需求。
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
认证过程用于认证用户的身份。认证过程的主要流程如下:
|
认证过程用于认证用户的身份。认证过程的主要流程如下:
|
||||||
1. 收集用户的身份标识信息(通常称之为主体,principal)和身份证明(通常称之为凭据,credentials)
|
1. 收集用户的身份标识信息(通常称之为主体,principal)和身份证明(通常称之为凭据,credentials)
|
||||||
2. 向系统提交主体和凭据
|
2. 向系统提交主体和凭据
|
||||||
3. 如果提交的凭据和系统中该主体对应的凭据相同,那么该用户会被认为是“通过认证的”。如果提交的凭据不符,那么该用户会被认为是“认证失败的”。
|
3. 如果提交的凭据和系统中该主体对应的凭据相同,那么该用户会被认为是“通过认证的”。如果提交的凭据不符,那么该用户会被认为是“认证失败的”。
|
||||||
```java
|
```java
|
||||||
// 认证流程代码
|
// 认证流程代码
|
||||||
//1. Acquire submitted principals and credentials:
|
//1. Acquire submitted principals and credentials:
|
||||||
AuthenticationToken token =
|
AuthenticationToken token =
|
||||||
new UsernamePasswordToken(username, password);
|
new UsernamePasswordToken(username, password);
|
||||||
//2. Get the current Subject:
|
//2. Get the current Subject:
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//3. Login:
|
//3. Login:
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
```
|
```
|
||||||
当login方法被调用时,SecurityMananger将会收到AuthenticationToken并且将其分配到一个或多个已经配置好的Realm中,并且让每个Realm执行需要的认证流程。每个Reaml都能对提交的AuthenticaitonToken做出处理。
|
当login方法被调用时,SecurityMananger将会收到AuthenticationToken并且将其分配到一个或多个已经配置好的Realm中,并且让每个Realm执行需要的认证流程。每个Reaml都能对提交的AuthenticaitonToken做出处理。
|
||||||
如果认证过程失败,那么会抛出AuthenticationException,可以通过捕获该异常来对失败的认证进行处理。
|
如果认证过程失败,那么会抛出AuthenticationException,可以通过捕获该异常来对失败的认证进行处理。
|
||||||
```java
|
```java
|
||||||
//3. Login:
|
//3. Login:
|
||||||
try {
|
try {
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
} catch (IncorrectCredentialsException ice) { …
|
} catch (IncorrectCredentialsException ice) { …
|
||||||
} catch (LockedAccountException lae) { …
|
} catch (LockedAccountException lae) { …
|
||||||
}
|
}
|
||||||
…
|
…
|
||||||
catch (AuthenticationException ae) {…
|
catch (AuthenticationException ae) {…
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
**当用户通过身份认证之后,其被认为是“通过身份认证的”,并且被允许使用应用。但是,通过身份认证并不意味着可以在系统中执行任何操作。通过授权,可以决定用户能够在系统中执行哪些操作。**
|
**当用户通过身份认证之后,其被认为是“通过身份认证的”,并且被允许使用应用。但是,通过身份认证并不意味着可以在系统中执行任何操作。通过授权,可以决定用户能够在系统中执行哪些操作。**
|
||||||
|
|
||||||
## Authorization
|
## Authorization
|
||||||
Authorization的实质是访问控制,通常用于控制用户在系统中能够使用哪些资源。大多数情况下,可以通过role或permission等形式来实现访问控制,用户通过分配给他们的角色或者权限来执行操作。通过检查用户的role或者permission,系统可以决定将哪些功能暴露给用户。
|
Authorization的实质是访问控制,通常用于控制用户在系统中能够使用哪些资源。大多数情况下,可以通过role或permission等形式来实现访问控制,用户通过分配给他们的角色或者权限来执行操作。通过检查用户的role或者permission,系统可以决定将哪些功能暴露给用户。
|
||||||
```java
|
```java
|
||||||
// 通过如下代码,Subject可以实现对用户的role检测
|
// 通过如下代码,Subject可以实现对用户的role检测
|
||||||
if ( subject.hasRole(“administrator”) ) {
|
if ( subject.hasRole(“administrator”) ) {
|
||||||
//show the ‘Create User’ button
|
//show the ‘Create User’ button
|
||||||
} else {
|
} else {
|
||||||
//grey-out the button?
|
//grey-out the button?
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过如下代码,可以实现权限分配而不是角色检测
|
// 通过如下代码,可以实现权限分配而不是角色检测
|
||||||
if ( subject.isPermitted(“user:create”) ) {
|
if ( subject.isPermitted(“user:create”) ) {
|
||||||
//show the ‘Create User’ button
|
//show the ‘Create User’ button
|
||||||
} else {
|
} else {
|
||||||
//grey-out the button?
|
//grey-out the button?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
权限控制甚至支持非常细粒度的权限,譬如实例级别的权限控制。
|
权限控制甚至支持非常细粒度的权限,譬如实例级别的权限控制。
|
||||||
```java
|
```java
|
||||||
// 如下代码检测用户是否拥有删除jsmith用户的权限
|
// 如下代码检测用户是否拥有删除jsmith用户的权限
|
||||||
if ( subject.isPermitted(“user:delete:jsmith”) ) {
|
if ( subject.isPermitted(“user:delete:jsmith”) ) {
|
||||||
//delete the ‘jsmith’ user
|
//delete the ‘jsmith’ user
|
||||||
} else {
|
} else {
|
||||||
//don’t delete ‘jsmith’
|
//don’t delete ‘jsmith’
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
和Authentication类似,Authorization也会进入SecurityManager,并且通过一个或者多个Realm来决定是否允许访问。
|
和Authentication类似,Authorization也会进入SecurityManager,并且通过一个或者多个Realm来决定是否允许访问。
|
||||||
**根据需要,Realm既会响应Authentication过程,也会响应Authority过程**
|
**根据需要,Realm既会响应Authentication过程,也会响应Authority过程**
|
||||||
|
|
||||||
## Session Management
|
## Session Management
|
||||||
Apache提供了一致的会话管理,在任何类型和架构的程序中都可以使用,该Session API从小型的守护进程到大型集群web应用都可以使用。
|
Apache提供了一致的会话管理,在任何类型和架构的程序中都可以使用,该Session API从小型的守护进程到大型集群web应用都可以使用。
|
||||||
并且,Shiro提供的Session API是容器无关的,在任何容器环境下Session API都相同。Shiro结构也支持可插入的session存储,可以将session存储在企业缓存、关系型数据库或者nosql中。
|
并且,Shiro提供的Session API是容器无关的,在任何容器环境下Session API都相同。Shiro结构也支持可插入的session存储,可以将session存储在企业缓存、关系型数据库或者nosql中。
|
||||||
Shiro Seesion的另一个好处是Shiro Session可以在不同技术实现的客户端之间进行共享,譬如Swing桌面客户端可以和web客户端一起加入同一个应用会话(用户同时使用swing客户端和web客户端时很有用)。
|
Shiro Seesion的另一个好处是Shiro Session可以在不同技术实现的客户端之间进行共享,譬如Swing桌面客户端可以和web客户端一起加入同一个应用会话(用户同时使用swing客户端和web客户端时很有用)。
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
* 用户获取Session
|
* 用户获取Session
|
||||||
*/
|
*/
|
||||||
// 如果当前用户存在会话,则获取已存在会话,
|
// 如果当前用户存在会话,则获取已存在会话,
|
||||||
// 如果当前用户不存在会话,则创建新的会话
|
// 如果当前用户不存在会话,则创建新的会话
|
||||||
Session session = subject.getSession();
|
Session session = subject.getSession();
|
||||||
// 接受一个boolean值,该boolean值标识如果当前用户不存在会话,是否创建一个新的会话
|
// 接受一个boolean值,该boolean值标识如果当前用户不存在会话,是否创建一个新的会话
|
||||||
Session session = subject.getSession(boolean create);
|
Session session = subject.getSession(boolean create);
|
||||||
```
|
```
|
||||||
### Shiro Session可在任何应用中使用
|
### Shiro Session可在任何应用中使用
|
||||||
```java
|
```java
|
||||||
Session session = subject.getSession();
|
Session session = subject.getSession();
|
||||||
session.getAttribute(“key”, someValue);
|
session.getAttribute(“key”, someValue);
|
||||||
Date start = session.getStartTimestamp();
|
Date start = session.getStartTimestamp();
|
||||||
Date timestamp = session.getLastAccessTime();
|
Date timestamp = session.getLastAccessTime();
|
||||||
session.setTimeout(millis);
|
session.setTimeout(millis);
|
||||||
```
|
```
|
||||||
## Shiro加密
|
## Shiro加密
|
||||||
Shiro加密是独立于用户(Subject)的,故而Shiro加密可以独立于Subject使用。
|
Shiro加密是独立于用户(Subject)的,故而Shiro加密可以独立于Subject使用。
|
||||||
Shiro加密主要关注于如下两个模块:
|
Shiro加密主要关注于如下两个模块:
|
||||||
- hash加密(又名消息摘要,message digest)
|
- hash加密(又名消息摘要,message digest)
|
||||||
- ciphers加密
|
- ciphers加密
|
||||||
### shiro hash
|
### shiro hash
|
||||||
在Shiro hash中,如果想要快速计算文件的MD5值,可以通过如下方式快速计算:
|
在Shiro hash中,如果想要快速计算文件的MD5值,可以通过如下方式快速计算:
|
||||||
```java
|
```java
|
||||||
String hex = new Md5Hash(myFile).toHex();
|
String hex = new Md5Hash(myFile).toHex();
|
||||||
```
|
```
|
||||||
在shiro hash中,如果想要对密码进行sha-512进行hash操作并且对结果进行base64编码成可打印字符串,可以进行如下操作:
|
在shiro hash中,如果想要对密码进行sha-512进行hash操作并且对结果进行base64编码成可打印字符串,可以进行如下操作:
|
||||||
```java
|
```java
|
||||||
String encodedPassword =
|
String encodedPassword =
|
||||||
new Sha512Hash(password, salt, count).toBase64();
|
new Sha512Hash(password, salt, count).toBase64();
|
||||||
```
|
```
|
||||||
Shiro框架在很大程度上简化了hash和编码。
|
Shiro框架在很大程度上简化了hash和编码。
|
||||||
### Shiro Ciphers
|
### Shiro Ciphers
|
||||||
Ciphers是一种算法,可以通过指定的key将加密后的数据还原到加密之前(不同于hash操作,hash操作通常是不可逆的)。通常用Ciphers来保证数据传输时的安全,防止数据在传输时被窥探。
|
Ciphers是一种算法,可以通过指定的key将加密后的数据还原到加密之前(不同于hash操作,hash操作通常是不可逆的)。通常用Ciphers来保证数据传输时的安全,防止数据在传输时被窥探。
|
||||||
Shiro通过引入CipherService API来简化加密的流程,**CipherService是一个简单,无状态并且线程安全的API,可以对应用数据进行加密或者解密操作。**
|
Shiro通过引入CipherService API来简化加密的流程,**CipherService是一个简单,无状态并且线程安全的API,可以对应用数据进行加密或者解密操作。**
|
||||||
```java
|
```java
|
||||||
// Shiro CipherService对数据进行加密操作
|
// Shiro CipherService对数据进行加密操作
|
||||||
|
|
||||||
AesCipherService cipherService = new AesCipherService();
|
AesCipherService cipherService = new AesCipherService();
|
||||||
cipherService.setKeySize(256);
|
cipherService.setKeySize(256);
|
||||||
//create a test key:
|
//create a test key:
|
||||||
byte[] testKey = cipherService.generateNewKey();
|
byte[] testKey = cipherService.generateNewKey();
|
||||||
|
|
||||||
//encrypt a file’s bytes:
|
//encrypt a file’s bytes:
|
||||||
byte[] encrypted =
|
byte[] encrypted =
|
||||||
cipherService.encrypt(fileBytes, testKey);
|
cipherService.encrypt(fileBytes, testKey);
|
||||||
```
|
```
|
||||||
## Shiro框架的Web支持
|
## Shiro框架的Web支持
|
||||||
### Web Session管理
|
### Web Session管理
|
||||||
对于web应用,shiro默认情况下其session会使用已经存在的servlet容器session。当使用subject.getSession()或者subject.getSession(boolean)获取session实例时,**Shiro将会返回一个基于Servlet容器的HttpSession实例来作为Session实例返回值**。
|
对于web应用,shiro默认情况下其session会使用已经存在的servlet容器session。当使用subject.getSession()或者subject.getSession(boolean)获取session实例时,**Shiro将会返回一个基于Servlet容器的HttpSession实例来作为Session实例返回值**。
|
||||||
> 对于Shiro来说,其业务层代码通过subject.getSession来获取Shiro Session实例,即使当前运行于Servlet容器,业务层代码在与Shiro Session交互时也不知道与其交互的是HttpSession。
|
> 对于Shiro来说,其业务层代码通过subject.getSession来获取Shiro Session实例,即使当前运行于Servlet容器,业务层代码在与Shiro Session交互时也不知道与其交互的是HttpSession。
|
||||||
> 故而在使用Shiro Session时,其Session是独立与环境的,Web应用和非Web应用都可以通过相同的Shiro Session API与Shiro Session进行交互,而Shiro Session是否是基于Servlet容器的HttpSession,用户是无感知的。
|
> 故而在使用Shiro Session时,其Session是独立与环境的,Web应用和非Web应用都可以通过相同的Shiro Session API与Shiro Session进行交互,而Shiro Session是否是基于Servlet容器的HttpSession,用户是无感知的。
|
||||||
|
|
||||||
### Shiro Native Session
|
### Shiro Native Session
|
||||||
当启用Shiro Native Session之后,对于Web应用,如果想使用Shiro Session来代替基于Servlet容器的HttpSession,无需修改HttpServletRequest.getSession()和HttpSession API为Shiro Session API。
|
当启用Shiro Native Session之后,对于Web应用,如果想使用Shiro Session来代替基于Servlet容器的HttpSession,无需修改HttpServletRequest.getSession()和HttpSession API为Shiro Session API。
|
||||||
Shiro Session完全实现了Servlet Session的标准,以此支持Shiro Session在Web应用中的使用。在使用了Shiro Native Session后,任何对HttpServletRequest和HttpSession API的调用都会被Shiro拦截,Shiro会用Shiro Native Session API来代理这些请求。
|
Shiro Session完全实现了Servlet Session的标准,以此支持Shiro Session在Web应用中的使用。在使用了Shiro Native Session后,任何对HttpServletRequest和HttpSession API的调用都会被Shiro拦截,Shiro会用Shiro Native Session API来代理这些请求。
|
||||||
> **故而,当想要在Web环境中使用Shiro Session API时,无需变动Web环境先前未使用Shiro Session API时的任何代码。**
|
> **故而,当想要在Web环境中使用Shiro Session API时,无需变动Web环境先前未使用Shiro Session API时的任何代码。**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,363 +1,363 @@
|
|||||||
# Spring Security
|
# Spring Security
|
||||||
## Spring Security简介
|
## Spring Security简介
|
||||||
Spring Security作为一个安全框架,向使用者提供了用户认证、授权、常规攻击保护等功能。
|
Spring Security作为一个安全框架,向使用者提供了用户认证、授权、常规攻击保护等功能。
|
||||||
## Spring Security自动配置
|
## Spring Security自动配置
|
||||||
默认情况下,在引入Spring Security的启动器依赖之后,Spring Boot自动配置会做如下工作:
|
默认情况下,在引入Spring Security的启动器依赖之后,Spring Boot自动配置会做如下工作:
|
||||||
- 启用Spring Security的默认配置,创建一个的bean对象,bean对象名称为“springSecurityFilterChain”,bean对象类型为SecurityFilterChain,实现了Filter。该bean对象为负责应用中所有与安全相关的操作(例如验证提交的username和password,保护应用的url等)
|
- 启用Spring Security的默认配置,创建一个的bean对象,bean对象名称为“springSecurityFilterChain”,bean对象类型为SecurityFilterChain,实现了Filter。该bean对象为负责应用中所有与安全相关的操作(例如验证提交的username和password,保护应用的url等)
|
||||||
- 创建一个UserDetailsService的bean对象,并且产生一个为”user“的username和一个随机产生的password,随机产生的password会输出在console日志中
|
- 创建一个UserDetailsService的bean对象,并且产生一个为”user“的username和一个随机产生的password,随机产生的password会输出在console日志中
|
||||||
- 将名为springSecurityFilterChain的bean对象注册到servlet容器中,用来对每个servlet请求进行处理
|
- 将名为springSecurityFilterChain的bean对象注册到servlet容器中,用来对每个servlet请求进行处理
|
||||||
> Spring Security会通过Spring Security会通过BCtypt(Hash解密算法)来对密码的存储进行保护
|
> Spring Security会通过Spring Security会通过BCtypt(Hash解密算法)来对密码的存储进行保护
|
||||||
|
|
||||||
## Spring Security结构
|
## Spring Security结构
|
||||||
### DelegatingFilterProxy
|
### DelegatingFilterProxy
|
||||||
Spring提供了Filter的一个实现类DelegatingFilterProxy,其将Servlet容器的生命周期和Spring的ApplicationContext连接在一起。
|
Spring提供了Filter的一个实现类DelegatingFilterProxy,其将Servlet容器的生命周期和Spring的ApplicationContext连接在一起。
|
||||||
***DelegatingFilterProxy会通过标准的servlet容器机制被注册,但是将所有工作都委托给Spring容器中实现了Filter的bean对象***。
|
***DelegatingFilterProxy会通过标准的servlet容器机制被注册,但是将所有工作都委托给Spring容器中实现了Filter的bean对象***。
|
||||||
> DelegatingFilterProxy会在ApplicationContext中查找实现了Filter的bean对象,并且调用该bean对象的doFilter方法
|
> DelegatingFilterProxy会在ApplicationContext中查找实现了Filter的bean对象,并且调用该bean对象的doFilter方法
|
||||||
> ```java
|
> ```java
|
||||||
> public void doFilter(ServletRequest request,
|
> public void doFilter(ServletRequest request,
|
||||||
> ServletResponse response, FilterChain chain) {
|
> ServletResponse response, FilterChain chain) {
|
||||||
> // Lazily get Filter that was registered as a Spring Bean
|
> // Lazily get Filter that was registered as a Spring Bean
|
||||||
> // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
|
> // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
|
||||||
> Filter delegate = getFilterBean(someBeanName);
|
> Filter delegate = getFilterBean(someBeanName);
|
||||||
> // delegate work to the Spring Bean
|
> // delegate work to the Spring Bean
|
||||||
> delegate.doFilter(request, response);
|
> delegate.doFilter(request, response);
|
||||||
> }
|
> }
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
### FilterChainProxy
|
### FilterChainProxy
|
||||||
Spring Security支持FilterChainProxy,FilterChainProxy是一个由Spring Security提供的特殊Filter,FilterChainProxy通过SecurityFilterChain允许向多个Filters委托工作。
|
Spring Security支持FilterChainProxy,FilterChainProxy是一个由Spring Security提供的特殊Filter,FilterChainProxy通过SecurityFilterChain允许向多个Filters委托工作。
|
||||||
***FilterChainProxy是一个bean对象,通过被包含在DelegatingFilterProxy中。***
|
***FilterChainProxy是一个bean对象,通过被包含在DelegatingFilterProxy中。***
|
||||||
|
|
||||||
### SecurityFilterChain
|
### SecurityFilterChain
|
||||||
SecurityFilterChain通常被FilterChainProxy使用,用来决定在该次请求中调用那些Spring Security Filters。
|
SecurityFilterChain通常被FilterChainProxy使用,用来决定在该次请求中调用那些Spring Security Filters。
|
||||||
|
|
||||||
### SecurityFilters
|
### SecurityFilters
|
||||||
Security Filters通过SecurityFilterChain API被插入到FilterChainProxy中。
|
Security Filters通过SecurityFilterChain API被插入到FilterChainProxy中。
|
||||||
### 处理Security异常
|
### 处理Security异常
|
||||||
ExceptionTranslationFilter将认证异常和权限异常翻译为http response。
|
ExceptionTranslationFilter将认证异常和权限异常翻译为http response。
|
||||||
> ExceptionTranslationFilter被插入到FilterChainProxy中,作为SecurityFilterChain中的一个。
|
> ExceptionTranslationFilter被插入到FilterChainProxy中,作为SecurityFilterChain中的一个。
|
||||||
> 如果应用程序没有抛出AccessDeniedException或AuthenticationException,那么ExceptionTranslationFilter并不会做任何事情。
|
> 如果应用程序没有抛出AccessDeniedException或AuthenticationException,那么ExceptionTranslationFilter并不会做任何事情。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// ExceptionTranslationFilter的伪代码
|
// ExceptionTranslationFilter的伪代码
|
||||||
try {
|
try {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
} catch (AccessDeniedException | AuthenticationException ex) {
|
} catch (AccessDeniedException | AuthenticationException ex) {
|
||||||
if (!authenticated || ex instanceof AuthenticationException) {
|
if (!authenticated || ex instanceof AuthenticationException) {
|
||||||
startAuthentication();
|
startAuthentication();
|
||||||
} else {
|
} else {
|
||||||
accessDenied();
|
accessDenied();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Spring Security身份认证的结构
|
## Spring Security身份认证的结构
|
||||||
### SecurityContextHolder
|
### SecurityContextHolder
|
||||||
Spring Security身份认证的核心模型,SecurityContextHolder包含有SecurityContext。
|
Spring Security身份认证的核心模型,SecurityContextHolder包含有SecurityContext。
|
||||||
> Spring Security将被认证用户的详细信息(details)存储在SecurityContextHolder中。Spring Security不在乎该SecurityContextHolder是如何被填充的,只要该SecurityContextHolder有值,那么其将会被用作当前已经被认证的用户。
|
> Spring Security将被认证用户的详细信息(details)存储在SecurityContextHolder中。Spring Security不在乎该SecurityContextHolder是如何被填充的,只要该SecurityContextHolder有值,那么其将会被用作当前已经被认证的用户。
|
||||||
|
|
||||||
> ***将用户标识为已认证的最简单的方法是为该用户设置SecurityContextHolder。***
|
> ***将用户标识为已认证的最简单的方法是为该用户设置SecurityContextHolder。***
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 设置SecurityContextHolder
|
// 设置SecurityContextHolder
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
Authentication authentication =
|
Authentication authentication =
|
||||||
new TestingAuthenticationToken("username", "password", "ROLE_USER");
|
new TestingAuthenticationToken("username", "password", "ROLE_USER");
|
||||||
context.setAuthentication(authentication);
|
context.setAuthentication(authentication);
|
||||||
|
|
||||||
SecurityContextHolder.setContext(context);
|
SecurityContextHolder.setContext(context);
|
||||||
```
|
```
|
||||||
默认情况下,SecurityContextHolder通过ThreadLocal来存储SecurityContext,故而SecurityContext对于位于同一线程之下的方法来说都可以访问。
|
默认情况下,SecurityContextHolder通过ThreadLocal来存储SecurityContext,故而SecurityContext对于位于同一线程之下的方法来说都可以访问。
|
||||||
> 使用ThreadLocal来存储SecurityContext是相当安全的,如果想要在该已认证主体的请求被处理完成之后清除SecurityContext,Spring Security中的FilterChainProxy会保证该SecurityContext被清除。
|
> 使用ThreadLocal来存储SecurityContext是相当安全的,如果想要在该已认证主体的请求被处理完成之后清除SecurityContext,Spring Security中的FilterChainProxy会保证该SecurityContext被清除。
|
||||||
|
|
||||||
### SecurityContext
|
### SecurityContext
|
||||||
SecurityContext从SecurityContextHolder中获得,SecurityContext中含有Authentication对象。
|
SecurityContext从SecurityContextHolder中获得,SecurityContext中含有Authentication对象。
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
Authentication在Spring Security中具有两个目的:
|
Authentication在Spring Security中具有两个目的:
|
||||||
- 作为AuthenticationManager的输入,用于提供待认证用户的认证凭证。当用于该场景下时,isAuthenticated()方法返回值应该为false
|
- 作为AuthenticationManager的输入,用于提供待认证用户的认证凭证。当用于该场景下时,isAuthenticated()方法返回值应该为false
|
||||||
- 代表当前已经认证过的用户。当前的Authentication可以从SecurityContext中获取,而默认情况下SecurityContext是存储在ThreadLocal中的
|
- 代表当前已经认证过的用户。当前的Authentication可以从SecurityContext中获取,而默认情况下SecurityContext是存储在ThreadLocal中的
|
||||||
|
|
||||||
Authentication含有如下部分:
|
Authentication含有如下部分:
|
||||||
- 主体(principal):用于标识用户,当通过username/password进行认证时,其通常是UserDetails类的实例
|
- 主体(principal):用于标识用户,当通过username/password进行认证时,其通常是UserDetails类的实例
|
||||||
- 凭据(credentials):通常是password,在许多场景下凭据会在用户认证成功之后被清空,为了保证凭据不会被泄露
|
- 凭据(credentials):通常是password,在许多场景下凭据会在用户认证成功之后被清空,为了保证凭据不会被泄露
|
||||||
- 权限(authorities):该GrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。
|
- 权限(authorities):该GrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。
|
||||||
|
|
||||||
### GrantedAuthority
|
### GrantedAuthority
|
||||||
GrantedAuthority是用户被授予的高层次许可,譬如用户角色或者作用域范围。
|
GrantedAuthority是用户被授予的高层次许可,譬如用户角色或者作用域范围。
|
||||||
GrantedAuthority可以通过Authentication.getAuthorities()方法来获得,该方法会返回一个GrantedAuthentication的集合。每个GrantedAuthentication都是一项被授予该用户的权限。
|
GrantedAuthority可以通过Authentication.getAuthorities()方法来获得,该方法会返回一个GrantedAuthentication的集合。每个GrantedAuthentication都是一项被授予该用户的权限。
|
||||||
|
|
||||||
### AuthenticationManager
|
### AuthenticationManager
|
||||||
AuthenticationManager的API定义了Security Filters如何来执行身份认证。对于身份认证返回的Authentication,会被调用AuthenticationManager的controller设置到SecurityContextHolder中。
|
AuthenticationManager的API定义了Security Filters如何来执行身份认证。对于身份认证返回的Authentication,会被调用AuthenticationManager的controller设置到SecurityContextHolder中。
|
||||||
> AuthenticationManager的实现可以是任何类,但是最通用的实现仍然是ProviderManager
|
> AuthenticationManager的实现可以是任何类,但是最通用的实现仍然是ProviderManager
|
||||||
|
|
||||||
### ProviderManager
|
### ProviderManager
|
||||||
ProviderManager是AuthenticationManager的最通用实现。ProviderManager将工作委托给一系列AuthenticationProvider。
|
ProviderManager是AuthenticationManager的最通用实现。ProviderManager将工作委托给一系列AuthenticationProvider。
|
||||||
> 对于每个ProviderManager,都可以决定将该认证标识为成功、失败,或者将认证工作委托给下游AuthenticationProvider。
|
> 对于每个ProviderManager,都可以决定将该认证标识为成功、失败,或者将认证工作委托给下游AuthenticationProvider。
|
||||||
> 如果所有的AuthenticationProvider都没有将该认证标识为成功或者失败,那么整个认证流程失败,并且抛出ProviderNotFoundException异常。
|
> 如果所有的AuthenticationProvider都没有将该认证标识为成功或者失败,那么整个认证流程失败,并且抛出ProviderNotFoundException异常。
|
||||||
> ProviderNotFoundException是一个特殊的AuthenticationException,该异常代表对传入Authentication的类型并没有配置该类型的ProviderManager
|
> ProviderNotFoundException是一个特殊的AuthenticationException,该异常代表对传入Authentication的类型并没有配置该类型的ProviderManager
|
||||||
|
|
||||||
> 在实践中,每个AuthenticationProvider知道如何处理一种特定类型的Authentication
|
> 在实践中,每个AuthenticationProvider知道如何处理一种特定类型的Authentication
|
||||||
|
|
||||||
默认情况下,ProviderManager在认证请求成功后会尝试清除返回的Authentication对象中任何敏感的凭据信息,这将会保证password等敏感信息保存时间尽可能地短,减少泄露的风险。
|
默认情况下,ProviderManager在认证请求成功后会尝试清除返回的Authentication对象中任何敏感的凭据信息,这将会保证password等敏感信息保存时间尽可能地短,减少泄露的风险。
|
||||||
|
|
||||||
### AuthenticationProvider
|
### AuthenticationProvider
|
||||||
复数个AuthenticationProvider可以被注入到ProviderManager中,每个AuthenticationProvider可以负责一种专门类型的认证,例如DaoAuthenticationProvider负责username/password认证,JwtAuthenticationProvider负责jwt token的认证。
|
复数个AuthenticationProvider可以被注入到ProviderManager中,每个AuthenticationProvider可以负责一种专门类型的认证,例如DaoAuthenticationProvider负责username/password认证,JwtAuthenticationProvider负责jwt token的认证。
|
||||||
|
|
||||||
### AuthenticationEntryPoint
|
### AuthenticationEntryPoint
|
||||||
AuthenticationEntryPoint用来发送一个Http Response,用来向客户端请求认证凭据。
|
AuthenticationEntryPoint用来发送一个Http Response,用来向客户端请求认证凭据。
|
||||||
某些情况下,客户端在请求资源时会主动在请求中包含凭据,如username/password等。在这种情况下,服务端并不需要再专门发送Http Response来向客户端请求认证凭据。
|
某些情况下,客户端在请求资源时会主动在请求中包含凭据,如username/password等。在这种情况下,服务端并不需要再专门发送Http Response来向客户端请求认证凭据。
|
||||||
> AuthenticationEntryPoint用来向客户端请求认证凭据,AuthenticationEntryPoint的实现可能会执行一个从定向操作,将请求重定向到一个登录页面用于获取凭据,并且返回一个WWW-Authentication Header。
|
> AuthenticationEntryPoint用来向客户端请求认证凭据,AuthenticationEntryPoint的实现可能会执行一个从定向操作,将请求重定向到一个登录页面用于获取凭据,并且返回一个WWW-Authentication Header。
|
||||||
|
|
||||||
### AbstractAuthenticationProcessingFilter
|
### AbstractAuthenticationProcessingFilter
|
||||||
该类作为base filter用来对用户的凭据进行认证。再凭据被认证之前,Spring Security通常会通过AuthenticationEntryPoint向客户端请求认证凭据。
|
该类作为base filter用来对用户的凭据进行认证。再凭据被认证之前,Spring Security通常会通过AuthenticationEntryPoint向客户端请求认证凭据。
|
||||||
之后,AbstractAuthenticationProcessingFilter会对提交的任何认证请求进行认证。
|
之后,AbstractAuthenticationProcessingFilter会对提交的任何认证请求进行认证。
|
||||||
#### 认证流程
|
#### 认证流程
|
||||||
1. 当用户提交认证凭据之后,AbstractAuthenticationProcesingFilter会根据HttpServletRequest对象创建一个Authentication对象用于认证,该Authentication的类型取决于AbstractAuthenticationProcessingFilter的子类类型。例如,UsernamePasswordAuthenticationFilter会通过提交request中的username和password创建UsernamePasswordAuthenticationToken。
|
1. 当用户提交认证凭据之后,AbstractAuthenticationProcesingFilter会根据HttpServletRequest对象创建一个Authentication对象用于认证,该Authentication的类型取决于AbstractAuthenticationProcessingFilter的子类类型。例如,UsernamePasswordAuthenticationFilter会通过提交request中的username和password创建UsernamePasswordAuthenticationToken。
|
||||||
2. 然后将构建产生的Authentication对象传入到AuthenticationManager中,进行认证
|
2. 然后将构建产生的Authentication对象传入到AuthenticationManager中,进行认证
|
||||||
3. 如果认证失败,那么会失败,SecurityContextHolder会被清空,RememberMeService.logFail方法将会被调用,AuthenticationFailureHandler也会被调用
|
3. 如果认证失败,那么会失败,SecurityContextHolder会被清空,RememberMeService.logFail方法将会被调用,AuthenticationFailureHandler也会被调用
|
||||||
4. 如果认证成功,那么SessionAuthenticationStrategy将会收到登录的通知
|
4. 如果认证成功,那么SessionAuthenticationStrategy将会收到登录的通知
|
||||||
5. 该Authentication对象再认证成功之后将会被设置到SecurityContextHolder中
|
5. 该Authentication对象再认证成功之后将会被设置到SecurityContextHolder中
|
||||||
6. RemeberMeService.logSuccess将会被调用
|
6. RemeberMeService.logSuccess将会被调用
|
||||||
7. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent.
|
7. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent.
|
||||||
8. AuthenticationSuccessHandler被调用
|
8. AuthenticationSuccessHandler被调用
|
||||||
|
|
||||||
## 用户名/密码认证
|
## 用户名/密码认证
|
||||||
### Form Login
|
### Form Login
|
||||||
Spring Security为以表单形式提供的用户名和密码认证提供支持。
|
Spring Security为以表单形式提供的用户名和密码认证提供支持。
|
||||||
#### 用户被重定向到登录页面的过程
|
#### 用户被重定向到登录页面的过程
|
||||||
1. 用户发送了一个没有经过身份认证的请求到指定资源,并且待请求的资源对该用户来说是未授权的
|
1. 用户发送了一个没有经过身份认证的请求到指定资源,并且待请求的资源对该用户来说是未授权的
|
||||||
2. Spring Security中FilterSecurityInterceptor抛出AccessDeniedException,代表该未授权的请求被拒绝
|
2. Spring Security中FilterSecurityInterceptor抛出AccessDeniedException,代表该未授权的请求被拒绝
|
||||||
3. 因为该用户没有经过认证,故而ExceptionTransactionFilter发起了开始认证的过程,并且使用配置好的AuthenticationEntryPoint向登录页面发起了重定向。在大多数情况下AuthenticationEntryPoint都是LoginUrlAuthenticationEntryPoint
|
3. 因为该用户没有经过认证,故而ExceptionTransactionFilter发起了开始认证的过程,并且使用配置好的AuthenticationEntryPoint向登录页面发起了重定向。在大多数情况下AuthenticationEntryPoint都是LoginUrlAuthenticationEntryPoint
|
||||||
4. 浏览器接下来会请求重定向到的登陆页面
|
4. 浏览器接下来会请求重定向到的登陆页面
|
||||||
|
|
||||||
当用户名和密码提交后,UsernamePasswordAuthenticationFilter会对username和password进行认证。UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter。
|
当用户名和密码提交后,UsernamePasswordAuthenticationFilter会对username和password进行认证。UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter。
|
||||||
|
|
||||||
#### 认证用户名和密码过程
|
#### 认证用户名和密码过程
|
||||||
1. 当用户提交了其用户名和密码之后,UsernamePasswordAuthenticationFilter会创建一个UsernamePasswordAuthenticationToken
|
1. 当用户提交了其用户名和密码之后,UsernamePasswordAuthenticationFilter会创建一个UsernamePasswordAuthenticationToken
|
||||||
2. 创建的UsernamePasswordAuthenticationToken会传入AuthenticationManager中进行认证
|
2. 创建的UsernamePasswordAuthenticationToken会传入AuthenticationManager中进行认证
|
||||||
3. 如果认证失败,那么SecurityContextHolder会被清除,RememberMeService.logFailure和AuthenticationFailureHandler会被调用
|
3. 如果认证失败,那么SecurityContextHolder会被清除,RememberMeService.logFailure和AuthenticationFailureHandler会被调用
|
||||||
4. 如果认证成功,那么SessionAuthenticationStrategy将会收到登录的通知,RemeberMeService.logSuccess和AuthenticationSuccessHandler会被调用,ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent
|
4. 如果认证成功,那么SessionAuthenticationStrategy将会收到登录的通知,RemeberMeService.logSuccess和AuthenticationSuccessHandler会被调用,ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent
|
||||||
|
|
||||||
Spring Security Form Login默认情况下是开启的,但是,一旦任何基于servlet的配置被提供,那么基于表单的login也必须要显式指定。
|
Spring Security Form Login默认情况下是开启的,但是,一旦任何基于servlet的配置被提供,那么基于表单的login也必须要显式指定。
|
||||||
```java
|
```java
|
||||||
// 显式指定form login的配置
|
// 显式指定form login的配置
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||||
http
|
http
|
||||||
.formLogin(withDefaults());
|
.formLogin(withDefaults());
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
如果想要自定义login form page,可以使用如下配置
|
如果想要自定义login form page,可以使用如下配置
|
||||||
```java
|
```java
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||||
http
|
http
|
||||||
.formLogin(form -> form
|
.formLogin(form -> form
|
||||||
.loginPage("/login")
|
.loginPage("/login")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
);
|
);
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### 基本Authentication
|
### 基本Authentication
|
||||||
#### 基本Authentication的认证流程
|
#### 基本Authentication的认证流程
|
||||||
1. 用户向私有资源发送未认证请求,其中对私有资源的访问并没有被授权
|
1. 用户向私有资源发送未认证请求,其中对私有资源的访问并没有被授权
|
||||||
2. SpringSecurity的FilterSecurityInterceptor表明该未认证的请求被拒绝访问,抛出AccessDeniedException
|
2. SpringSecurity的FilterSecurityInterceptor表明该未认证的请求被拒绝访问,抛出AccessDeniedException
|
||||||
3. 由于该请求没有经过身份认证,故而ExceptionTranslationFilter启动身份认证,被配置好的AuthenticationEntryPoint是一个BasicAuthenticationEntryPoint类的实例,该实例会发送WWW-Authentication的header。RequestCache通常是一个NullRequestCache,不会保存任何的http request请求,因为客户端能够重新发送其原来发送过的请求。
|
3. 由于该请求没有经过身份认证,故而ExceptionTranslationFilter启动身份认证,被配置好的AuthenticationEntryPoint是一个BasicAuthenticationEntryPoint类的实例,该实例会发送WWW-Authentication的header。RequestCache通常是一个NullRequestCache,不会保存任何的http request请求,因为客户端能够重新发送其原来发送过的请求。
|
||||||
4. 当客户端获取到WWW-Authentication的header,客户端会知道其接下来会通过username和password重新尝试,重新发送http请求。
|
4. 当客户端获取到WWW-Authentication的header,客户端会知道其接下来会通过username和password重新尝试,重新发送http请求。
|
||||||
|
|
||||||
默认情况下,basic authentication是被开启的。但是,如果有任何基于基于servlet的配置被提供,那么必须通过如下方式显式开启basic authentication。
|
默认情况下,basic authentication是被开启的。但是,如果有任何基于基于servlet的配置被提供,那么必须通过如下方式显式开启basic authentication。
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.httpBasic(withDefaults());
|
.httpBasic(withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Digest Authentication(摘要认证,***不安全***)
|
### Digest Authentication(摘要认证,***不安全***)
|
||||||
在目前,不应该在现代应用程序中使用Digest Authentication,因为使用摘要认证时必须将password通过纯文本、加密或MD5的格式存储(MD5已经被证实不安全)。相对的,应该使用单向的密码散列(如bCrypt, PBKDF2, SCrypt)来存储认证凭证,但是这些都不被Digest Authentication所支持。
|
在目前,不应该在现代应用程序中使用Digest Authentication,因为使用摘要认证时必须将password通过纯文本、加密或MD5的格式存储(MD5已经被证实不安全)。相对的,应该使用单向的密码散列(如bCrypt, PBKDF2, SCrypt)来存储认证凭证,但是这些都不被Digest Authentication所支持。
|
||||||
> 摘要认证主要用来解决Basic Authentication中存在的问题,摘要认证确保了认证凭证在网络上不会以明文的方式传输。
|
> 摘要认证主要用来解决Basic Authentication中存在的问题,摘要认证确保了认证凭证在网络上不会以明文的方式传输。
|
||||||
> 如果想要使用非https的方式并且最大限度的加强认证过程,那么可以考虑使用Digest Authentication。
|
> 如果想要使用非https的方式并且最大限度的加强认证过程,那么可以考虑使用Digest Authentication。
|
||||||
|
|
||||||
#### 摘要认证中的随机数
|
#### 摘要认证中的随机数
|
||||||
摘要认证中的核心是随机数,该随机数的值由服务端产生,Spring Security中随机数次啊用如下格式:
|
摘要认证中的核心是随机数,该随机数的值由服务端产生,Spring Security中随机数次啊用如下格式:
|
||||||
```txt
|
```txt
|
||||||
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
||||||
expirationTime: The date and time when the nonce expires, expressed in milliseconds
|
expirationTime: The date and time when the nonce expires, expressed in milliseconds
|
||||||
key: A private key to prevent modification of the nonce token
|
key: A private key to prevent modification of the nonce token
|
||||||
```
|
```
|
||||||
需要为存储不安全的密码文本配置使用NoOpPasswordEncoder。可以通过如下方式来配置Digest Authentication。
|
需要为存储不安全的密码文本配置使用NoOpPasswordEncoder。可以通过如下方式来配置Digest Authentication。
|
||||||
```java
|
```java
|
||||||
@Autowired
|
@Autowired
|
||||||
UserDetailsService userDetailsService;
|
UserDetailsService userDetailsService;
|
||||||
|
|
||||||
DigestAuthenticationEntryPoint entryPoint() {
|
DigestAuthenticationEntryPoint entryPoint() {
|
||||||
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
|
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
|
||||||
result.setRealmName("My App Relam");
|
result.setRealmName("My App Relam");
|
||||||
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
|
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
|
||||||
}
|
}
|
||||||
|
|
||||||
DigestAuthenticationFilter digestAuthenticationFilter() {
|
DigestAuthenticationFilter digestAuthenticationFilter() {
|
||||||
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
|
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
|
||||||
result.setUserDetailsService(userDetailsService);
|
result.setUserDetailsService(userDetailsService);
|
||||||
result.setAuthenticationEntryPoint(entryPoint());
|
result.setAuthenticationEntryPoint(entryPoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
|
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
|
||||||
.addFilterBefore(digestFilter());
|
.addFilterBefore(digestFilter());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## 密码存储方式
|
## 密码存储方式
|
||||||
### 内存中存储密码
|
### 内存中存储密码
|
||||||
Spring Security中InMemoryUserDetailsManager实现了UserDetailsService,用于向基于存储在内存中的密码认证提供支持。
|
Spring Security中InMemoryUserDetailsManager实现了UserDetailsService,用于向基于存储在内存中的密码认证提供支持。
|
||||||
InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。基于UserDetails的认证主要用来接受基于用户名/密码的认证。
|
InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。基于UserDetails的认证主要用来接受基于用户名/密码的认证。
|
||||||
InMemoryUserDetailsManager可以通过如下方式进行配置:
|
InMemoryUserDetailsManager可以通过如下方式进行配置:
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
public UserDetailsService users() {
|
public UserDetailsService users() {
|
||||||
UserDetails user = User.builder()
|
UserDetails user = User.builder()
|
||||||
.username("user")
|
.username("user")
|
||||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||||
.roles("USER")
|
.roles("USER")
|
||||||
.build();
|
.build();
|
||||||
UserDetails admin = User.builder()
|
UserDetails admin = User.builder()
|
||||||
.username("admin")
|
.username("admin")
|
||||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||||
.roles("USER", "ADMIN")
|
.roles("USER", "ADMIN")
|
||||||
.build();
|
.build();
|
||||||
return new InMemoryUserDetailsManager(user, admin);
|
return new InMemoryUserDetailsManager(user, admin);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### 内存中存储密码时使用defaultPasswordEncoder
|
#### 内存中存储密码时使用defaultPasswordEncoder
|
||||||
***通过defaultPasswordEncoder来指定密码编码器时,无法防止通过反编译字节码来获取密码的攻击。***
|
***通过defaultPasswordEncoder来指定密码编码器时,无法防止通过反编译字节码来获取密码的攻击。***
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
public UserDetailsService users() {
|
public UserDetailsService users() {
|
||||||
// The builder will ensure the passwords are encoded before saving in memory
|
// The builder will ensure the passwords are encoded before saving in memory
|
||||||
UserBuilder users = User.withDefaultPasswordEncoder();
|
UserBuilder users = User.withDefaultPasswordEncoder();
|
||||||
UserDetails user = users
|
UserDetails user = users
|
||||||
.username("user")
|
.username("user")
|
||||||
.password("password")
|
.password("password")
|
||||||
.roles("USER")
|
.roles("USER")
|
||||||
.build();
|
.build();
|
||||||
UserDetails admin = users
|
UserDetails admin = users
|
||||||
.username("admin")
|
.username("admin")
|
||||||
.password("password")
|
.password("password")
|
||||||
.roles("USER", "ADMIN")
|
.roles("USER", "ADMIN")
|
||||||
.build();
|
.build();
|
||||||
return new InMemoryUserDetailsManager(user, admin);
|
return new InMemoryUserDetailsManager(user, admin);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### JDBC Authentication
|
### JDBC Authentication
|
||||||
Spring Security的JdbcDaoImpl实现了UserDetailsService来基于username/password的认证提供从jdbc获取密码的支持。JdbcUserDetailsManager继承了JdbcDaoImpl来通过DetailsManager的接口提供对UserDetails的管理。
|
Spring Security的JdbcDaoImpl实现了UserDetailsService来基于username/password的认证提供从jdbc获取密码的支持。JdbcUserDetailsManager继承了JdbcDaoImpl来通过DetailsManager的接口提供对UserDetails的管理。
|
||||||
Spring Security为基于jdbc的认证提供了默认的查询语句。
|
Spring Security为基于jdbc的认证提供了默认的查询语句。
|
||||||
#### User Schema
|
#### User Schema
|
||||||
JdbcDaoImpl需要数据表来导入密码、账户状态和用户的一系列权限。JdbcDaoImpl默认需要的schema如下:
|
JdbcDaoImpl需要数据表来导入密码、账户状态和用户的一系列权限。JdbcDaoImpl默认需要的schema如下:
|
||||||
```sql
|
```sql
|
||||||
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联
|
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联
|
||||||
# 用户表需要提供username、password、用户状态
|
# 用户表需要提供username、password、用户状态
|
||||||
# 权限表需要提供用户名和权限名称
|
# 权限表需要提供用户名和权限名称
|
||||||
create table users(
|
create table users(
|
||||||
username varchar_ignorecase(50) not null primary key,
|
username varchar_ignorecase(50) not null primary key,
|
||||||
password varchar_ignorecase(500) not null,
|
password varchar_ignorecase(500) not null,
|
||||||
enabled boolean not null
|
enabled boolean not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table authorities (
|
create table authorities (
|
||||||
username varchar_ignorecase(50) not null,
|
username varchar_ignorecase(50) not null,
|
||||||
authority varchar_ignorecase(50) not null,
|
authority varchar_ignorecase(50) not null,
|
||||||
constraint fk_authorities_users foreign key(username) references users(username)
|
constraint fk_authorities_users foreign key(username) references users(username)
|
||||||
);
|
);
|
||||||
create unique index ix_auth_username on authorities (username,authority);
|
create unique index ix_auth_username on authorities (username,authority);
|
||||||
```
|
```
|
||||||
#### Group Schema
|
#### Group Schema
|
||||||
如果你的程序中使用了Group,那么还额外需要一张group的表,默认如下:
|
如果你的程序中使用了Group,那么还额外需要一张group的表,默认如下:
|
||||||
```sql
|
```sql
|
||||||
# 如果要为group配置权限,需要引入三张表,group表,权限表和group_member表
|
# 如果要为group配置权限,需要引入三张表,group表,权限表和group_member表
|
||||||
create table groups (
|
create table groups (
|
||||||
id bigint auto_increment primary key,
|
id bigint auto_increment primary key,
|
||||||
group_name varchar_ignorecase(50) not null
|
group_name varchar_ignorecase(50) not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table group_authorities (
|
create table group_authorities (
|
||||||
group_id bigint not null,
|
group_id bigint not null,
|
||||||
authority varchar(50) not null,
|
authority varchar(50) not null,
|
||||||
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
|
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table group_members (
|
create table group_members (
|
||||||
id bigint auto_increment primary key,
|
id bigint auto_increment primary key,
|
||||||
username varchar(50) not null,
|
username varchar(50) not null,
|
||||||
group_id bigint not null,
|
group_id bigint not null,
|
||||||
constraint fk_group_members_group foreign key(group_id) references groups(id)
|
constraint fk_group_members_group foreign key(group_id) references groups(id)
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 配置Datasource
|
#### 配置Datasource
|
||||||
```java
|
```java
|
||||||
// 生产环境时,应该通过对外部数据库的连接来建立数据源
|
// 生产环境时,应该通过对外部数据库的连接来建立数据源
|
||||||
@Bean
|
@Bean
|
||||||
DataSource dataSource() {
|
DataSource dataSource() {
|
||||||
return new EmbeddedDatabaseBuilder()
|
return new EmbeddedDatabaseBuilder()
|
||||||
.setType(H2)
|
.setType(H2)
|
||||||
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
|
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### 创建JdbcUserDetailsManager Bean对象
|
#### 创建JdbcUserDetailsManager Bean对象
|
||||||
```java
|
```java
|
||||||
@Bean
|
@Bean
|
||||||
UserDetailsManager users(DataSource dataSource) {
|
UserDetailsManager users(DataSource dataSource) {
|
||||||
UserDetails user = User.builder()
|
UserDetails user = User.builder()
|
||||||
.username("user")
|
.username("user")
|
||||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||||
.roles("USER")
|
.roles("USER")
|
||||||
.build();
|
.build();
|
||||||
UserDetails admin = User.builder()
|
UserDetails admin = User.builder()
|
||||||
.username("admin")
|
.username("admin")
|
||||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||||
.roles("USER", "ADMIN")
|
.roles("USER", "ADMIN")
|
||||||
.build();
|
.build();
|
||||||
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
|
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
|
||||||
users.createUser(user);
|
users.createUser(user);
|
||||||
users.createUser(admin);
|
users.createUser(admin);
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### UserDetails
|
### UserDetails
|
||||||
UserDetails是通过UserDetailsService返回的。DaoAuthenticationProvider对UserrDetails进行验证并且返回Authentication.
|
UserDetails是通过UserDetailsService返回的。DaoAuthenticationProvider对UserrDetails进行验证并且返回Authentication.
|
||||||
|
|
||||||
### UserDetailsService
|
### UserDetailsService
|
||||||
UserDetailsService被DaoAuthenticationProvider调用,用来获取username、password和其他随着password/username一起认证的信息。对于UserDetailsService,Spring Security提供了in-memory和jdbc两种实现形式。
|
UserDetailsService被DaoAuthenticationProvider调用,用来获取username、password和其他随着password/username一起认证的信息。对于UserDetailsService,Spring Security提供了in-memory和jdbc两种实现形式。
|
||||||
可以通过自定义UserDetailsService类bean对象的方式来自定义认证过程。
|
可以通过自定义UserDetailsService类bean对象的方式来自定义认证过程。
|
||||||
```java
|
```java
|
||||||
// 自定义UserDetailsService的bean对象
|
// 自定义UserDetailsService的bean对象
|
||||||
@Bean
|
@Bean
|
||||||
CustomUserDetailsService customUserDetailsService() {
|
CustomUserDetailsService customUserDetailsService() {
|
||||||
return new CustomUserDetailsService();
|
return new CustomUserDetailsService();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### PasswordEncoder
|
### PasswordEncoder
|
||||||
Spring Security支持PasswordEncoder来安全的存储密码。可以通过自定义PasswordEncoder类的bean对象的形式来自定义Spring Security安全存储密码的过程。
|
Spring Security支持PasswordEncoder来安全的存储密码。可以通过自定义PasswordEncoder类的bean对象的形式来自定义Spring Security安全存储密码的过程。
|
||||||
|
|
||||||
### DaoAuthenticationProvider
|
### DaoAuthenticationProvider
|
||||||
DaoAuthenticationProvider是AuthenticationProvider的一个实现类,通过调用UserDetailsService和PasswordEncoder来认证用户名和密码。
|
DaoAuthenticationProvider是AuthenticationProvider的一个实现类,通过调用UserDetailsService和PasswordEncoder来认证用户名和密码。
|
||||||
Spring Security中DaoAuthenticationProvider的工作流程:
|
Spring Security中DaoAuthenticationProvider的工作流程:
|
||||||
1. authentication filter会读取username和password并且将其封装到UsernamePasswordAuthenticationToken中传递给AuthenticationManager,ProviderManager实现了AuthenticationManager
|
1. authentication filter会读取username和password并且将其封装到UsernamePasswordAuthenticationToken中传递给AuthenticationManager,ProviderManager实现了AuthenticationManager
|
||||||
2. ProviderManager被配置为使用DaoAuthenticationProvider
|
2. ProviderManager被配置为使用DaoAuthenticationProvider
|
||||||
3. DaoAuthenticationProvider通过UserDetailsService来查找UserDetails
|
3. DaoAuthenticationProvider通过UserDetailsService来查找UserDetails
|
||||||
4. DaoAuthenticationProvider通过PasswordEncoder来验证UserDetails中的密码
|
4. DaoAuthenticationProvider通过PasswordEncoder来验证UserDetails中的密码
|
||||||
5. 当验证成功时,会返回UsernamePasswordAuthenticationToken类型的Authentication,并且返回的Authentication拥有一个主体为UserDetailsService返回的UserDetails
|
5. 当验证成功时,会返回UsernamePasswordAuthenticationToken类型的Authentication,并且返回的Authentication拥有一个主体为UserDetailsService返回的UserDetails
|
||||||
6. 返回的UsernamePasswordAuthenticationToken会在SecurityContextHolder中保存
|
6. 返回的UsernamePasswordAuthenticationToken会在SecurityContextHolder中保存
|
||||||
|
|
||||||
|
|||||||
12
spring/Spring core/@Validated, @Valid.md
Normal file
12
spring/Spring core/@Validated, @Valid.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# @Validated & @Valid
|
||||||
|
## @Validated
|
||||||
|
@Validated可以用于方法级别,方法参数级别以及类级别
|
||||||
|
- 类级别:当@Validated注解用于类级别时,该类中所有的约束(例如@Max,@Size)都会被校验
|
||||||
|
- 参数级别:类似于@Valid
|
||||||
|
- 方法级别:将@Validated注解用于方法级别,会override group信息,但是不会插入切面
|
||||||
|
|
||||||
|
可以将@Validated作用于spring mvc handler的参数,也可以将其作为方法级别的验证。方法级别的验证允许覆盖validation group,但是不会作为切面。
|
||||||
|
> @Validated实现原理基于spring aop,故而只有标注了@Validated注解的bean对象才会被代理并拦截
|
||||||
|
|
||||||
|
## @Valid
|
||||||
|
相对于@Validated注解,@Valid注解允许应用于返回类型和field上,故而通过@Valid注解可以用于嵌套类的校验
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
# POJO
|
# POJO
|
||||||
## POJO定义
|
## POJO定义
|
||||||
POJO(Plain Old Java Object)是一种直接的类型,POJO并不包含对任何框架的引用。
|
POJO(Plain Old Java Object)是一种直接的类型,POJO并不包含对任何框架的引用。
|
||||||
> 对于POJO类型,该类属性和方法的定义并没有特定的约束和限制
|
> 对于POJO类型,该类属性和方法的定义并没有特定的约束和限制
|
||||||
## Java Bean命名约束
|
## Java Bean命名约束
|
||||||
由于对POJO本身并没有对POJO类属性和方法的定义强制指定命名约束,因而许多框默认支持Java Bean命名约束。
|
由于对POJO本身并没有对POJO类属性和方法的定义强制指定命名约束,因而许多框默认支持Java Bean命名约束。
|
||||||
> ### Java Bean命名约束
|
> ### Java Bean命名约束
|
||||||
> 在Java Bean命名约束中,为POJO类属性和方法的命名指定了如下规则:
|
> 在Java Bean命名约束中,为POJO类属性和方法的命名指定了如下规则:
|
||||||
> 1. 属性的访问权限都被设置为private,属性通过getter和setter向外暴露
|
> 1. 属性的访问权限都被设置为private,属性通过getter和setter向外暴露
|
||||||
> 2. 对于方法的命名,getter和setter遵循getXXX/setXXX的命名规范(对于boolean属性的getter,可以使用isXXX形式
|
> 2. 对于方法的命名,getter和setter遵循getXXX/setXXX的命名规范(对于boolean属性的getter,可以使用isXXX形式
|
||||||
> 3. Java Bean命名规范要求Java Bean对象需要提供无参构造方法
|
> 3. Java Bean命名规范要求Java Bean对象需要提供无参构造方法
|
||||||
> 4. 实现Serializable接口,能够将对象以二进制的格式进行存储
|
> 4. 实现Serializable接口,能够将对象以二进制的格式进行存储
|
||||||
## 其他命名规范
|
## 其他命名规范
|
||||||
由于Java Bean命名规范中有些规则强制对Java Bean的命名进行限制可能会带来弊端,故而如今许多框架在接受Java Bean命名规范之余,仍然支持其他的POJO命名规范
|
由于Java Bean命名规范中有些规则强制对Java Bean的命名进行限制可能会带来弊端,故而如今许多框架在接受Java Bean命名规范之余,仍然支持其他的POJO命名规范
|
||||||
> 如在Spring中,通过@Component注解注册Bean对象时,被@Component注解的类并不一定要实现Serializable接口,也不一定要拥有无参构造方法。
|
> 如在Spring中,通过@Component注解注册Bean对象时,被@Component注解的类并不一定要实现Serializable接口,也不一定要拥有无参构造方法。
|
||||||
|
|||||||
@@ -1,109 +1,109 @@
|
|||||||
# SpEL(Spring Expression Language)
|
# SpEL(Spring Expression Language)
|
||||||
- ## SpEL的用法
|
- ## SpEL的用法
|
||||||
- SpEL如何将表达式从字符串转化为计算后的值
|
- SpEL如何将表达式从字符串转化为计算后的值
|
||||||
- 在转化过程中,在parseExpression方法执行时可能会抛出ParseException异常,在执行getValue方法时可能会抛出EvaluationException
|
- 在转化过程中,在parseExpression方法执行时可能会抛出ParseException异常,在执行getValue方法时可能会抛出EvaluationException
|
||||||
```java
|
```java
|
||||||
ExpressionParser parser = new SpelExpressionParser();
|
ExpressionParser parser = new SpelExpressionParser();
|
||||||
Expression exp = parser.parseExpression("'Hello World'");
|
Expression exp = parser.parseExpression("'Hello World'");
|
||||||
String message = (String) exp.getValue();
|
String message = (String) exp.getValue();
|
||||||
```
|
```
|
||||||
- 在SpEL中获取String的字节数组
|
- 在SpEL中获取String的字节数组
|
||||||
```java
|
```java
|
||||||
ExpressionParser parser=new SpelExpressionParser();
|
ExpressionParser parser=new SpelExpressionParser();
|
||||||
Expression exp=parser.parseExpression("'Hello World'.bytes");
|
Expression exp=parser.parseExpression("'Hello World'.bytes");
|
||||||
byte[] bytes=(byte[])exp.getValue();
|
byte[] bytes=(byte[])exp.getValue();
|
||||||
```
|
```
|
||||||
- 在调用Expression类型的getValue方法时,可以不用进行强制类型转换,而是在getValue方法中传入一个Class参数,返回值将会被自动转换成Class对应的目标类型,当转换失败时会抛出EvaluationException
|
- 在调用Expression类型的getValue方法时,可以不用进行强制类型转换,而是在getValue方法中传入一个Class参数,返回值将会被自动转换成Class对应的目标类型,当转换失败时会抛出EvaluationException
|
||||||
```java
|
```java
|
||||||
ExpressionParser parser=new SpelExpressionParser();
|
ExpressionParser parser=new SpelExpressionParser();
|
||||||
Expression exp=parser.parseExpression("'Hello World'.bytes.length");
|
Expression exp=parser.parseExpression("'Hello World'.bytes.length");
|
||||||
Integer bytes=exp.getValue(Integer.class);
|
Integer bytes=exp.getValue(Integer.class);
|
||||||
```
|
```
|
||||||
- SpEL可以针对特定的对象,给出一个表达式并且在getValue方法中传入一个对象,那么表达式中的变量将会针对该对象中的特定属性
|
- SpEL可以针对特定的对象,给出一个表达式并且在getValue方法中传入一个对象,那么表达式中的变量将会针对该对象中的特定属性
|
||||||
```java
|
```java
|
||||||
// 如下步骤会比较waifu对象的name属性是否为"touma"字符串
|
// 如下步骤会比较waifu对象的name属性是否为"touma"字符串
|
||||||
ExpressionParser parser=new SpelExpressionParser();
|
ExpressionParser parser=new SpelExpressionParser();
|
||||||
Expression exp=parser.parseExpression("name=='touma'");
|
Expression exp=parser.parseExpression("name=='touma'");
|
||||||
Boolean equals=exp.getValue(waifu,Boolean.class);
|
Boolean equals=exp.getValue(waifu,Boolean.class);
|
||||||
```
|
```
|
||||||
- 可以为parser设置一个parserconfiguration,用于处理当列表或集合元素的index操作超过集合长度时的默认行为
|
- 可以为parser设置一个parserconfiguration,用于处理当列表或集合元素的index操作超过集合长度时的默认行为
|
||||||
```java
|
```java
|
||||||
class Demo {
|
class Demo {
|
||||||
public List<String> list;
|
public List<String> list;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn on:
|
// Turn on:
|
||||||
// - auto null reference initialization
|
// - auto null reference initialization
|
||||||
// - auto collection growing
|
// - auto collection growing
|
||||||
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
|
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
|
||||||
|
|
||||||
ExpressionParser parser = new SpelExpressionParser(config);
|
ExpressionParser parser = new SpelExpressionParser(config);
|
||||||
|
|
||||||
Expression expression = parser.parseExpression("list[3]");
|
Expression expression = parser.parseExpression("list[3]");
|
||||||
|
|
||||||
Demo demo = new Demo();
|
Demo demo = new Demo();
|
||||||
|
|
||||||
Object o = expression.getValue(demo);
|
Object o = expression.getValue(demo);
|
||||||
|
|
||||||
// demo.list will now be a real collection of 4 entries
|
// demo.list will now be a real collection of 4 entries
|
||||||
// Each entry is a new empty String
|
// Each entry is a new empty String
|
||||||
```
|
```
|
||||||
- ## SpEL在bean对象定义时的使用
|
- ## SpEL在bean对象定义时的使用
|
||||||
- 在使用@Value注解时,可以结合SpEL表达式进行使用,@Value注解可以运用在域变量、方法、方法和构造器的参数上。@Value会指定默认值
|
- 在使用@Value注解时,可以结合SpEL表达式进行使用,@Value注解可以运用在域变量、方法、方法和构造器的参数上。@Value会指定默认值
|
||||||
- ## SpEL对List、Map的支持
|
- ## SpEL对List、Map的支持
|
||||||
- 可以通过{}来直接表示list
|
- 可以通过{}来直接表示list
|
||||||
```java
|
```java
|
||||||
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
|
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
|
||||||
|
|
||||||
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
|
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
|
||||||
```
|
```
|
||||||
- 可以通过{key:value}形式来直接表示map,空map用{:}来进行表示
|
- 可以通过{key:value}形式来直接表示map,空map用{:}来进行表示
|
||||||
```java
|
```java
|
||||||
// evaluates to a Java map containing the two entries
|
// evaluates to a Java map containing the two entries
|
||||||
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
|
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
|
||||||
|
|
||||||
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
|
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
|
||||||
```
|
```
|
||||||
- 可以通过new int[]{}的形式为SpEL指定数组
|
- 可以通过new int[]{}的形式为SpEL指定数组
|
||||||
```java
|
```java
|
||||||
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
|
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
|
||||||
|
|
||||||
// Array with initializer
|
// Array with initializer
|
||||||
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
|
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
|
||||||
|
|
||||||
// Multi dimensional array
|
// Multi dimensional array
|
||||||
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
|
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
|
||||||
```
|
```
|
||||||
- ## SpEL支持的特殊操作符
|
- ## SpEL支持的特殊操作符
|
||||||
- instanceof
|
- instanceof
|
||||||
```java
|
```java
|
||||||
boolean falseValue = parser.parseExpression(
|
boolean falseValue = parser.parseExpression(
|
||||||
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
||||||
```
|
```
|
||||||
- 正则表达式
|
- 正则表达式
|
||||||
```java
|
```java
|
||||||
boolean trueValue = parser.parseExpression(
|
boolean trueValue = parser.parseExpression(
|
||||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||||
```
|
```
|
||||||
- 类型操作符,获取类型的Class对象、调用静态方法
|
- 类型操作符,获取类型的Class对象、调用静态方法
|
||||||
```java
|
```java
|
||||||
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
|
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
|
||||||
|
|
||||||
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
|
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
|
||||||
|
|
||||||
boolean trueValue = parser.parseExpression(
|
boolean trueValue = parser.parseExpression(
|
||||||
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
|
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
|
||||||
.getValue(Boolean.class);
|
.getValue(Boolean.class);
|
||||||
```
|
```
|
||||||
- new操作符:
|
- new操作符:
|
||||||
- 可以在SpEL表达式中通过new操作符来调用构造器,但是除了位于java.lang包中的类,对其他的类调用构造器时都必须指定类的全类名
|
- 可以在SpEL表达式中通过new操作符来调用构造器,但是除了位于java.lang包中的类,对其他的类调用构造器时都必须指定类的全类名
|
||||||
```java
|
```java
|
||||||
Inventor einstein = p.parseExpression(
|
Inventor einstein = p.parseExpression(
|
||||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||||
.getValue(Inventor.class);
|
.getValue(Inventor.class);
|
||||||
|
|
||||||
// create new Inventor instance within the add() method of List
|
// create new Inventor instance within the add() method of List
|
||||||
p.parseExpression(
|
p.parseExpression(
|
||||||
"Members.add(new org.spring.samples.spel.inventor.Inventor(
|
"Members.add(new org.spring.samples.spel.inventor.Inventor(
|
||||||
'Albert Einstein', 'German'))").getValue(societyContext);
|
'Albert Einstein', 'German'))").getValue(societyContext);
|
||||||
```
|
```
|
||||||
@@ -1,177 +1,177 @@
|
|||||||
# Spring AOP
|
# Spring AOP
|
||||||
- ## Spring AOP的核心概念
|
- ## Spring AOP的核心概念
|
||||||
- Aspect:切面,一个模块化的考虑
|
- Aspect:切面,一个模块化的考虑
|
||||||
- Joint Point:连接点,程序执行时的一个时间点,通常是方法的执行
|
- Joint Point:连接点,程序执行时的一个时间点,通常是方法的执行
|
||||||
- Advice:当切面在一个切入点执行多做时,执行的动作被称之为Advice,Advice有不同的类型:before、after、around
|
- Advice:当切面在一个切入点执行多做时,执行的动作被称之为Advice,Advice有不同的类型:before、after、around
|
||||||
- Pointcut:切入点,advice通常运行在满足pointcut的join point上,pointcut表达式与join point相关联,Spring中默认使用AspectJ切入点表达式
|
- Pointcut:切入点,advice通常运行在满足pointcut的join point上,pointcut表达式与join point相关联,Spring中默认使用AspectJ切入点表达式
|
||||||
- Introduction:在类中声明新的方法、域变量甚至是接口实现
|
- Introduction:在类中声明新的方法、域变量甚至是接口实现
|
||||||
- linking:将应用类型或对象和切面链接起来
|
- linking:将应用类型或对象和切面链接起来
|
||||||
- ## Spring AOP的类型
|
- ## Spring AOP的类型
|
||||||
- before:在连接点之前运行,但是无法阻止后续连接点的执行
|
- before:在连接点之前运行,但是无法阻止后续连接点的执行
|
||||||
- after returning:在连接点正常返回之后进行
|
- after returning:在连接点正常返回之后进行
|
||||||
- after throwing:在链接点抛出异常正常退出之后进行
|
- after throwing:在链接点抛出异常正常退出之后进行
|
||||||
- after finally:上两种的结合,不管连接点是正常退出还是抛出异常退出,都会在其之后执行
|
- after finally:上两种的结合,不管连接点是正常退出还是抛出异常退出,都会在其之后执行
|
||||||
- around:around可以自定义连接点之前和之后的执行内容,其也能够选择时候执行连接点的方法
|
- around:around可以自定义连接点之前和之后的执行内容,其也能够选择时候执行连接点的方法
|
||||||
- ## Spring AOP的特点
|
- ## Spring AOP的特点
|
||||||
- 区别于AspectJ AOP框架,Spring AOP框架是基于代理来实现的
|
- 区别于AspectJ AOP框架,Spring AOP框架是基于代理来实现的
|
||||||
- 对于实现了接口的类,Spring AOP通常是通过JDK动态代理来实现的,对于没有实现接口的类,Spring AOP是通过cglib来实现的
|
- 对于实现了接口的类,Spring AOP通常是通过JDK动态代理来实现的,对于没有实现接口的类,Spring AOP是通过cglib来实现的
|
||||||
- 可以强制Spring AOP使用cglib,在如下场景:
|
- 可以强制Spring AOP使用cglib,在如下场景:
|
||||||
- 如果想要advise类中方法,而该方法没有在接口中定义
|
- 如果想要advise类中方法,而该方法没有在接口中定义
|
||||||
- 如果想要将代理对象传递给一个具有特定类型的方法作为参数
|
- 如果想要将代理对象传递给一个具有特定类型的方法作为参数
|
||||||
- ## Spring AOP的AspectJ注解支持
|
- ## Spring AOP的AspectJ注解支持
|
||||||
- Spring AOP支持AspectJ注解,Spring AOP可以解释和AspectJ 5相同的注解,通过使用AspectJ提供的包来进行切入点解析和匹配
|
- Spring AOP支持AspectJ注解,Spring AOP可以解释和AspectJ 5相同的注解,通过使用AspectJ提供的包来进行切入点解析和匹配
|
||||||
- 但是,即使使用了AspectJ注解,AOP在运行时仍然是纯粹的Spring AOP,项目不需要引入AspectJ的编译器和weaver
|
- 但是,即使使用了AspectJ注解,AOP在运行时仍然是纯粹的Spring AOP,项目不需要引入AspectJ的编译器和weaver
|
||||||
- Spring AOP对AspectJ注解支持的开启
|
- Spring AOP对AspectJ注解支持的开启
|
||||||
- 通过@EnableAspectJAutoProxy注解,会自动的为满足切入点匹配的连接点bean对象创建移动代理对象
|
- 通过@EnableAspectJAutoProxy注解,会自动的为满足切入点匹配的连接点bean对象创建移动代理对象
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAspectJAutoProxy
|
@EnableAspectJAutoProxy
|
||||||
class AspectJConfiguration {
|
class AspectJConfiguration {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## 声明Spring AOP切面
|
- ## 声明Spring AOP切面
|
||||||
- 在容器中,任何bean对象,如其类型具有@AspectJ注解,将会被自动探知到并且用来配置spring aop
|
- 在容器中,任何bean对象,如其类型具有@AspectJ注解,将会被自动探知到并且用来配置spring aop
|
||||||
- 在Spring AOP中,aspect其自身是无法作为其他aspect的目标对象的。被标记为@Aspect的类,不仅标明其为aspect,并且将其从自动代理中排除
|
- 在Spring AOP中,aspect其自身是无法作为其他aspect的目标对象的。被标记为@Aspect的类,不仅标明其为aspect,并且将其从自动代理中排除
|
||||||
- 如果为某个bean对象配置了切面,那么在后续创建该bean对象时,实际上是创建该bean对象的代理对象
|
- 如果为某个bean对象配置了切面,那么在后续创建该bean对象时,实际上是创建该bean对象的代理对象
|
||||||
```java
|
```java
|
||||||
@Component // 将该类型声明为bean对象
|
@Component // 将该类型声明为bean对象
|
||||||
@Aspect // 声明切面
|
@Aspect // 声明切面
|
||||||
public class ProxyAspect {
|
public class ProxyAspect {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## 声明Spring AOP切入点
|
- ## 声明Spring AOP切入点
|
||||||
- 由于Spring AOP仅仅支持方法的连接点,故而可以将切入点看做对bean对象方法的匹配
|
- 由于Spring AOP仅仅支持方法的连接点,故而可以将切入点看做对bean对象方法的匹配
|
||||||
- Join Point expression的种类:
|
- Join Point expression的种类:
|
||||||
- execution:匹配目标方法的执行,可以在括号中接收一个函数签名,包含返回类型、函数名和函数参数类型
|
- execution:匹配目标方法的执行,可以在括号中接收一个函数签名,包含返回类型、函数名和函数参数类型
|
||||||
```java
|
```java
|
||||||
// 被@JointPoint注解标注的方法必须具有void的返回类型
|
// 被@JointPoint注解标注的方法必须具有void的返回类型
|
||||||
@Pointcut("execution(* Point.*(..))")
|
@Pointcut("execution(* Point.*(..))")
|
||||||
void methodInjected() {
|
void methodInjected() {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- within:匹配声明在某一特定类中的方法
|
- within:匹配声明在某一特定类中的方法
|
||||||
```java
|
```java
|
||||||
@Pointcut("within(Point)")
|
@Pointcut("within(Point)")
|
||||||
```
|
```
|
||||||
- this:匹配生成的代理对象为该类型的一个实例
|
- this:匹配生成的代理对象为该类型的一个实例
|
||||||
- target:匹配目标对象为该类型的一个实例
|
- target:匹配目标对象为该类型的一个实例
|
||||||
- args:匹配特定参数
|
- args:匹配特定参数
|
||||||
- @args:传递参数的类型具有指定的注解
|
- @args:传递参数的类型具有指定的注解
|
||||||
- @target:运行时该对象的类具有指定的注解
|
- @target:运行时该对象的类具有指定的注解
|
||||||
- @within:运行时执行的方法,其方法定义在具有指定注解的类中(可以是继承父类的方法,父类指定了注解
|
- @within:运行时执行的方法,其方法定义在具有指定注解的类中(可以是继承父类的方法,父类指定了注解
|
||||||
- @annotation:执行的方法具有指定注解
|
- @annotation:执行的方法具有指定注解
|
||||||
- Spring AOP同样支持将JoinPoint匹配为具有特定name的Spring bean对象
|
- Spring AOP同样支持将JoinPoint匹配为具有特定name的Spring bean对象
|
||||||
```java
|
```java
|
||||||
@Pointcut("bean(nameA) || bean(nameB))")
|
@Pointcut("bean(nameA) || bean(nameB))")
|
||||||
```
|
```
|
||||||
- ## Spring AOP中的Advice
|
- ## Spring AOP中的Advice
|
||||||
- Advice和Pointcut Expresion相关联,主要可以分为before、after、around等种类
|
- Advice和Pointcut Expresion相关联,主要可以分为before、after、around等种类
|
||||||
- Before:
|
- Before:
|
||||||
```java
|
```java
|
||||||
@Before("execution(* Point.*(..))")
|
@Before("execution(* Point.*(..))")
|
||||||
public void doSomething() {
|
public void doSomething() {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- AfterReturning:
|
- AfterReturning:
|
||||||
```java
|
```java
|
||||||
// AfterReturning支持获取切入点执行后返回的值
|
// AfterReturning支持获取切入点执行后返回的值
|
||||||
@AfterReturning(
|
@AfterReturning(
|
||||||
pointcut="execution(* Point.*(..))",
|
pointcut="execution(* Point.*(..))",
|
||||||
returning="retVal")
|
returning="retVal")
|
||||||
public void doSomething(int retVal) {
|
public void doSomething(int retVal) {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- AfterThrowing:
|
- AfterThrowing:
|
||||||
```java
|
```java
|
||||||
@AfterThrowing(
|
@AfterThrowing(
|
||||||
pointcut="execution(* Point.*())",
|
pointcut="execution(* Point.*())",
|
||||||
throwing="ex"
|
throwing="ex"
|
||||||
)
|
)
|
||||||
public void doSomething(Throwable ex) {
|
public void doSomething(Throwable ex) {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- After:After不管是切入点正常返回还是抛出异常,都会执行,类似于finally
|
- After:After不管是切入点正常返回还是抛出异常,都会执行,类似于finally
|
||||||
```java
|
```java
|
||||||
@After("execution(* Point.*())")
|
@After("execution(* Point.*())")
|
||||||
public void doSomething() {
|
public void doSomething() {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Around:其方法必须会一个Oject类型的返回值,并且方法的第一个参数类型是ProceedingJoinPoint
|
- Around:其方法必须会一个Oject类型的返回值,并且方法的第一个参数类型是ProceedingJoinPoint
|
||||||
```java
|
```java
|
||||||
@Around("execution(* Point.*())")
|
@Around("execution(* Point.*())")
|
||||||
public Object doSomething(ProceedingJoinPoint pjp) {
|
public Object doSomething(ProceedingJoinPoint pjp) {
|
||||||
return isCacheExisted()?returnFromCache():pjp.proceed();
|
return isCacheExisted()?returnFromCache():pjp.proceed();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## Spring AOP中Advice方法对JoinPoint的访问
|
- ## Spring AOP中Advice方法对JoinPoint的访问
|
||||||
- 任何advice方法,都可以声明声明其第一个参数为JoinPoint类型。@Around标注的adivce方法其第一个参数的类型必须为ProceedingJoinPoint类型,该类型为JoinPoint的子类型
|
- 任何advice方法,都可以声明声明其第一个参数为JoinPoint类型。@Around标注的adivce方法其第一个参数的类型必须为ProceedingJoinPoint类型,该类型为JoinPoint的子类型
|
||||||
- JoinPoint接口具有如下方法:
|
- JoinPoint接口具有如下方法:
|
||||||
- getArgs:返回方法参数
|
- getArgs:返回方法参数
|
||||||
- getThis:返回代理对象
|
- getThis:返回代理对象
|
||||||
- getTarget:返回目标对象
|
- getTarget:返回目标对象
|
||||||
- getSignature:返回函数的签名
|
- getSignature:返回函数的签名
|
||||||
- toString:返回该advice方法的描述信息
|
- toString:返回该advice方法的描述信息
|
||||||
- ## Advice方法通过参数来获取传递给底层方法的参数
|
- ## Advice方法通过参数来获取传递给底层方法的参数
|
||||||
- 在pointcut表达式的args中,如果用advice方法中的参数名来代替参数类型,那么该类型的参数值会被传递给该参数
|
- 在pointcut表达式的args中,如果用advice方法中的参数名来代替参数类型,那么该类型的参数值会被传递给该参数
|
||||||
```java
|
```java
|
||||||
@Before("execution(* Point.*(..) && args(position,..))")
|
@Before("execution(* Point.*(..) && args(position,..))")
|
||||||
public void adviceMethod(Position position) {
|
public void adviceMethod(Position position) {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 或者,可以通过如下方式,先通过一个Pointcut获取参数,在在另一个方法中获取named pointcut已获取的参数
|
- 或者,可以通过如下方式,先通过一个Pointcut获取参数,在在另一个方法中获取named pointcut已获取的参数
|
||||||
```java
|
```java
|
||||||
// 此时,adviceMethodTwo同样能够获取Position参数
|
// 此时,adviceMethodTwo同样能够获取Position参数
|
||||||
@Pointcut("execution(* Point.*(..)) && args(position,..)")
|
@Pointcut("execution(* Point.*(..)) && args(position,..)")
|
||||||
public void adviceMethodOne(Position position) {
|
public void adviceMethodOne(Position position) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before("adviceMethodOne(position)")
|
@Before("adviceMethodOne(position)")
|
||||||
public void adviceMethodTwo(Position position) {
|
public void adviceMethodTwo(Position position) {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Spring AOP可以通过如下方式来约束泛型的参数
|
- Spring AOP可以通过如下方式来约束泛型的参数
|
||||||
```java
|
```java
|
||||||
@Before("execution(* GenericsInterface+.method(*) && args(param))")
|
@Before("execution(* GenericsInterface+.method(*) && args(param))")
|
||||||
public void adviceMethod(DesiredType param) {
|
public void adviceMethod(DesiredType param) {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## 通过Spring AOP对参数进行预处理
|
- ## 通过Spring AOP对参数进行预处理
|
||||||
```java
|
```java
|
||||||
@Around("execution(* Point.area(*) && args(width,height))")
|
@Around("execution(* Point.area(*) && args(width,height))")
|
||||||
public double caculateInCM(ProceedingJoinPoint jp,double width,double height) {
|
public double caculateInCM(ProceedingJoinPoint jp,double width,double height) {
|
||||||
width*=100;
|
width*=100;
|
||||||
height*=100;
|
height*=100;
|
||||||
return jp.proceed(width,height);
|
return jp.proceed(width,height);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## Spring AOP中多个advice对应到同一个Pointcut
|
- ## Spring AOP中多个advice对应到同一个Pointcut
|
||||||
- 如果多个advice都具有相同的pointcut,那么多个advice之间的执行顺序是未定义的。可以为Aspect类实现Ordered接口,或者添加@Order标记来定义该advice的执行优先级,那么具有具有较小order值的方法将会优先被执行
|
- 如果多个advice都具有相同的pointcut,那么多个advice之间的执行顺序是未定义的。可以为Aspect类实现Ordered接口,或者添加@Order标记来定义该advice的执行优先级,那么具有具有较小order值的方法将会优先被执行
|
||||||
- ## Spring AOP Introduction
|
- ## Spring AOP Introduction
|
||||||
- 在Spring AOP中,可以通过Introduction来声明一个对象继承了某接口,并且为被代理的对象提供被继承接口的实现
|
- 在Spring AOP中,可以通过Introduction来声明一个对象继承了某接口,并且为被代理的对象提供被继承接口的实现
|
||||||
- 可以通过@DeclareParent注解为指定对象添加接口并且指明该接口默认的实现类,完成后可以直接将生成的代理对象复制给接口变量
|
- 可以通过@DeclareParent注解为指定对象添加接口并且指明该接口默认的实现类,完成后可以直接将生成的代理对象复制给接口变量
|
||||||
```java
|
```java
|
||||||
@Aspect
|
@Aspect
|
||||||
public class MyAspect {
|
public class MyAspect {
|
||||||
@DeclareParent(value="cc.rikakonatsumi.interfaces.*+",defaultImpl=DefaultImpl.class)
|
@DeclareParent(value="cc.rikakonatsumi.interfaces.*+",defaultImpl=DefaultImpl.class)
|
||||||
private static MyInterface myInterface;
|
private static MyInterface myInterface;
|
||||||
|
|
||||||
// 之后,可以直接通过this(ref)在pointcut表达式中获取服务对象,也可以通过getBean方法获取容器中的对象
|
// 之后,可以直接通过this(ref)在pointcut表达式中获取服务对象,也可以通过getBean方法获取容器中的对象
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## @RestControllerAdvice的使用
|
- ## @RestControllerAdvice的使用
|
||||||
- @RestControllerAdvice是@Componnent注解的一个特例,@RestControllerAdivce注解的组成包含@Component
|
- @RestControllerAdvice是@Componnent注解的一个特例,@RestControllerAdivce注解的组成包含@Component
|
||||||
- @RestControllerAdivce组合了@ControllerAdvice和@ResponseBody两个注解
|
- @RestControllerAdivce组合了@ControllerAdvice和@ResponseBody两个注解
|
||||||
- 通常,@RestControllerAdvice用作为spring mvc的所有方法做ExceptionHandler
|
- 通常,@RestControllerAdvice用作为spring mvc的所有方法做ExceptionHandler
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,183 +1,183 @@
|
|||||||
# Spring Core IOC
|
# Spring Core IOC
|
||||||
- ## IOC容器和bean简介
|
- ## IOC容器和bean简介
|
||||||
- IOC简介:
|
- IOC简介:
|
||||||
- IOC(控制反转)也被称之为依赖注入(DI),对象通过构造函数参数、工厂方法参数、或者在构造后通过setter来设置属性来定义依赖。在对象被创建时,IOC容器会将依赖注入到bean对象中,
|
- IOC(控制反转)也被称之为依赖注入(DI),对象通过构造函数参数、工厂方法参数、或者在构造后通过setter来设置属性来定义依赖。在对象被创建时,IOC容器会将依赖注入到bean对象中,
|
||||||
- IOC容器:
|
- IOC容器:
|
||||||
- IOC容器接口:
|
- IOC容器接口:
|
||||||
- BeanFactory:BeanFactory是一个接口,提供了高级配置功能来管理任何类型的对象
|
- BeanFactory:BeanFactory是一个接口,提供了高级配置功能来管理任何类型的对象
|
||||||
- ApplicationContext:ApplicationContext是BeanFactory的一个子接口,在BeanFactory的基础上,其添加了一些更为特殊的特性。
|
- ApplicationContext:ApplicationContext是BeanFactory的一个子接口,在BeanFactory的基础上,其添加了一些更为特殊的特性。
|
||||||
- IOC容器职责
|
- IOC容器职责
|
||||||
- IOC容器负责来初始化、配置、组装bean对象
|
- IOC容器负责来初始化、配置、组装bean对象
|
||||||
- ## 基于注解的Spring容器配置
|
- ## 基于注解的Spring容器配置
|
||||||
- @Required
|
- @Required
|
||||||
- @Required应用于bean对象属性的setter方法,表示该属性在配置时必须被填充,通过依赖注入或是用xml定义bean时显式指定值
|
- @Required应用于bean对象属性的setter方法,表示该属性在配置时必须被填充,通过依赖注入或是用xml定义bean时显式指定值
|
||||||
- 该注解当前已经被弃用
|
- 该注解当前已经被弃用
|
||||||
- @Autowired
|
- @Autowired
|
||||||
- 通过在构造函数上标明@Autowired来对方法参数进行注入
|
- 通过在构造函数上标明@Autowired来对方法参数进行注入
|
||||||
- 当在构造函数上标记@Autowired时,如果当前类中只有一个构造函数,那么@Autowired注解可以被省略;如果当前类有多个构造函数,那么应该在某个构造函数上指明@Autowired注解
|
- 当在构造函数上标记@Autowired时,如果当前类中只有一个构造函数,那么@Autowired注解可以被省略;如果当前类有多个构造函数,那么应该在某个构造函数上指明@Autowired注解
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
class Shiromiya {
|
class Shiromiya {
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public Shiromiya(JdbcTemplate jdbcTemplate) {
|
public Shiromiya(JdbcTemplate jdbcTemplate) {
|
||||||
this.jdbcTemplate=jdbcTemplate;
|
this.jdbcTemplate=jdbcTemplate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 同样,可以在setter上应用@Component
|
- 同样,可以在setter上应用@Component
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
class Shiromiya {
|
class Shiromiya {
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public setJdbcTemplate(JdbcTemplate jdbcTemplate) {
|
public setJdbcTemplate(JdbcTemplate jdbcTemplate) {
|
||||||
this.jdbcTemplate=jdbcTemplate;
|
this.jdbcTemplate=jdbcTemplate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 将@Autowired应用于字段
|
- 将@Autowired应用于字段
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
class Shiromiya {
|
class Shiromiya {
|
||||||
@Autowired
|
@Autowired
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 通过@Autowired获取相同类型的所有bean对象
|
- 通过@Autowired获取相同类型的所有bean对象
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
class Shiromiya {
|
class Shiromiya {
|
||||||
private String[] waifus;
|
private String[] waifus;
|
||||||
/*
|
/*
|
||||||
* 这里,同样也支持Collections类型
|
* 这里,同样也支持Collections类型
|
||||||
* 例如 List<String>
|
* 例如 List<String>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public Shiromiya(String[] waifus) {
|
public Shiromiya(String[] waifus) {
|
||||||
this.waifus=waifus;
|
this.waifus=waifus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同样,可以通过Map类型来获取所有相同类型bean对象的name和value
|
// 同样,可以通过Map类型来获取所有相同类型bean对象的name和value
|
||||||
// key:对应bean对象的name
|
// key:对应bean对象的name
|
||||||
// value:对应该bean对象的值
|
// value:对应该bean对象的值
|
||||||
@Component
|
@Component
|
||||||
class Shiromiya {
|
class Shiromiya {
|
||||||
private Map<String,String> waifus;
|
private Map<String,String> waifus;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setWaifus(Map<String,String> waifus) {
|
public void setWaifus(Map<String,String> waifus) {
|
||||||
this.waifus=waifus;
|
this.waifus=waifus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 在@Autowired标记在构造函数上时,即使required为true,在参数为多bean类型时,即使没有匹配的bean,该属性会赋值为{}(空集合)而不是抛出异常
|
- 在@Autowired标记在构造函数上时,即使required为true,在参数为多bean类型时,即使没有匹配的bean,该属性会赋值为{}(空集合)而不是抛出异常
|
||||||
- @Autowired作用与构造函数的规则
|
- @Autowired作用与构造函数的规则
|
||||||
- 当required属性为其默认值true时,在bean类型中只有一个构造函数可以用@Autowired标注
|
- 当required属性为其默认值true时,在bean类型中只有一个构造函数可以用@Autowired标注
|
||||||
- 如果bean类型中有多个构造函数标注了@Autowired注解,那么那么他们都必须将required属性设置为false,并且所有标注了@Autowired属性的构造函数都会被视为依赖注入的候选构造函数
|
- 如果bean类型中有多个构造函数标注了@Autowired注解,那么那么他们都必须将required属性设置为false,并且所有标注了@Autowired属性的构造函数都会被视为依赖注入的候选构造函数
|
||||||
- 如果有多个候选的构造函数,那么在IOC容器中可以满足的匹配bean最多的构造函数将会被选中
|
- 如果有多个候选的构造函数,那么在IOC容器中可以满足的匹配bean最多的构造函数将会被选中
|
||||||
- 如果没有候选函数被选中,那么其会采用默认构造函数,如无默认构造函数,则抛出异常
|
- 如果没有候选函数被选中,那么其会采用默认构造函数,如无默认构造函数,则抛出异常
|
||||||
- 如果bean有多个构造函数,并且所有构造函数都没有标明@Autowired注解,那么会采用默认构造函数,如果默认构造函数不存在,抛出异常
|
- 如果bean有多个构造函数,并且所有构造函数都没有标明@Autowired注解,那么会采用默认构造函数,如果默认构造函数不存在,抛出异常
|
||||||
- 如果bean类型只有一个构造函数,那么该构造函数会被用来进行依赖注入,即使该构造函数没有标注@Autowired注解
|
- 如果bean类型只有一个构造函数,那么该构造函数会被用来进行依赖注入,即使该构造函数没有标注@Autowired注解
|
||||||
- 除了使用@Autowired的required属性,还可以使用@Nullable注解来标注可为空
|
- 除了使用@Autowired的required属性,还可以使用@Nullable注解来标注可为空
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class Shiromiya {
|
public class Shiromiya {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Nullable
|
@Nullable
|
||||||
private int num;
|
private int num;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- @Primary
|
- @Primary
|
||||||
- @Autowired注解是通过类型注入,如果相同类型存在多个bean时,可以通过@Primary注解来表明一个primary bean
|
- @Autowired注解是通过类型注入,如果相同类型存在多个bean时,可以通过@Primary注解来表明一个primary bean
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
public class BeanConfiguration {
|
public class BeanConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public String name_1() {
|
public String name_1() {
|
||||||
return "kazusa";
|
return "kazusa";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public String name_2() {
|
public String name_2() {
|
||||||
return "ogiso";
|
return "ogiso";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* 此时,若通过@Autowired注入String类型,“kazusa”将会是默认值
|
* 此时,若通过@Autowired注入String类型,“kazusa”将会是默认值
|
||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
- @Qualifier
|
- @Qualifier
|
||||||
- 可以通过@Qualifier来指定bean的name导入特定bean,并且可以为bean指定默认的qualifier
|
- 可以通过@Qualifier来指定bean的name导入特定bean,并且可以为bean指定默认的qualifier
|
||||||
```java
|
```java
|
||||||
@Component
|
@Component
|
||||||
public class Shiromiya {
|
public class Shiromiya {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("name_2")
|
@Qualifier("name_2")
|
||||||
private String n_2;
|
private String n_2;
|
||||||
|
|
||||||
private String n_1;
|
private String n_1;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public Shiromiya(@Qualifier("name_1") String n) {
|
public Shiromiya(@Qualifier("name_1") String n) {
|
||||||
this.n_1=n;
|
this.n_1=n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- bean对象的qualifier并不需要唯一,可以为不同的bean对象赋值相同的qualifier,并且在注入bean集合的时候根据qualifier过滤
|
- bean对象的qualifier并不需要唯一,可以为不同的bean对象赋值相同的qualifier,并且在注入bean集合的时候根据qualifier过滤
|
||||||
```java
|
```java
|
||||||
@Configuration
|
@Configuration
|
||||||
@Qualifier("config")
|
@Qualifier("config")
|
||||||
class BeanConfiguration {
|
class BeanConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
@Qualifier("name")
|
@Qualifier("name")
|
||||||
public String name_1() {
|
public String name_1() {
|
||||||
return "kazusa";
|
return "kazusa";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Qualifier("name")
|
@Qualifier("name")
|
||||||
public String name_2() {
|
public String name_2() {
|
||||||
return "ogiso";
|
return "ogiso";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Qualifier("not-name")
|
@Qualifier("not-name")
|
||||||
public String not_name_1() {
|
public String not_name_1() {
|
||||||
return "fuck";
|
return "fuck";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class Person {
|
public class Person {
|
||||||
/* 此nameList属性会注入qualifier为name的所有bean
|
/* 此nameList属性会注入qualifier为name的所有bean
|
||||||
* 在此处为"kazusa"和"ogiso"
|
* 在此处为"kazusa"和"ogiso"
|
||||||
*/
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("name")
|
@Qualifier("name")
|
||||||
Map<String,String> nameList;
|
Map<String,String> nameList;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("config")
|
@Qualifier("config")
|
||||||
BeanConfiguration conf;
|
BeanConfiguration conf;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Person{" +
|
return "Person{" +
|
||||||
"nameList=" + nameList +
|
"nameList=" + nameList +
|
||||||
", conf=" + conf +
|
", conf=" + conf +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 作为一种回退机制,当bean的qualifier未被定义时,bean的name属性将会被作为其qualifier,autowired时会根据@Qualifier注解中指定的值匹配具有相同name的bean对象
|
- 作为一种回退机制,当bean的qualifier未被定义时,bean的name属性将会被作为其qualifier,autowired时会根据@Qualifier注解中指定的值匹配具有相同name的bean对象
|
||||||
- 若想根据bean的name进行匹配,无需@Qualifier注解,只需要将注入点的name(filed的变量名,标注为@Autowired函数的形参名)和bean的name进行比较,如果相同则匹配成功,否则匹配失败
|
- 若想根据bean的name进行匹配,无需@Qualifier注解,只需要将注入点的name(filed的变量名,标注为@Autowired函数的形参名)和bean的name进行比较,如果相同则匹配成功,否则匹配失败
|
||||||
- @Autowired同样支持自身引用的注入,但是自身引用的注入只能作为一种fallback机制。如果当前IOC容器中存在其他的同类型对象,那么其他对象会被优先注入,对象自己并不会参与候选的对象注入。但是,如果IOC中并不存在其他同类型对象,那么自身对象将会被作为引用注入。
|
- @Autowired同样支持自身引用的注入,但是自身引用的注入只能作为一种fallback机制。如果当前IOC容器中存在其他的同类型对象,那么其他对象会被优先注入,对象自己并不会参与候选的对象注入。但是,如果IOC中并不存在其他同类型对象,那么自身对象将会被作为引用注入。
|
||||||
- @Resource
|
- @Resource
|
||||||
- @Resource标签类似于@Autowired标签,但是@Resource具有一个name属性用来匹配bean对象的name属性
|
- @Resource标签类似于@Autowired标签,但是@Resource具有一个name属性用来匹配bean对象的name属性
|
||||||
- @Resource标签首先会对具有相同name的bean对象,如果没有匹配到具有相同name的bean对象,才会fallback到类型匹配
|
- @Resource标签首先会对具有相同name的bean对象,如果没有匹配到具有相同name的bean对象,才会fallback到类型匹配
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
# Spring Data Access
|
# Spring Data Access
|
||||||
- ## Spring事务
|
- ## Spring事务
|
||||||
- 本地事务和全局事务:
|
- 本地事务和全局事务:
|
||||||
- 全局事务:全局事务允许使用多个事务资源,应用服务器来对全局事务进行管理
|
- 全局事务:全局事务允许使用多个事务资源,应用服务器来对全局事务进行管理
|
||||||
- 本地事务:本地事务无法管理多个事务资源
|
- 本地事务:本地事务无法管理多个事务资源
|
||||||
- 本地事务和全局事务的优缺点
|
- 本地事务和全局事务的优缺点
|
||||||
- 全局事务的使用需要和服务器环境相绑定,降低了代码的重用性
|
- 全局事务的使用需要和服务器环境相绑定,降低了代码的重用性
|
||||||
- 本地事务无法使用多个事务资源,无法通过JTA等框架来对多个事务资源进行管理,无法使用分布式事务
|
- 本地事务无法使用多个事务资源,无法通过JTA等框架来对多个事务资源进行管理,无法使用分布式事务
|
||||||
- ## 声明式事务
|
- ## 声明式事务
|
||||||
- Spring中声明式事务是通过AOP来实现的
|
- Spring中声明式事务是通过AOP来实现的
|
||||||
- 在声明式事务中,可以为方法级的粒度指定事务行为
|
- 在声明式事务中,可以为方法级的粒度指定事务行为
|
||||||
- 声明式事务的回滚规则:
|
- 声明式事务的回滚规则:
|
||||||
- 在Spring声明式事务中,可以为事务指定回滚规则,即指定针对哪些异常,事务会自动执行回滚操作
|
- 在Spring声明式事务中,可以为事务指定回滚规则,即指定针对哪些异常,事务会自动执行回滚操作
|
||||||
- 在默认情况下,只有抛出unchecked异常(通常为RuntimeException)时,声明式事务才会进行回滚
|
- 在默认情况下,只有抛出unchecked异常(通常为RuntimeException)时,声明式事务才会进行回滚
|
||||||
- 声明式事务的实现细节:
|
- 声明式事务的实现细节:
|
||||||
- 声明式事务通过aop代理实现,并且事务的advice是通过xml元数据配置来驱动的
|
- 声明式事务通过aop代理实现,并且事务的advice是通过xml元数据配置来驱动的
|
||||||
- aop和事务元数据联合产生了一个aop代理对象,并且该代理对象通过使用TransactionInterceptor和TransactionManager来实现事务
|
- aop和事务元数据联合产生了一个aop代理对象,并且该代理对象通过使用TransactionInterceptor和TransactionManager来实现事务
|
||||||
- @Transactional通常和线程绑定的事务一起工作,线程绑定的事务由PlatformTransactionManager管理。@Transactional会将事务暴露给当前执行线程中所有的dao操作
|
- @Transactional通常和线程绑定的事务一起工作,线程绑定的事务由PlatformTransactionManager管理。@Transactional会将事务暴露给当前执行线程中所有的dao操作
|
||||||
- 声明式事务的回滚:
|
- 声明式事务的回滚:
|
||||||
- 在Spring事务中推荐让事务回滚的方式是在事务执行的方法中抛出一个异常
|
- 在Spring事务中推荐让事务回滚的方式是在事务执行的方法中抛出一个异常
|
||||||
- Spring事务在默认情况下只会针对unchecked异常(RuntimeException)进行回滚,对于Error,Spring事务也会执行回滚操作
|
- Spring事务在默认情况下只会针对unchecked异常(RuntimeException)进行回滚,对于Error,Spring事务也会执行回滚操作
|
||||||
- checked异常并不会导致事务的回滚操作,可以注册rollback rule来指定对特定的异常(包括checked异常)进行回滚操作
|
- checked异常并不会导致事务的回滚操作,可以注册rollback rule来指定对特定的异常(包括checked异常)进行回滚操作
|
||||||
- rollback rule:
|
- rollback rule:
|
||||||
- 回滚规则(rollback rule)通常用来指定当一个异常被抛出时,是否为该异常执行事务的回滚操作
|
- 回滚规则(rollback rule)通常用来指定当一个异常被抛出时,是否为该异常执行事务的回滚操作
|
||||||
- 在@Transactional注解中,可以指定rollbackFor/noRollbackFor、rollbackForClassName/noRollbackForClassName来指定为那些异常类执行回滚操作
|
- 在@Transactional注解中,可以指定rollbackFor/noRollbackFor、rollbackForClassName/noRollbackForClassName来指定为那些异常类执行回滚操作
|
||||||
> 当指定rollbackFor属性为checked异常时(如rollbackFor=FileNotFoundException.class),此时指定的异常不会覆盖其默认行为(为RuntimeException和Error异常执行回滚操作)。
|
> 当指定rollbackFor属性为checked异常时(如rollbackFor=FileNotFoundException.class),此时指定的异常不会覆盖其默认行为(为RuntimeException和Error异常执行回滚操作)。
|
||||||
> 故而指定后其默认会为Error、RuntimeException、FileNotFoundException三类异常执行回滚操作
|
> 故而指定后其默认会为Error、RuntimeException、FileNotFoundException三类异常执行回滚操作
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Transactional(rollbackFor={MyException.class})
|
@Transactional(rollbackFor={MyException.class})
|
||||||
public void myOperation() {
|
public void myOperation() {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## 基于注解的声明式事务
|
- ## 基于注解的声明式事务
|
||||||
- @Transactional既可以作用于类上,也可以作用于方法上。当作用于类上时,该声明类中所有的方法都会被声明是事务的,该类子类中所有的方法也都是事务的
|
- @Transactional既可以作用于类上,也可以作用于方法上。当作用于类上时,该声明类中所有的方法都会被声明是事务的,该类子类中所有的方法也都是事务的
|
||||||
- @Transactional是可以继承的,被@Inherited元注解修饰。
|
- @Transactional是可以继承的,被@Inherited元注解修饰。
|
||||||
>@Inherited类是元注解,用来修饰注解类。如果一个注解类被@Inherited注解标识,那么在对class查询该注解类时,如果当前class没有声明该注解,将会在当前class的父类中查找该注解,依次递归。直到在父类中找到该注解或是到达继承结构的顶部(Object类)。@Inherited标注的注解仅能在类继承中有效,如注解被标注在接口上,那么将@Inherited标注的注解将不会被递归查询到。
|
>@Inherited类是元注解,用来修饰注解类。如果一个注解类被@Inherited注解标识,那么在对class查询该注解类时,如果当前class没有声明该注解,将会在当前class的父类中查找该注解,依次递归。直到在父类中找到该注解或是到达继承结构的顶部(Object类)。@Inherited标注的注解仅能在类继承中有效,如注解被标注在接口上,那么将@Inherited标注的注解将不会被递归查询到。
|
||||||
- 并且,class级别的@Transactional并不应用在从父类继承的方法上,即若一个类被@Transactional注解标注,并且该类从父类继承方法,那么该类从父类继承的方法并不会被看作是事务的,除非在该类中重新声明继承的方法。
|
- 并且,class级别的@Transactional并不应用在从父类继承的方法上,即若一个类被@Transactional注解标注,并且该类从父类继承方法,那么该类从父类继承的方法并不会被看作是事务的,除非在该类中重新声明继承的方法。
|
||||||
- 通过在Configuration类上注解@EnableTransactionManagement,配合@Transactional可以将一个bean对象声明是事务的
|
- 通过在Configuration类上注解@EnableTransactionManagement,配合@Transactional可以将一个bean对象声明是事务的
|
||||||
- 当基于标准spring配置时,应该仅将@Transactional注解标注于public方法,当将@Transactional注解标注于非public方法时无效
|
- 当基于标准spring配置时,应该仅将@Transactional注解标注于public方法,当将@Transactional注解标注于非public方法时无效
|
||||||
- 在Spring中,仅推荐将@Transactional注解应用于类上,不推荐将其应用在接口(接口方法)上。如果将其应用在接口上,那么该事务配置仅仅对基于接口的动态代理有效,对基于class的代理无效。
|
- 在Spring中,仅推荐将@Transactional注解应用于类上,不推荐将其应用在接口(接口方法)上。如果将其应用在接口上,那么该事务配置仅仅对基于接口的动态代理有效,对基于class的代理无效。
|
||||||
- 当类级别和方法级别都设置了@Transactional注解时,方法级别的设置会优先被使用
|
- 当类级别和方法级别都设置了@Transactional注解时,方法级别的设置会优先被使用
|
||||||
- ## @Transactional注解的配置
|
- ## @Transactional注解的配置
|
||||||
- 事务的传播: 默认情况下,@Transactional的propagation属性是PROPAGATION_REQUIRED
|
- 事务的传播: 默认情况下,@Transactional的propagation属性是PROPAGATION_REQUIRED
|
||||||
- 事务的隔离级别: 默认情况下,@Transactional的isolation属性是ISOLATION_DEFAULT,使用数据库默认的隔离级别
|
- 事务的隔离级别: 默认情况下,@Transactional的isolation属性是ISOLATION_DEFAULT,使用数据库默认的隔离级别
|
||||||
- readOnly: 默认情况下,@Transactional的readOnly属性是false,默认事务是可读写的
|
- readOnly: 默认情况下,@Transactional的readOnly属性是false,默认事务是可读写的
|
||||||
- timeout: 默认况下下,@Transactional的超时属性取决于底层的事务系统,如果底层事务系统不支持timeout,则timeout属性为none
|
- timeout: 默认况下下,@Transactional的超时属性取决于底层的事务系统,如果底层事务系统不支持timeout,则timeout属性为none
|
||||||
- rollbackFor: 默认情况下,@Transactional会针对unchecked异常和Error进行回滚操作
|
- rollbackFor: 默认情况下,@Transactional会针对unchecked异常和Error进行回滚操作
|
||||||
- transactionManager: 默认情况下,@Transactional注解会使用项目中默认的事务管理器(即bean name为transactionManager的事务管理器)。可以为@Transactional注解指定value属性或是transactionManager属性来指定想要采用的事务管理器的bean name或是qualifier
|
- transactionManager: 默认情况下,@Transactional注解会使用项目中默认的事务管理器(即bean name为transactionManager的事务管理器)。可以为@Transactional注解指定value属性或是transactionManager属性来指定想要采用的事务管理器的bean name或是qualifier
|
||||||
- ## Transaction Propagation
|
- ## Transaction Propagation
|
||||||
- ### PROPAGATION.REQUIRED
|
- ### PROPAGATION.REQUIRED
|
||||||
- 在Spring中,事务的传播行为默认是PROPAGATION_REQUIRED,默认情况下该选项会强制的要求一个物理事务
|
- 在Spring中,事务的传播行为默认是PROPAGATION_REQUIRED,默认情况下该选项会强制的要求一个物理事务
|
||||||
- 如果当前作用域中不存在事务,那么会创建一个新的事务
|
- 如果当前作用域中不存在事务,那么会创建一个新的事务
|
||||||
- 如果当前作用域的外层作用域已经存在事务,那么会加入到当前作用域的事务中去
|
- 如果当前作用域的外层作用域已经存在事务,那么会加入到当前作用域的事务中去
|
||||||
- 在Spring中,默认情况下,当嵌套事务加入到外层的事务中时,会忽略内层事务定义的隔离级别、timeout设置和读写标志等。
|
- 在Spring中,默认情况下,当嵌套事务加入到外层的事务中时,会忽略内层事务定义的隔离级别、timeout设置和读写标志等。
|
||||||
> 如果想要对外层事务进行验证,可以手动将事务管理器的validateExistingTransaction属性设置为true。这样,当加入到一个隔离级别与内层事务完全不同的外层事务中时,该加入操作会被拒绝。在该设置下,如果read-write内层事务想要加入到外层的read-only事务中时,该加入操作也会被拒绝。
|
> 如果想要对外层事务进行验证,可以手动将事务管理器的validateExistingTransaction属性设置为true。这样,当加入到一个隔离级别与内层事务完全不同的外层事务中时,该加入操作会被拒绝。在该设置下,如果read-write内层事务想要加入到外层的read-only事务中时,该加入操作也会被拒绝。
|
||||||
- 在事务传播行为被设置为PROPAGATION_REQUIRED的情况下,会为每个被设置事务的方法创建一个逻辑的事务作用域。各个逻辑事务作用域之间都是相互独立的,在不同逻辑事务作用域之间都可以独立设置事务的rollback-only属性。但是,在PROPAGATION_REQUIRED的情况下,内层事务和外层事务都映射到同一个物理事务,内层事务加入到外层事务中,故而在内层逻辑事务中为物理事务设置rollback-only会切实影响到外层事务的提交。
|
- 在事务传播行为被设置为PROPAGATION_REQUIRED的情况下,会为每个被设置事务的方法创建一个逻辑的事务作用域。各个逻辑事务作用域之间都是相互独立的,在不同逻辑事务作用域之间都可以独立设置事务的rollback-only属性。但是,在PROPAGATION_REQUIRED的情况下,内层事务和外层事务都映射到同一个物理事务,内层事务加入到外层事务中,故而在内层逻辑事务中为物理事务设置rollback-only会切实影响到外层事务的提交。
|
||||||
- 当事务传播行为被设置为PROPAGATION_REQUIRED时,如果内层事务设置了rollback-only标记,那么会导致外层物理事务的回滚。当外层事务尝试提交并失败回滚后,会抛出一个UnexceptedRollbackException异常,外层事务commit方法的调用者会接受到该UnexceptedRollbackException,代表内层发生了非预期的回滚操作
|
- 当事务传播行为被设置为PROPAGATION_REQUIRED时,如果内层事务设置了rollback-only标记,那么会导致外层物理事务的回滚。当外层事务尝试提交并失败回滚后,会抛出一个UnexceptedRollbackException异常,外层事务commit方法的调用者会接受到该UnexceptedRollbackException,代表内层发生了非预期的回滚操作
|
||||||
- ### PROPAGATION.REQUIRES_NEW
|
- ### PROPAGATION.REQUIRES_NEW
|
||||||
- 相对于PROPAGATION_REQUIRED,PROPAGATION.REQUIRES_NEW传播行为会一直使用独立的物理事务,而不会尝试区加入外部已经存在的物理事务。
|
- 相对于PROPAGATION_REQUIRED,PROPAGATION.REQUIRES_NEW传播行为会一直使用独立的物理事务,而不会尝试区加入外部已经存在的物理事务。
|
||||||
- 对于PROPAGATION_NEW,其内层事务和外层事务都可以独立的提交或回滚,内层事务的回滚并不会导致外层事务的回滚。
|
- 对于PROPAGATION_NEW,其内层事务和外层事务都可以独立的提交或回滚,内层事务的回滚并不会导致外层事务的回滚。
|
||||||
- 将事务传播行为设置为PROPAGATION.REQUIRES_NEW时,内层事务可以独立定义自己的隔离级别、timeout值、read-only属性,而不必继承外部事务的这些属性。在PROPAGATION_REQUIRED中,内部事务自定义这些属性将会被忽略,内部事务加入外部事务后会采用外部事务的设置。
|
- 将事务传播行为设置为PROPAGATION.REQUIRES_NEW时,内层事务可以独立定义自己的隔离级别、timeout值、read-only属性,而不必继承外部事务的这些属性。在PROPAGATION_REQUIRED中,内部事务自定义这些属性将会被忽略,内部事务加入外部事务后会采用外部事务的设置。
|
||||||
- ### PROPAGATION.NESTED
|
- ### PROPAGATION.NESTED
|
||||||
- 和PROPAGATION_REQUIRED类似,PROPAGATION_NESTED同样也只有一个物理事务。但是其支持多个savepoint(存档点),该物理事务可以回滚到特定的存档点而非必须回滚整个事务。
|
- 和PROPAGATION_REQUIRED类似,PROPAGATION_NESTED同样也只有一个物理事务。但是其支持多个savepoint(存档点),该物理事务可以回滚到特定的存档点而非必须回滚整个事务。
|
||||||
- 由于PROPAGATION_NESTED对存档点的支持,故而在PROPAGATION_NESTED条件下,可以进行部分回滚。内层事务的回滚操作并不会造成外部事务的回滚,内层事务回滚后外层事务仍然能够继续执行和提交。
|
- 由于PROPAGATION_NESTED对存档点的支持,故而在PROPAGATION_NESTED条件下,可以进行部分回滚。内层事务的回滚操作并不会造成外部事务的回滚,内层事务回滚后外层事务仍然能够继续执行和提交。
|
||||||
> 由于PROPAGATION_NESTED需要JDBC savepoint(存档点)的支持,故而该设置仅仅对JDBC事务资源有效。
|
> 由于PROPAGATION_NESTED需要JDBC savepoint(存档点)的支持,故而该设置仅仅对JDBC事务资源有效。
|
||||||
|
|
||||||
> 当事务被回滚之后,当前事务无法再被提交,故而:
|
> 当事务被回滚之后,当前事务无法再被提交,故而:
|
||||||
> 若在子事务中已经回滚(子事务传播行为为required),那么父事务的状态已经被回滚,即使父事务捕获子事务抛出的异常,那么在捕获异常之后执行的sql操作也不会被提交到数据库中,父事务状态处于已回滚,无法再次提交
|
> 若在子事务中已经回滚(子事务传播行为为required),那么父事务的状态已经被回滚,即使父事务捕获子事务抛出的异常,那么在捕获异常之后执行的sql操作也不会被提交到数据库中,父事务状态处于已回滚,无法再次提交
|
||||||
> ***但是,当子事务传播行为为nested时,子事务虽然和父事务共用一个事务,子事务回滚时只会回滚到子事务开启之前的存档点,父事务在捕获子事务抛出异常之后执行的sql语句仍然可以被提交***
|
> ***但是,当子事务传播行为为nested时,子事务虽然和父事务共用一个事务,子事务回滚时只会回滚到子事务开启之前的存档点,父事务在捕获子事务抛出异常之后执行的sql语句仍然可以被提交***
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,177 +1,177 @@
|
|||||||
- [gson](#gson)
|
- [gson](#gson)
|
||||||
- [gson简介](#gson简介)
|
- [gson简介](#gson简介)
|
||||||
- [gson使用](#gson使用)
|
- [gson使用](#gson使用)
|
||||||
- [Gson库通过Maven引入](#gson库通过maven引入)
|
- [Gson库通过Maven引入](#gson库通过maven引入)
|
||||||
- [基本类型的序列化和反序列化](#基本类型的序列化和反序列化)
|
- [基本类型的序列化和反序列化](#基本类型的序列化和反序列化)
|
||||||
- [对象的序列化和反序列化](#对象的序列化和反序列化)
|
- [对象的序列化和反序列化](#对象的序列化和反序列化)
|
||||||
- [gson和对象联用的使用规范](#gson和对象联用的使用规范)
|
- [gson和对象联用的使用规范](#gson和对象联用的使用规范)
|
||||||
- [gson和嵌套类的关联使用](#gson和嵌套类的关联使用)
|
- [gson和嵌套类的关联使用](#gson和嵌套类的关联使用)
|
||||||
- [gson和数组的关联使用](#gson和数组的关联使用)
|
- [gson和数组的关联使用](#gson和数组的关联使用)
|
||||||
- [gson对java中的集合进行序列化和反序列化](#gson对java中的集合进行序列化和反序列化)
|
- [gson对java中的集合进行序列化和反序列化](#gson对java中的集合进行序列化和反序列化)
|
||||||
- [gson对Map类型的序列化和反序列化](#gson对map类型的序列化和反序列化)
|
- [gson对Map类型的序列化和反序列化](#gson对map类型的序列化和反序列化)
|
||||||
- [序列化和反序列化泛型对象](#序列化和反序列化泛型对象)
|
- [序列化和反序列化泛型对象](#序列化和反序列化泛型对象)
|
||||||
- [序列化和反序列化集合,集合中保存任意类型的对象](#序列化和反序列化集合集合中保存任意类型的对象)
|
- [序列化和反序列化集合,集合中保存任意类型的对象](#序列化和反序列化集合集合中保存任意类型的对象)
|
||||||
|
|
||||||
# gson
|
# gson
|
||||||
## gson简介
|
## gson简介
|
||||||
gson是一个java库,通常用来将java对象转化为其json表示的字符串,或者将json格式的字符串转化为其等价的java对象。
|
gson是一个java库,通常用来将java对象转化为其json表示的字符串,或者将json格式的字符串转化为其等价的java对象。
|
||||||
## gson使用
|
## gson使用
|
||||||
在gson中,使用最频繁的类是Gson。可以通过new Gson()构造函数来创建Gson对象,也可以通过GsonBuilder来创建Gson对象,GsonBuidler在创建Gson对象时能够指定一些设置,如版本控制等。
|
在gson中,使用最频繁的类是Gson。可以通过new Gson()构造函数来创建Gson对象,也可以通过GsonBuilder来创建Gson对象,GsonBuidler在创建Gson对象时能够指定一些设置,如版本控制等。
|
||||||
由于Gson对象在执行json操作时并不会保存任何状态,故而Gson对象是线程安全的,单一的Gson对象可以在多线程环境下被重复使用。
|
由于Gson对象在执行json操作时并不会保存任何状态,故而Gson对象是线程安全的,单一的Gson对象可以在多线程环境下被重复使用。
|
||||||
### Gson库通过Maven引入
|
### Gson库通过Maven引入
|
||||||
```xml
|
```xml
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Gson: Java to JSON conversion -->
|
<!-- Gson: Java to JSON conversion -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
<version>2.9.1</version>
|
<version>2.9.1</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
```
|
```
|
||||||
### 基本类型的序列化和反序列化
|
### 基本类型的序列化和反序列化
|
||||||
```java
|
```java
|
||||||
// Serialization
|
// Serialization
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
gson.toJson(1); // ==> 1
|
gson.toJson(1); // ==> 1
|
||||||
gson.toJson("abcd"); // ==> "abcd"
|
gson.toJson("abcd"); // ==> "abcd"
|
||||||
gson.toJson(new Long(10)); // ==> 10
|
gson.toJson(new Long(10)); // ==> 10
|
||||||
int[] values = { 1 };
|
int[] values = { 1 };
|
||||||
gson.toJson(values); // ==> [1]
|
gson.toJson(values); // ==> [1]
|
||||||
|
|
||||||
// Deserialization
|
// Deserialization
|
||||||
int one = gson.fromJson("1", int.class);
|
int one = gson.fromJson("1", int.class);
|
||||||
Integer one = gson.fromJson("1", Integer.class);
|
Integer one = gson.fromJson("1", Integer.class);
|
||||||
Long one = gson.fromJson("1", Long.class);
|
Long one = gson.fromJson("1", Long.class);
|
||||||
Boolean false = gson.fromJson("false", Boolean.class);
|
Boolean false = gson.fromJson("false", Boolean.class);
|
||||||
String str = gson.fromJson("\"abc\"", String.class);
|
String str = gson.fromJson("\"abc\"", String.class);
|
||||||
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);
|
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);
|
||||||
```
|
```
|
||||||
### 对象的序列化和反序列化
|
### 对象的序列化和反序列化
|
||||||
类似于java自带的序列化机制,当成员字段被transient修饰时,并不会序列化该字段
|
类似于java自带的序列化机制,当成员字段被transient修饰时,并不会序列化该字段
|
||||||
```java
|
```java
|
||||||
class BagOfPrimitives {
|
class BagOfPrimitives {
|
||||||
private int value1 = 1;
|
private int value1 = 1;
|
||||||
private String value2 = "abc";
|
private String value2 = "abc";
|
||||||
private transient int value3 = 3;
|
private transient int value3 = 3;
|
||||||
BagOfPrimitives() {
|
BagOfPrimitives() {
|
||||||
// no-args constructor
|
// no-args constructor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
BagOfPrimitives obj = new BagOfPrimitives();
|
BagOfPrimitives obj = new BagOfPrimitives();
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
String json = gson.toJson(obj);
|
String json = gson.toJson(obj);
|
||||||
|
|
||||||
// ==> json is {"value1":1,"value2":"abc"}
|
// ==> json is {"value1":1,"value2":"abc"}
|
||||||
|
|
||||||
// Deserialization
|
// Deserialization
|
||||||
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
|
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
|
||||||
// ==> obj2 is just like obj
|
// ==> obj2 is just like obj
|
||||||
```
|
```
|
||||||
### gson和对象联用的使用规范
|
### gson和对象联用的使用规范
|
||||||
- gson使用过程中,待序列化或反序列化的成员字段可以是private的,同时也推荐将待序列化或反序列化的成员字段声明为private
|
- gson使用过程中,待序列化或反序列化的成员字段可以是private的,同时也推荐将待序列化或反序列化的成员字段声明为private
|
||||||
- 没有必要对成员字段使用注解来特定标明在序列化或者反序列化中包含该字段,默认情况下该对象所有成员字段和该对象父类对象所包含的所有字段都会被序列化
|
- 没有必要对成员字段使用注解来特定标明在序列化或者反序列化中包含该字段,默认情况下该对象所有成员字段和该对象父类对象所包含的所有字段都会被序列化
|
||||||
- 类似于jdk自带的序列化和反序列化机制,如果一个字段被标明为transient,该字段将不会被包含到序列化和反序列化过程中
|
- 类似于jdk自带的序列化和反序列化机制,如果一个字段被标明为transient,该字段将不会被包含到序列化和反序列化过程中
|
||||||
- gson实现能够正确处理字段为空的情况
|
- gson实现能够正确处理字段为空的情况
|
||||||
- 当序列化过程中,为null的字段将会被省略
|
- 当序列化过程中,为null的字段将会被省略
|
||||||
- 当反序列化过程中,如果一个字段在json串中并没有被设置,反序列化得到的对象中该字段将会被设置为默认值:引用类型默认值为null、数字类型默认值为0,boolean类型其默认值为false
|
- 当反序列化过程中,如果一个字段在json串中并没有被设置,反序列化得到的对象中该字段将会被设置为默认值:引用类型默认值为null、数字类型默认值为0,boolean类型其默认值为false
|
||||||
- 在内部类、匿名类、本地类中关联外部类的字段将会被忽略,并不会包含在序列化和反序列化过程中
|
- 在内部类、匿名类、本地类中关联外部类的字段将会被忽略,并不会包含在序列化和反序列化过程中
|
||||||
### gson和嵌套类的关联使用
|
### gson和嵌套类的关联使用
|
||||||
gson可以单独的对静态内部类进行序列化和反序列化,因为静态内部类并不包含对外部类的引用;但是gson无法单独的反序列化内部类,因为在反序列化内部类的过程中,其无参构造器需要一个指向其外部类对象的引用,但是该外部类对象在对内部类进行反序列化时是不可访问的。
|
gson可以单独的对静态内部类进行序列化和反序列化,因为静态内部类并不包含对外部类的引用;但是gson无法单独的反序列化内部类,因为在反序列化内部类的过程中,其无参构造器需要一个指向其外部类对象的引用,但是该外部类对象在对内部类进行反序列化时是不可访问的。
|
||||||
可以通过将内部类改为静态的内部类,此时对内部类的反序列化将不会需要指向外部类对象的引用。
|
可以通过将内部类改为静态的内部类,此时对内部类的反序列化将不会需要指向外部类对象的引用。
|
||||||
### gson和数组的关联使用
|
### gson和数组的关联使用
|
||||||
gson支持多维数组,并且支持任意的复杂元素类型
|
gson支持多维数组,并且支持任意的复杂元素类型
|
||||||
```java
|
```java
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
int[] ints = {1, 2, 3, 4, 5};
|
int[] ints = {1, 2, 3, 4, 5};
|
||||||
String[] strings = {"abc", "def", "ghi"};
|
String[] strings = {"abc", "def", "ghi"};
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
gson.toJson(ints); // ==> [1,2,3,4,5]
|
gson.toJson(ints); // ==> [1,2,3,4,5]
|
||||||
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
|
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
|
||||||
|
|
||||||
// Deserialization
|
// Deserialization
|
||||||
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
|
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
|
||||||
// ==> ints2 will be same as ints
|
// ==> ints2 will be same as ints
|
||||||
```
|
```
|
||||||
### gson对java中的集合进行序列化和反序列化
|
### gson对java中的集合进行序列化和反序列化
|
||||||
gson可以序列化任意对象的集合,但是无法对其进行反序列化,因为在反序列化时用户没有任何方法去指定其生成的集合中元素的类型。因而,需要通过typeToken来告知Gson需要反序列化的类型。
|
gson可以序列化任意对象的集合,但是无法对其进行反序列化,因为在反序列化时用户没有任何方法去指定其生成的集合中元素的类型。因而,需要通过typeToken来告知Gson需要反序列化的类型。
|
||||||
```java
|
```java
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
|
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
|
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
|
||||||
|
|
||||||
// Deserialization
|
// Deserialization
|
||||||
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
|
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
|
||||||
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
|
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
|
||||||
// ==> ints2 is same as ints
|
// ==> ints2 is same as ints
|
||||||
```
|
```
|
||||||
### gson对Map类型的序列化和反序列化
|
### gson对Map类型的序列化和反序列化
|
||||||
默认情况下,gson会将java中任意的Map实现类型序列化为JSON对象。由于JSON对象其key只支持字符串类型,gson会将待序列化的Map key调用toString转化为字符串。如果map中的key为null,则序列化后的key为"null"
|
默认情况下,gson会将java中任意的Map实现类型序列化为JSON对象。由于JSON对象其key只支持字符串类型,gson会将待序列化的Map key调用toString转化为字符串。如果map中的key为null,则序列化后的key为"null"
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
* gson对map进行序列化
|
* gson对map进行序列化
|
||||||
**/
|
**/
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
Map<String, String> stringMap = new LinkedHashMap<>();
|
Map<String, String> stringMap = new LinkedHashMap<>();
|
||||||
stringMap.put("key", "value");
|
stringMap.put("key", "value");
|
||||||
stringMap.put(null, "null-entry");
|
stringMap.put(null, "null-entry");
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
|
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
|
||||||
|
|
||||||
Map<Integer, Integer> intMap = new LinkedHashMap<>();
|
Map<Integer, Integer> intMap = new LinkedHashMap<>();
|
||||||
intMap.put(2, 4);
|
intMap.put(2, 4);
|
||||||
intMap.put(3, 6);
|
intMap.put(3, 6);
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
|
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
|
||||||
```
|
```
|
||||||
在反序列化的过程中,gson会使用为Map key类型注册的TypeAdapter的read方法来进行反序列化。为了让gson知道反序列化得到的Map对象的key和value类型,需要使用TypeToken。
|
在反序列化的过程中,gson会使用为Map key类型注册的TypeAdapter的read方法来进行反序列化。为了让gson知道反序列化得到的Map对象的key和value类型,需要使用TypeToken。
|
||||||
```java
|
```java
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
|
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
|
||||||
String json = "{\"key\": \"value\"}";
|
String json = "{\"key\": \"value\"}";
|
||||||
|
|
||||||
// Deserialization
|
// Deserialization
|
||||||
Map<String, String> stringMap = gson.fromJson(json, mapType);
|
Map<String, String> stringMap = gson.fromJson(json, mapType);
|
||||||
// ==> stringMap is {key=value}
|
// ==> stringMap is {key=value}
|
||||||
```
|
```
|
||||||
默认情况下,gson序列化map时,复杂类型的key会调用toString方法来将其转化成字符串。但是,gson同样支持开启复杂类型key的序列化操作。通过Gson.enableComplexMapKeySerialization()方法来开启,Gson会调用为Map的key类型注册的TypeAdapter的write方法来序列化key,而不是通过toString方法将key转化为字符串。
|
默认情况下,gson序列化map时,复杂类型的key会调用toString方法来将其转化成字符串。但是,gson同样支持开启复杂类型key的序列化操作。通过Gson.enableComplexMapKeySerialization()方法来开启,Gson会调用为Map的key类型注册的TypeAdapter的write方法来序列化key,而不是通过toString方法将key转化为字符串。
|
||||||
> 当Map中任意一条Entry的key通过Adapter被序列化为了JSON数组或者对象,那么Gson会将整个Map序列化为Json数组,数组元素为map中entry的键值对。如果map中所有的entry key都不会被序列化为json object或json array,那么该map将会被序列化为json对象
|
> 当Map中任意一条Entry的key通过Adapter被序列化为了JSON数组或者对象,那么Gson会将整个Map序列化为Json数组,数组元素为map中entry的键值对。如果map中所有的entry key都不会被序列化为json object或json array,那么该map将会被序列化为json对象
|
||||||
|
|
||||||
> 在对枚举型key进行反序列化的过程中,如果enum找不到一个具有匹配name()值的常量时,其会采用一个回退机制,根据枚举常量的toString()值来进行反序列化的匹配。
|
> 在对枚举型key进行反序列化的过程中,如果enum找不到一个具有匹配name()值的常量时,其会采用一个回退机制,根据枚举常量的toString()值来进行反序列化的匹配。
|
||||||
|
|
||||||
### 序列化和反序列化泛型对象
|
### 序列化和反序列化泛型对象
|
||||||
当gson对object对象调用toJson方法时,gson会调用object.getClass()来获取需要序列化的字段信息。类似的,在gson调用fromJson时,会向fromJson方法传递一个MyClass.class对象。该方法在序列化和反序列化类型是非泛型类型时能够正常运行。
|
当gson对object对象调用toJson方法时,gson会调用object.getClass()来获取需要序列化的字段信息。类似的,在gson调用fromJson时,会向fromJson方法传递一个MyClass.class对象。该方法在序列化和反序列化类型是非泛型类型时能够正常运行。
|
||||||
但是,当待序列化和反序列化的类型是泛型类型时,在序列化和反序列化对象时泛型类型信息会丢失,因为泛型采用的是类型擦除。
|
但是,当待序列化和反序列化的类型是泛型类型时,在序列化和反序列化对象时泛型类型信息会丢失,因为泛型采用的是类型擦除。
|
||||||
```java
|
```java
|
||||||
class Foo<T> {
|
class Foo<T> {
|
||||||
T value;
|
T value;
|
||||||
}
|
}
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
Foo<Bar> foo = new Foo<Bar>();
|
Foo<Bar> foo = new Foo<Bar>();
|
||||||
gson.toJson(foo); // May not serialize foo.value correctly
|
gson.toJson(foo); // May not serialize foo.value correctly
|
||||||
|
|
||||||
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
|
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
|
||||||
```
|
```
|
||||||
> 上述代码中,foo.getClass()方法返回的只是Foo.class对象,并不会包含泛型类型Bar的信息,故而在反序列化时gson并不知道应该将value反序列化为Bar类型
|
> 上述代码中,foo.getClass()方法返回的只是Foo.class对象,并不会包含泛型类型Bar的信息,故而在反序列化时gson并不知道应该将value反序列化为Bar类型
|
||||||
|
|
||||||
可以通过向fromJson中传入Type参数来详细指定想要将json串转化成的泛型类型信息
|
可以通过向fromJson中传入Type参数来详细指定想要将json串转化成的泛型类型信息
|
||||||
```java
|
```java
|
||||||
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
|
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
|
||||||
gson.toJson(foo, fooType);
|
gson.toJson(foo, fooType);
|
||||||
|
|
||||||
gson.fromJson(json, fooType);
|
gson.fromJson(json, fooType);
|
||||||
```
|
```
|
||||||
### 序列化和反序列化集合,集合中保存任意类型的对象
|
### 序列化和反序列化集合,集合中保存任意类型的对象
|
||||||
当JSON串中数组包含各种类型元素时,将其转化为包含任意类型的java集合,可以有如下方法:
|
当JSON串中数组包含各种类型元素时,将其转化为包含任意类型的java集合,可以有如下方法:
|
||||||
- 使用Gson Parser API(JsonParser,底层parser api)将json串中数组转化为JsonArray,并且为每个元素调用Gson.fromJson。该方法是推荐的方法
|
- 使用Gson Parser API(JsonParser,底层parser api)将json串中数组转化为JsonArray,并且为每个元素调用Gson.fromJson。该方法是推荐的方法
|
||||||
> gson.fromJson可以针对String、Reader、JsonElement来调用
|
> gson.fromJson可以针对String、Reader、JsonElement来调用
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,85 +1,85 @@
|
|||||||
- [Spring Logging](#spring-logging)
|
- [Spring Logging](#spring-logging)
|
||||||
- [Log Format](#log-format)
|
- [Log Format](#log-format)
|
||||||
- [控制台输出](#控制台输出)
|
- [控制台输出](#控制台输出)
|
||||||
- [文件输出](#文件输出)
|
- [文件输出](#文件输出)
|
||||||
- [File Rotation](#file-rotation)
|
- [File Rotation](#file-rotation)
|
||||||
- [Log Level](#log-level)
|
- [Log Level](#log-level)
|
||||||
- [Log Group](#log-group)
|
- [Log Group](#log-group)
|
||||||
|
|
||||||
# Spring Logging
|
# Spring Logging
|
||||||
## Log Format
|
## Log Format
|
||||||
默认Spring Boot输出日志的格式如下
|
默认Spring Boot输出日志的格式如下
|
||||||
```console
|
```console
|
||||||
2022-08-18 05:33:51.660 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : Starting MyApplication using Java 1.8.0_345 on myhost with PID 16378 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
|
2022-08-18 05:33:51.660 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : Starting MyApplication using Java 1.8.0_345 on myhost with PID 16378 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
|
||||||
2022-08-18 05:33:51.664 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : No active profile set, falling back to 1 default profile: "default"
|
2022-08-18 05:33:51.664 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : No active profile set, falling back to 1 default profile: "default"
|
||||||
2022-08-18 05:33:53.907 INFO 16378 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
|
2022-08-18 05:33:53.907 INFO 16378 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
|
||||||
2022-08-18 05:33:53.939 INFO 16378 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
2022-08-18 05:33:53.939 INFO 16378 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||||
2022-08-18 05:33:53.939 INFO 16378 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
|
2022-08-18 05:33:53.939 INFO 16378 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
|
||||||
2022-08-18 05:33:54.217 INFO 16378 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
2022-08-18 05:33:54.217 INFO 16378 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||||
2022-08-18 05:33:54.217 INFO 16378 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2343 ms
|
2022-08-18 05:33:54.217 INFO 16378 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2343 ms
|
||||||
2022-08-18 05:33:55.396 INFO 16378 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
|
2022-08-18 05:33:55.396 INFO 16378 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
|
||||||
2022-08-18 05:33:55.640 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : Started MyApplication in 5.456 seconds (JVM running for 6.299)
|
2022-08-18 05:33:55.640 INFO 16378 --- [ main] o.s.b.d.f.s.MyApplication : Started MyApplication in 5.456 seconds (JVM running for 6.299)
|
||||||
```
|
```
|
||||||
- 日期与时间 : 精度为ms
|
- 日期与时间 : 精度为ms
|
||||||
- log level : ERROR, WARN, INFO, DEBUG, TRACE
|
- log level : ERROR, WARN, INFO, DEBUG, TRACE
|
||||||
- 进程ID
|
- 进程ID
|
||||||
- 线程名称 : [main]
|
- 线程名称 : [main]
|
||||||
- logger name : 输出日志类的类名(通常为缩写)
|
- logger name : 输出日志类的类名(通常为缩写)
|
||||||
- log信息
|
- log信息
|
||||||
## 控制台输出
|
## 控制台输出
|
||||||
默认情况下,log日志信息会回显输出到console中,默认ERROR, WARN, DEBUG三个级别的信息将会被日志输出。
|
默认情况下,log日志信息会回显输出到console中,默认ERROR, WARN, DEBUG三个级别的信息将会被日志输出。
|
||||||
可以通过--debug选项来启用“debug”模式
|
可以通过--debug选项来启用“debug”模式
|
||||||
```shell
|
```shell
|
||||||
java -jar xxx.jar --debug
|
java -jar xxx.jar --debug
|
||||||
```
|
```
|
||||||
通过在application.properties中指定debug=true也可以开启“debug”模式
|
通过在application.properties中指定debug=true也可以开启“debug”模式
|
||||||
```properties
|
```properties
|
||||||
debug=true
|
debug=true
|
||||||
```
|
```
|
||||||
当“debug”模式被开启后,一部分核心的logger(内嵌容器、Hibernate、SpringBoot)将会被配置输出更多的信息。
|
当“debug”模式被开启后,一部分核心的logger(内嵌容器、Hibernate、SpringBoot)将会被配置输出更多的信息。
|
||||||
> ***开启debug模式并不意味着输出所有日志级别为Debug的信息***
|
> ***开启debug模式并不意味着输出所有日志级别为Debug的信息***
|
||||||
|
|
||||||
> ***同样,也可以通过--trace或者在properties中指定trace=true来开启trace模式***
|
> ***同样,也可以通过--trace或者在properties中指定trace=true来开启trace模式***
|
||||||
|
|
||||||
## 文件输出
|
## 文件输出
|
||||||
默认情况下,Spring Boot只会将日志输出到console中,如果想要额外定义将日志输出到文件中,需要在application.properties中定义logging.file.name或者logging.file.path
|
默认情况下,Spring Boot只会将日志输出到console中,如果想要额外定义将日志输出到文件中,需要在application.properties中定义logging.file.name或者logging.file.path
|
||||||
| logging.file.name | logging.file.path | example | description |
|
| logging.file.name | logging.file.path | example | description |
|
||||||
| :-: | :-: | :-: | :-: |
|
| :-: | :-: | :-: | :-: |
|
||||||
| (none) | (none) | | 只在控制台输出 |
|
| (none) | (none) | | 只在控制台输出 |
|
||||||
| 特定文件 | (none) | my.log | 特定log文件路径,可以是绝对路径或相对路径) |
|
| 特定文件 | (none) | my.log | 特定log文件路径,可以是绝对路径或相对路径) |
|
||||||
| (none) | 特定目录 | /var/log | 将日志输出到该路径下的spring.log文件,可以是绝对路径或相对路径|
|
| (none) | 特定目录 | /var/log | 将日志输出到该路径下的spring.log文件,可以是绝对路径或相对路径|
|
||||||
> 当log文件大小到达10MB时将会旋转重写,和console log一样,log文件也会输出ERROR, WARN和INFO
|
> 当log文件大小到达10MB时将会旋转重写,和console log一样,log文件也会输出ERROR, WARN和INFO
|
||||||
|
|
||||||
> ***logging properties和实际的logging机制是相互独立的,因而,特定的配置属性(如logback.configurationFile)并不由SpringBoot管理***
|
> ***logging properties和实际的logging机制是相互独立的,因而,特定的配置属性(如logback.configurationFile)并不由SpringBoot管理***
|
||||||
|
|
||||||
## File Rotation
|
## File Rotation
|
||||||
如果使用的是Logback,则可以在application.properties中定义file rotation行为。
|
如果使用的是Logback,则可以在application.properties中定义file rotation行为。
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| :-: | :-: |
|
| :-: | :-: |
|
||||||
| logging.logback.rollingpolicy.file-name-pattern | 定义创建log归档的命名模式 |
|
| logging.logback.rollingpolicy.file-name-pattern | 定义创建log归档的命名模式 |
|
||||||
| logging.logback.rollingpolicy.clean-history-on-start | 定义是否应该在项目启动时清理历史日志 |
|
| logging.logback.rollingpolicy.clean-history-on-start | 定义是否应该在项目启动时清理历史日志 |
|
||||||
| logging.logback.rollingpolicy.max-file-size | 定义日志在归档前的最大大小 |
|
| logging.logback.rollingpolicy.max-file-size | 定义日志在归档前的最大大小 |
|
||||||
| logging.logback.rollingpolicy.total-size-cap | 日志归档在被删除前可以占用的最大大小 |
|
| logging.logback.rollingpolicy.total-size-cap | 日志归档在被删除前可以占用的最大大小 |
|
||||||
| logging.logback.rollingpolicy.max-history | 要保留归档日志文件的最大数量 |
|
| logging.logback.rollingpolicy.max-history | 要保留归档日志文件的最大数量 |
|
||||||
|
|
||||||
## Log Level
|
## Log Level
|
||||||
所有的日志系统都可以通过Application.properties定义logging.level.<logger-name>=<level>来定义事务级别,事务级别可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。
|
所有的日志系统都可以通过Application.properties定义logging.level.<logger-name>=<level>来定义事务级别,事务级别可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。
|
||||||
可以通过logging.level.root来定义root logger的隔离级别。
|
可以通过logging.level.root来定义root logger的隔离级别。
|
||||||
```properties
|
```properties
|
||||||
logging.level.root=warn
|
logging.level.root=warn
|
||||||
logging.level.org.springframework.web=debug
|
logging.level.org.springframework.web=debug
|
||||||
logging.level.org.hibernate=error
|
logging.level.org.hibernate=error
|
||||||
```
|
```
|
||||||
## Log Group
|
## Log Group
|
||||||
可以通过log group将关联的logger组合在一起,并且对log group统一指定日志级别。
|
可以通过log group将关联的logger组合在一起,并且对log group统一指定日志级别。
|
||||||
```properties
|
```properties
|
||||||
# 定义一个名为“tomcat”的log group
|
# 定义一个名为“tomcat”的log group
|
||||||
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
|
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
|
||||||
|
|
||||||
# 为名为“tomcat”的log group统一指定log level
|
# 为名为“tomcat”的log group统一指定log level
|
||||||
logging.level.tomcat=trace
|
logging.level.tomcat=trace
|
||||||
```
|
```
|
||||||
> Spring Boot具有如下先定义好的log group,可以开箱即用
|
> Spring Boot具有如下先定义好的log group,可以开箱即用
|
||||||
> - web : org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
|
> - web : org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
|
||||||
> - sql : org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener
|
> - sql : org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# lombok标签简介
|
# lombok标签简介
|
||||||
## @NonNull
|
## @NonNull
|
||||||
- 如果用@NonNull标签来修饰一个参数,在方法或构造器的开头会插入一个空校验来检查该参数是否为空。
|
- 如果用@NonNull标签来修饰一个参数,在方法或构造器的开头会插入一个空校验来检查该参数是否为空。
|
||||||
- 如果将@NonNull标签用来修饰一个field,任何通过注解产生的方法(如@Setter产生的Setter)都会在试图分配该field一个值时进行空校验
|
- 如果将@NonNull标签用来修饰一个field,任何通过注解产生的方法(如@Setter产生的Setter)都会在试图分配该field一个值时进行空校验
|
||||||
## @RequiredArgsConstructor
|
## @RequiredArgsConstructor
|
||||||
被该注解修饰的类,会产生一个包含required args的构造器。required args包含final field和被特殊约束的field(如被@NonNull约束)
|
被该注解修饰的类,会产生一个包含required args的构造器。required args包含final field和被特殊约束的field(如被@NonNull约束)
|
||||||
## @ToString
|
## @ToString
|
||||||
产生一个toString方法的实现,并且该实现会被所有对象继承
|
产生一个toString方法的实现,并且该实现会被所有对象继承
|
||||||
## @Data
|
## @Data
|
||||||
@Data是一个快捷的注解,其将@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor等注解整合到了一起
|
@Data是一个快捷的注解,其将@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor等注解整合到了一起
|
||||||
- 对于@Data注解标注的类,如果类中包含一个方法,其方法名和@Data将要产生的方法相同并且参数个数也相同(不需要参数类型相同),那么该方法将不会被产生,并且不会产生任何警告或错误
|
- 对于@Data注解标注的类,如果类中包含一个方法,其方法名和@Data将要产生的方法相同并且参数个数也相同(不需要参数类型相同),那么该方法将不会被产生,并且不会产生任何警告或错误
|
||||||
- 对于@Data标注的类,如果该类显示声明了一个构造器,那么@Data不会再生成任何构造器
|
- 对于@Data标注的类,如果该类显示声明了一个构造器,那么@Data不会再生成任何构造器
|
||||||
- 可以通过为@Data标注类中的方法添加@lombok.experimental.Tolerate来为lombok隐藏这些方法
|
- 可以通过为@Data标注类中的方法添加@lombok.experimental.Tolerate来为lombok隐藏这些方法
|
||||||
@@ -1,182 +1,238 @@
|
|||||||
# Spring Boot Async
|
- [Spring Boot Async](#spring-boot-async)
|
||||||
## Spring Executor和Scheduler的自动配置
|
- [Spring Executor和Scheduler的自动配置](#spring-executor和scheduler的自动配置)
|
||||||
当当前上下文中没有Executor类型的bean对象时,spring boot会自动配置一个ThreadPoolTaskExecutor类型的bean对象,并且将该bean对象和异步task执行(@EnableAsync)和spring mvc异步请求处理关联在一起。
|
- [Task Execution and Scheduling](#task-execution-and-scheduling)
|
||||||
该默认创建的ThreadPoolTaskExecutor默认使用8个核心线程,并且线程数量可以根据负载动态的增加或者减少。
|
- [Task Execution Abstraction](#task-execution-abstraction)
|
||||||
可以通过如下方式对ThreadPoolTaskExecutor进行配置:
|
- [TaskExecutor接口的实现种类](#taskexecutor接口的实现种类)
|
||||||
```properties
|
- [TaskExecutor的使用](#taskexecutor的使用)
|
||||||
# 该线程池最多含有16个线程
|
- [Spring TaskScheduler Abstraction](#spring-taskscheduler-abstraction)
|
||||||
spring.task.execution.pool.max-size=16
|
- [Trigger接口](#trigger接口)
|
||||||
# 有有界队列存放task,上限为100
|
- [Trigger实现类](#trigger实现类)
|
||||||
spring.task.execution.pool.queue-capacity=100
|
- [TaskScheduler实现类](#taskscheduler实现类)
|
||||||
# 当线程空闲10s(默认60s)时会进行回收
|
- [对任务调度和异步执行的注解支持](#对任务调度和异步执行的注解支持)
|
||||||
spring.task.execution.pool.keep-alive=10s
|
- [启用Scheduling注解](#启用scheduling注解)
|
||||||
# 设置核心线程数量
|
- [@Scheduled注解](#scheduled注解)
|
||||||
spring.task.execution.pool.core-size=8
|
- [@Async注解](#async注解)
|
||||||
```
|
- [@Async方法的异常处理](#async方法的异常处理)
|
||||||
如果使用@EnableScheduling,一个ThreadPoolTaskScheduler也可以被配置。该线程池默认使用一个线程,但是也可以动态设置:
|
- [Cron表达式](#cron表达式)
|
||||||
```properties
|
- [Macros(宏)](#macros宏)
|
||||||
spring.task.scheduling.thread-name-prefix=scheduling-
|
|
||||||
spring.task.scheduling.pool.size=2
|
|
||||||
```
|
# Spring Boot Async
|
||||||
## Task Execution and Scheduling
|
## Spring Executor和Scheduler的自动配置
|
||||||
Spring通过TaskExecutor和TaskScheduler接口为task的异步执行和调度提供了抽象。
|
当当前上下文中没有Executor类型的bean对象时,spring boot会自动配置一个ThreadPoolTaskExecutor类型的bean对象,并且将该bean对象和异步task执行(@EnableAsync)和spring mvc异步请求处理关联在一起。
|
||||||
### Task Execution Abstraction
|
该默认创建的ThreadPoolTaskExecutor默认使用8个核心线程,并且线程数量可以根据负载动态的增加或者减少。
|
||||||
Executor对应的是JDK中线程池的概念。在Spring中,TaskExecutor接口和java.util.concurrent.Executor接口相同,该接口中只有一个execute方法(execute(Runnable task)),接受一个task。
|
可以通过如下方式对ThreadPoolTaskExecutor进行配置:
|
||||||
### TaskExecutor接口的实现种类
|
```properties
|
||||||
Spring中包含许多预制的TaskExecutor实现类,该实现类如下:
|
# 该线程池最多含有16个线程
|
||||||
- SyncTaskExecutor:该实现类不会执行异步的调用,所有任务的执行都会发生在调用该executor的线程中。(通常使用在不需要多线程的场景)
|
spring.task.execution.pool.max-size=16
|
||||||
- SimpleAsyncTaskExecutor:该实现类不会复用任何的线程,相反的,对于每次调用该executor,都会使用一个全新的线程。但是,该实现类的确支持对并发数量的限制,该executor会阻塞任何超过并发数量限制的调用,直到slot被释放为止。(SimpleAsyncTaskExecutor并不支持池化技术)
|
# 有有界队列存放task,上限为100
|
||||||
- ConcurrentTaskExecutor:该实现类是java.util.concurrent.Executor实例的adapter,其可以将java.util.concurrent.Executor实例的配置参数以bean properties的形式暴露。(当ThreadPoolTaskExecutor的灵活性无法满足需求时,可以使用ConcurrentTaskExecutor)
|
spring.task.execution.pool.queue-capacity=100
|
||||||
- ThreadPoolTaskExecutor:该实现类型是最广泛被使用的。该实现类可以通过bean properties来配置java.util.concurrent.ThreadPoolExecutor实例,并且将该实例wrap在TaskExecutor实例中。当想要使用另一种java.util.concurrent.Executor时,可以使用ConcurrentTaskExecutor。
|
# 当线程空闲10s(默认60s)时会进行回收
|
||||||
- DefaultManagedTaskExecutor:该实现使用了通过JNDI获取的ManagedExecutorService
|
spring.task.execution.pool.keep-alive=10s
|
||||||
|
# 设置核心线程数量
|
||||||
### TaskExecutor的使用
|
spring.task.execution.pool.core-size=8
|
||||||
在springboot中,当使用@EnableAsync时,可以通过配置ThreadPoolExecutor的bean properties来配置线程池的核心线程数和最大线程数等属性。
|
```
|
||||||
```properties
|
如果使用@EnableScheduling,一个ThreadPoolTaskScheduler也可以被配置。该线程池默认使用一个线程,但是也可以动态设置:
|
||||||
# 该线程池最多含有16个线程
|
```properties
|
||||||
spring.task.execution.pool.max-size=16
|
spring.task.scheduling.thread-name-prefix=scheduling-
|
||||||
# 有有界队列存放task,上限为100
|
spring.task.scheduling.pool.size=2
|
||||||
spring.task.execution.pool.queue-capacity=100
|
```
|
||||||
# 当线程空闲10s(默认60s)时会进行回收
|
## Task Execution and Scheduling
|
||||||
spring.task.execution.pool.keep-alive=10s
|
Spring通过TaskExecutor和TaskScheduler接口为task的异步执行和调度提供了抽象。
|
||||||
# 设置核心线程数量
|
### Task Execution Abstraction
|
||||||
spring.task.execution.pool.core-size=8
|
Executor对应的是JDK中线程池的概念。在Spring中,TaskExecutor接口和java.util.concurrent.Executor接口相同,该接口中只有一个execute方法(execute(Runnable task)),接受一个task。
|
||||||
```
|
### TaskExecutor接口的实现种类
|
||||||
|
Spring中包含许多预制的TaskExecutor实现类,该实现类如下:
|
||||||
## Spring TaskScheduler Abstraction
|
- SyncTaskExecutor:该实现类不会执行异步的调用,所有任务的执行都会发生在调用该executor的线程中。(通常使用在不需要多线程的场景)
|
||||||
为了在特定的时间点执行task,Spring引入了TaskScheduler接口,接口定义如下:
|
- SimpleAsyncTaskExecutor:该实现类不会复用任何的线程,相反的,对于每次调用该executor,都会使用一个全新的线程。但是,该实现类的确支持对并发数量的限制,该executor会阻塞任何超过并发数量限制的调用,直到slot被释放为止。(SimpleAsyncTaskExecutor并不支持池化技术)
|
||||||
```java
|
- ConcurrentTaskExecutor:该实现类是java.util.concurrent.Executor实例的adapter,其可以将java.util.concurrent.Executor实例的配置参数以bean properties的形式暴露。(当ThreadPoolTaskExecutor的灵活性无法满足需求时,可以使用ConcurrentTaskExecutor)
|
||||||
public interface TaskScheduler {
|
- ThreadPoolTaskExecutor:该实现类型是最广泛被使用的。该实现类可以通过bean properties来配置java.util.concurrent.ThreadPoolExecutor实例,并且将该实例wrap在TaskExecutor实例中。当想要使用另一种java.util.concurrent.Executor时,可以使用ConcurrentTaskExecutor。
|
||||||
|
- DefaultManagedTaskExecutor:该实现使用了通过JNDI获取的ManagedExecutorService
|
||||||
ScheduledFuture schedule(Runnable task, Trigger trigger);
|
|
||||||
|
### TaskExecutor的使用
|
||||||
ScheduledFuture schedule(Runnable task, Instant startTime);
|
在springboot中,当使用@EnableAsync时,可以通过配置ThreadPoolExecutor的bean properties来配置线程池的核心线程数和最大线程数等属性。
|
||||||
|
```properties
|
||||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
|
# 该线程池最多含有16个线程
|
||||||
|
spring.task.execution.pool.max-size=16
|
||||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
|
# 有有界队列存放task,上限为100
|
||||||
|
spring.task.execution.pool.queue-capacity=100
|
||||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
|
# 当线程空闲10s(默认60s)时会进行回收
|
||||||
|
spring.task.execution.pool.keep-alive=10s
|
||||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
|
# 设置核心线程数量
|
||||||
```
|
spring.task.execution.pool.core-size=8
|
||||||
> scheduleAtFixedRate和scheduleAtFixedDelay区别:
|
```
|
||||||
> - fixed rate:表示两个task执行开始时间的间隔
|
|
||||||
> - fixed delay:表示上一个task结束时间和下一个task开始时间的间隔
|
## Spring TaskScheduler Abstraction
|
||||||
>
|
为了在特定的时间点执行task,Spring引入了TaskScheduler接口,接口定义如下:
|
||||||
> 实例如下:
|
```java
|
||||||
> - fixed rate:TTWWWTTTWWT...(开始时间间隔为5)
|
public interface TaskScheduler {
|
||||||
> - fixed delay:TTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为5)
|
|
||||||
>
|
ScheduledFuture schedule(Runnable task, Trigger trigger);
|
||||||
> **通常,TaskScheduler默认情况下是单线程执行的,故而fixed rate执行时,如果一个Task执行时间超过period时,在当前task执行完成之前,下一个task并不会开始执行。下一个task会等待当前task执行完成之后立马执行。**
|
|
||||||
|
ScheduledFuture schedule(Runnable task, Instant startTime);
|
||||||
### Trigger接口
|
|
||||||
Trigger接口的核心理念是下次执行事件由上次执行的结果决定。上次执行的结果存储在TriggerContext中。
|
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
|
||||||
Trigger接口如下:
|
|
||||||
```java
|
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
|
||||||
public interface Trigger {
|
|
||||||
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
|
||||||
Date nextExecutionTime(TriggerContext triggerContext);
|
|
||||||
}
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
|
||||||
```
|
```
|
||||||
TriggerContext接口如下:(其具有默认的实现类SimpleTriggerContext)
|
> scheduleAtFixedRate和scheduleAtFixedDelay区别:
|
||||||
```java
|
> - fixed rate:表示两个task执行开始时间的间隔
|
||||||
public interface TriggerContext {
|
> - fixed delay:表示上一个task结束时间和下一个task开始时间的间隔
|
||||||
|
>
|
||||||
Date lastScheduledExecutionTime();
|
> 实例如下:
|
||||||
|
> - fixed rate:TTWWWTTTWWT...(开始时间间隔为5)
|
||||||
Date lastActualExecutionTime();
|
> - fixed delay:TTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为5)
|
||||||
|
>
|
||||||
Date lastCompletionTime();
|
> **通常,TaskScheduler默认情况下是单线程执行的,故而fixed rate执行时,如果一个Task执行时间超过period时,在当前task执行完成之前,下一个task并不会开始执行。下一个task会等待当前task执行完成之后立马执行。**
|
||||||
}
|
|
||||||
```
|
### Trigger接口
|
||||||
### Trigger实现类
|
Trigger接口的核心理念是下次执行事件由上次执行的结果决定。上次执行的结果存储在TriggerContext中。
|
||||||
Spring为Trigger提供了两个实现类,其中CronTrigger允许task的调度按照cron expression来执行(类似linux中的crond)。
|
Trigger接口如下:
|
||||||
```java
|
```java
|
||||||
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
|
public interface Trigger {
|
||||||
```
|
|
||||||
Spring的另一个Trigger实现是PeriodicTrigger,其接受一个固定的period期间,一个可选的初始delay值,并接收一个boolean值标识该period是fixed-rate还是fixed-delay。
|
Date nextExecutionTime(TriggerContext triggerContext);
|
||||||
### TaskScheduler实现类
|
}
|
||||||
如果不需要外部的线程管理,可以使用spring提供的ThreadPoolTaskScheduler,其会将任务委托给ScheduledExecutorService来提供bean-properties形式的配置。
|
```
|
||||||
|
TriggerContext接口如下:(其具有默认的实现类SimpleTriggerContext)
|
||||||
## 对任务调度和异步执行的注解支持
|
```java
|
||||||
Spring同时对任务调度和异步方法的执行提供注解支持。
|
public interface TriggerContext {
|
||||||
### 启用Scheduling注解
|
|
||||||
想要启用@Async和@Scheduled注解,必须将@EnableAsync注解和@EnableScheduling注解添加到一个@Configuration类上。
|
Date lastScheduledExecutionTime();
|
||||||
```java
|
|
||||||
@Configuration
|
Date lastActualExecutionTime();
|
||||||
@EnableAsync
|
|
||||||
@EnableScheduling
|
Date lastCompletionTime();
|
||||||
public class AppConfig {
|
}
|
||||||
}
|
```
|
||||||
```
|
### Trigger实现类
|
||||||
> **@Async注解的实现是通过proxy模式来实现的,故而如果在类内部调用位于同一个类中的@Async方法,那么代理拦截会失效,此时调用的@Async方法将会同步执行而非异步执行**
|
Spring为Trigger提供了两个实现类,其中CronTrigger允许task的调度按照cron expression来执行(类似linux中的crond)。
|
||||||
|
```java
|
||||||
> @Async可以接收一个value值,用于指定目标的执行器(Executor或TaskExecutor的beanName)
|
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
|
||||||
|
```
|
||||||
### @Scheduled注解
|
Spring的另一个Trigger实现是PeriodicTrigger,其接受一个固定的period期间,一个可选的初始delay值,并接收一个boolean值标识该period是fixed-rate还是fixed-delay。
|
||||||
在将@Scheduled注解标注到方法时,可以为其指定一个trigger的元数据,使用示例如下:
|
### TaskScheduler实现类
|
||||||
```java
|
如果不需要外部的线程管理,可以使用spring提供的ThreadPoolTaskScheduler,其会将任务委托给ScheduledExecutorService来提供bean-properties形式的配置。
|
||||||
@Scheduled(fixedDelay = 5000)
|
|
||||||
public void doSomething() {
|
## 对任务调度和异步执行的注解支持
|
||||||
// something that should run periodically
|
Spring同时对任务调度和异步方法的执行提供注解支持。
|
||||||
}
|
### 启用Scheduling注解
|
||||||
```
|
想要启用@Async和@Scheduled注解,必须将@EnableAsync注解和@EnableScheduling注解添加到一个@Configuration类上。
|
||||||
> 默认情况下,fixedDelay、fixedRate、initialDelay的时间单位都是ms,可以指定timeUnit来指定其他的时间单位:
|
```java
|
||||||
> ```java
|
@Configuration
|
||||||
> @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
|
@EnableAsync
|
||||||
> public void doSomething() {
|
@EnableScheduling
|
||||||
> // something that should run periodically
|
public class AppConfig {
|
||||||
> }
|
}
|
||||||
> ```
|
```
|
||||||
|
> **@Async注解的实现是通过proxy模式来实现的,故而如果在类内部调用位于同一个类中的@Async方法,那么代理拦截会失效,此时调用的@Async方法将会同步执行而非异步执行**
|
||||||
可以为@Scheduled注解使用cron表达式
|
|
||||||
```java
|
> @Async可以接收一个value值,用于指定目标的执行器(Executor或TaskExecutor的beanName)
|
||||||
@Scheduled(cron="*/5 * * * * MON-FRI")
|
|
||||||
public void doSomething() {
|
### @Scheduled注解
|
||||||
// something that should run on weekdays only
|
在将@Scheduled注解标注到方法时,可以为其指定一个trigger的元数据,使用示例如下:
|
||||||
}
|
```java
|
||||||
```
|
@Scheduled(fixedDelay = 5000)
|
||||||
|
public void doSomething() {
|
||||||
### @Async注解
|
// something that should run periodically
|
||||||
通过为方法指定@Async注解,可以让该方法异步执行,该方法的执行通过TaskExecutor。
|
}
|
||||||
```java
|
```
|
||||||
@Async
|
> 默认情况下,fixedDelay、fixedRate、initialDelay的时间单位都是ms,可以指定timeUnit来指定其他的时间单位:
|
||||||
void doSomething() {
|
> ```java
|
||||||
// this will be run asynchronously
|
> @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
|
||||||
}
|
> public void doSomething() {
|
||||||
```
|
> // something that should run periodically
|
||||||
可以为@Async标注的方法指定参数和返回值,但是异步方法的返回值只能为void或是Future类型。
|
> }
|
||||||
在该异步方法的调用方调用返回Future实例的get方法之前,调用方仍然能够执行其他操作,异步方法的执行位于TaskExecutor的线程中。
|
> ```
|
||||||
```java
|
|
||||||
@Async
|
可以为@Scheduled注解使用cron表达式
|
||||||
Future<String> returnSomething(int i) {
|
```java
|
||||||
// this will be run asynchronously
|
@Scheduled(cron="*/5 * * * * MON-FRI")
|
||||||
}
|
public void doSomething() {
|
||||||
```
|
// something that should run on weekdays only
|
||||||
不能将@Async注解和生命周期回调(例如@PostConstruct)进行混用,如果想要异步的初始化bean对象,需要按照如下方法:
|
}
|
||||||
```java
|
```
|
||||||
public class SampleBeanImpl implements SampleBean {
|
|
||||||
|
### @Async注解
|
||||||
@Async
|
通过为方法指定@Async注解,可以让该方法异步执行,该方法的执行通过TaskExecutor。
|
||||||
void doSomething() {
|
```java
|
||||||
// ...
|
@Async
|
||||||
}
|
void doSomething() {
|
||||||
|
// this will be run asynchronously
|
||||||
}
|
}
|
||||||
|
```
|
||||||
public class SampleBeanInitializer {
|
可以为@Async标注的方法指定参数和返回值,但是异步方法的返回值只能为void或是Future类型。
|
||||||
|
在该异步方法的调用方调用返回Future实例的get方法之前,调用方仍然能够执行其他操作,异步方法的执行位于TaskExecutor的线程中。
|
||||||
private final SampleBean bean;
|
```java
|
||||||
|
@Async
|
||||||
public SampleBeanInitializer(SampleBean bean) {
|
Future<String> returnSomething(int i) {
|
||||||
this.bean = bean;
|
// this will be run asynchronously
|
||||||
}
|
}
|
||||||
|
```
|
||||||
@PostConstruct
|
不能将@Async注解和生命周期回调(例如@PostConstruct)进行混用,如果想要异步的初始化bean对象,需要按照如下方法:
|
||||||
public void initialize() {
|
```java
|
||||||
bean.doSomething();
|
public class SampleBeanImpl implements SampleBean {
|
||||||
}
|
|
||||||
|
@Async
|
||||||
}
|
void doSomething() {
|
||||||
```
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SampleBeanInitializer {
|
||||||
|
|
||||||
|
private final SampleBean bean;
|
||||||
|
|
||||||
|
public SampleBeanInitializer(SampleBean bean) {
|
||||||
|
this.bean = bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initialize() {
|
||||||
|
bean.doSomething();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### @Async方法的异常处理
|
||||||
|
当@Async方法的返回类型是Future类型时,处理async方法执行时抛出的异常非常简单,当在Future对象上调用get方法时异常会被抛出。**但当@Async方法的返回类型是void时,执行时抛出的异常既无法被捕获也无法被传输。可以指定一个AsyncUncaughtExceptionHandler来处理此类异常。**
|
||||||
|
```java
|
||||||
|
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||||
|
// handle exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Cron表达式
|
||||||
|
在Spring中,cron表达式格式如下:
|
||||||
|
\* \* \* \* \* \*
|
||||||
|
其代表为(s,min,hour,day of month/\*(1~31)\*/,month/\*(1~12)\*/,day of week/\*(0~7,0 or 7 is sun)\*/)
|
||||||
|
> 规则如下:
|
||||||
|
> - 所有字段都可以用(*)来匹配所有值
|
||||||
|
> - 逗号(,)可以用来分隔同一字段中多个值
|
||||||
|
> - 分号(-)可以用来指定范围,指定的范围左右都包含,eg,1-5代表[1,5]
|
||||||
|
> - 在范围(或是*)后跟随下划线(/)代表间隔,如在分钟字段指定*/20,代表该小时内每过20min
|
||||||
|
> - 对于月份或者day of week,可以使用英文名的前三个字母来代替,大小写不敏感
|
||||||
|
> - 在day of month或day of week字段中可以包含L字母
|
||||||
|
> - 在day of month字段,L代表该月的最后一天,在该字段还可以为L指定一个负的偏移量,如L-n代表该月的倒数第n+1天
|
||||||
|
> - 在day of week字段,L代表该周的最后一天,L前还可以前缀月份的数字或是月份的前三个字母(dL或DDDL,如7L或sunL,代表该月份的最后一个星期日),代该月份的最后一个day of week
|
||||||
|
> - day of month字段可以指定为nW,代表离day of month为n最近的一个工作日,如果n为周六,则该该字段代表的值为周五的day of month,如果n为周六,则该字段代表下周一的day of month(如果n为1其位于周六,其也代表下周一的day of month,1W代表该月的第一个工作日)
|
||||||
|
> - 如果day of month的值为LW,则代表该月的最后一个工作日
|
||||||
|
> - day of week字段还能指定为d#n或DDD#n的形式,代表该月的的第几个d in week(例如SUN#2,代表当前月的第二个星期日)
|
||||||
|
|
||||||
|
#### Macros(宏)
|
||||||
|
Cron表达式可读性不太好,可以使用如下预定义的宏:
|
||||||
|
| Macro | meaning |
|
||||||
|
|:-:|:-:|
|
||||||
|
| @yearly (or @annually) | once a year (0 0 0 1 1 *) |
|
||||||
|
| @monthly | once a month (0 0 0 1 * *) |
|
||||||
|
| @weekly | once a week (0 0 0 * * 0) |
|
||||||
|
| @daily (or @midnight) | once a day (0 0 0 * * *), or |
|
||||||
|
| @hourly | once an hour, (0 0 * * * *) |
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
# Spring Data Redis
|
# Spring Data Redis
|
||||||
- ## Spring Data Redis
|
- ## Spring Data Redis
|
||||||
- 在Spring框架中,与Redis进行通信,既提供了低层次的api与字节数据进行交互,也提供了高度抽象的api供用户使用
|
- 在Spring框架中,与Redis进行通信,既提供了低层次的api与字节数据进行交互,也提供了高度抽象的api供用户使用
|
||||||
- Redis Connection支持通过连接器和Redis服务端进行低层次的交互
|
- Redis Connection支持通过连接器和Redis服务端进行低层次的交互
|
||||||
- RedisTemplate则是向用户提供了高层次api与Rdis服务端进行交互
|
- RedisTemplate则是向用户提供了高层次api与Rdis服务端进行交互
|
||||||
- ## 连接Redis
|
- ## 连接Redis
|
||||||
- 为了通过IOC容器连接Redis,需要使用统一的Spring Redis API。对于任何库提供的Redis Connector,都会统一向外提供一致的Spring Redis API。
|
- 为了通过IOC容器连接Redis,需要使用统一的Spring Redis API。对于任何库提供的Redis Connector,都会统一向外提供一致的Spring Redis API。
|
||||||
- Spring Redis API通过RedisConnection和RedisConnectionFactory接口进行工作,并且从Redis处获取连接
|
- Spring Redis API通过RedisConnection和RedisConnectionFactory接口进行工作,并且从Redis处获取连接
|
||||||
- ## RedisConnection和RedisConnectionFactory
|
- ## RedisConnection和RedisConnectionFactory
|
||||||
- RdisConnection用于提供和Redis后端的交互。RedisConnection接口会自动将底层Connector库异常转化为统一的Spring DAO异常,因而在使用ReidsConnection的过程中,如果切换底层链接库,并不需要代码的改动。
|
- RdisConnection用于提供和Redis后端的交互。RedisConnection接口会自动将底层Connector库异常转化为统一的Spring DAO异常,因而在使用ReidsConnection的过程中,如果切换底层链接库,并不需要代码的改动。
|
||||||
- RedisConnection时通过RedisConnectionFactory来获取的
|
- RedisConnection时通过RedisConnectionFactory来获取的
|
||||||
- ## RedisTemplate
|
- ## RedisTemplate
|
||||||
- RedisTemplate是线程安全的,可以在多个线程中被并发安全的使用
|
- RedisTemplate是线程安全的,可以在多个线程中被并发安全的使用
|
||||||
- RedisTemplate使用java底层的序列化机制,在通过RedisTemplate读取或者写入数据时,会通过Java的序列化机制将对象序列化/反序列化。
|
- RedisTemplate使用java底层的序列化机制,在通过RedisTemplate读取或者写入数据时,会通过Java的序列化机制将对象序列化/反序列化。
|
||||||
- RedisTemplate要求key是非空的,但是可以接受空的value值
|
- RedisTemplate要求key是非空的,但是可以接受空的value值
|
||||||
- 当想要使用RedisTemplate的某个视图时,可以将RedisTemplate对象注入给该名为xxxOperations的视图。
|
- 当想要使用RedisTemplate的某个视图时,可以将RedisTemplate对象注入给该名为xxxOperations的视图。
|
||||||
```java
|
```java
|
||||||
@Service
|
@Service
|
||||||
public class RedisTemplateOperations {
|
public class RedisTemplateOperations {
|
||||||
// 直接注入
|
// 直接注入
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate<String,String> redisTemplate;
|
private RedisTemplate<String,String> redisTemplate;
|
||||||
|
|
||||||
// 将RedisTemplate转换成其某一个视图之后再注入
|
// 将RedisTemplate转换成其某一个视图之后再注入
|
||||||
@Resource(name="redisTemplate")
|
@Resource(name="redisTemplate")
|
||||||
private ListOperations<String,String> opsForList;
|
private ListOperations<String,String> opsForList;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- ## StringRedisTemplate
|
- ## StringRedisTemplate
|
||||||
- 在Redis操作中,key和Value通常都是String类型。故而,Spring Data Redis提供了操作StringRedisTemplate类
|
- 在Redis操作中,key和Value通常都是String类型。故而,Spring Data Redis提供了操作StringRedisTemplate类
|
||||||
- StringRedisTemplate是RedisTemplate的子类,并且在应用启动时IOC容器中会注入redisTemplate和stringRedisTemplate两个对象
|
- StringRedisTemplate是RedisTemplate的子类,并且在应用启动时IOC容器中会注入redisTemplate和stringRedisTemplate两个对象
|
||||||
- 相比较于RedisTemplate,StringRedisTemplate底层使用StringRedisSerializer来进行序列化,其序列化的key和value都是可读的
|
- 相比较于RedisTemplate,StringRedisTemplate底层使用StringRedisSerializer来进行序列化,其序列化的key和value都是可读的
|
||||||
- ## 序列化Serializer选择
|
- ## 序列化Serializer选择
|
||||||
- 除了先前提到的jdk默认的序列化机制和StringRedisSerializer,Spring Data Redis还提供了其他Serializer
|
- 除了先前提到的jdk默认的序列化机制和StringRedisSerializer,Spring Data Redis还提供了其他Serializer
|
||||||
- 例如,可以选择将key和value化为json字符串格式,可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer
|
- 例如,可以选择将key和value化为json字符串格式,可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer
|
||||||
> 相对于Jackson2JsonSerializer,GenericJackson2JsonSerializer在序列化对象为json串时添加了对象的java类型信息,故而在将json串反序列化并且转换为原有类型时不会抛出异常
|
> 相对于Jackson2JsonSerializer,GenericJackson2JsonSerializer在序列化对象为json串时添加了对象的java类型信息,故而在将json串反序列化并且转换为原有类型时不会抛出异常
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
* 如果想要自定义redisTemplate的序列化方式,可以添加如下配置类
|
* 如果想要自定义redisTemplate的序列化方式,可以添加如下配置类
|
||||||
* RedisTemplate支持分别自定义key/value/hashkey/hashvalue的序列化方式
|
* RedisTemplate支持分别自定义key/value/hashkey/hashvalue的序列化方式
|
||||||
* RedisTemplate也支持设置defaultSerializer,当key/value/hashkey/hashvalue的Serializer没有显式指定时,会应用defaultSerializer
|
* RedisTemplate也支持设置defaultSerializer,当key/value/hashkey/hashvalue的Serializer没有显式指定时,会应用defaultSerializer
|
||||||
**/
|
**/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SerializerConfig {
|
public class SerializerConfig {
|
||||||
@Bean(name="redisTemplate")
|
@Bean(name="redisTemplate")
|
||||||
RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||||
redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
|
redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
|
||||||
return redisTemplate;
|
return redisTemplate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Spring Data
|
# Spring Data
|
||||||
- ## Spring Boot中选择连接池的算法:
|
- ## Spring Boot中选择连接池的算法:
|
||||||
- HikariCP的表现和并发性都很好,如果HikariCP可以被获取,选择HikariCP
|
- HikariCP的表现和并发性都很好,如果HikariCP可以被获取,选择HikariCP
|
||||||
- 如果HikariCP不能获取,选用Tomcat Datasource
|
- 如果HikariCP不能获取,选用Tomcat Datasource
|
||||||
- 如果Tomcat Datasource也不能获取,选用DBCP2
|
- 如果Tomcat Datasource也不能获取,选用DBCP2
|
||||||
- 如果上述都无法获取,选用Oracle UCP
|
- 如果上述都无法获取,选用Oracle UCP
|
||||||
- Spring Boot使用自定义连接池:
|
- Spring Boot使用自定义连接池:
|
||||||
- 可以通过显式指定自定义的连接池种类来绕过该算法,通过指定spring.datasource.type来自定义连接池
|
- 可以通过显式指定自定义的连接池种类来绕过该算法,通过指定spring.datasource.type来自定义连接池
|
||||||
- 可以通过DatasourceBuilder来定义额外的datasource。如果定义了自己的datasouce bean,那么自动装配将不会发生。
|
- 可以通过DatasourceBuilder来定义额外的datasource。如果定义了自己的datasouce bean,那么自动装配将不会发生。
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
# Spring单元测试
|
# Spring单元测试
|
||||||
## SpringBootTest
|
## SpringBootTest
|
||||||
在SpringBoot中,提供了@SpringBootTest注解。当需要SpringBoot特性时,可以通过使用@SpringBootTest注解来作为@ContextConfiguration的替代。@SprngBootTest创建ApplicationContext,该context在test中被使用。
|
在SpringBoot中,提供了@SpringBootTest注解。当需要SpringBoot特性时,可以通过使用@SpringBootTest注解来作为@ContextConfiguration的替代。@SprngBootTest创建ApplicationContext,该context在test中被使用。
|
||||||
## @AfterAll
|
## @AfterAll
|
||||||
该注解标明的方法,会在所有Test执行完成之后再执行
|
该注解标明的方法,会在所有Test执行完成之后再执行
|
||||||
> ### @AfterAll注解特性
|
> ### @AfterAll注解特性
|
||||||
> - @AfterAll注解的方法必须含有void类型返回值
|
> - @AfterAll注解的方法必须含有void类型返回值
|
||||||
> - @AfterAll注解标注方法不能为private
|
> - @AfterAll注解标注方法不能为private
|
||||||
> - 默认情况下,@AfterAll注解的方法必须是static修饰的
|
> - 默认情况下,@AfterAll注解的方法必须是static修饰的
|
||||||
|
|
||||||
> ### 在非static方法中标注@AfterAll
|
> ### 在非static方法中标注@AfterAll
|
||||||
> 在非static方法上标注@AfterAll,需要在class上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS)。
|
> 在非static方法上标注@AfterAll,需要在class上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS)。
|
||||||
> 因为默认情况下TestInstance的默认生命周期是PER_METHOD
|
> 因为默认情况下TestInstance的默认生命周期是PER_METHOD
|
||||||
> ```JAVA
|
> ```JAVA
|
||||||
> @TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
> @TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
> public class BeforeAndAfterAnnotationsUnitTest {
|
> public class BeforeAndAfterAnnotationsUnitTest {
|
||||||
>
|
>
|
||||||
> String input;
|
> String input;
|
||||||
> Long result;
|
> Long result;
|
||||||
> @BeforeAll
|
> @BeforeAll
|
||||||
> public void setup() {
|
> public void setup() {
|
||||||
> input = "77";
|
> input = "77";
|
||||||
> }
|
> }
|
||||||
>
|
>
|
||||||
> @AfterAll
|
> @AfterAll
|
||||||
> public void teardown() {
|
> public void teardown() {
|
||||||
> input = null;
|
> input = null;
|
||||||
> result = null;
|
> result = null;
|
||||||
> }
|
> }
|
||||||
>
|
>
|
||||||
> @Test
|
> @Test
|
||||||
> public void whenConvertStringToLong_thenResultShouldBeLong() {
|
> public void whenConvertStringToLong_thenResultShouldBeLong() {
|
||||||
> result = Long.valueOf(input);
|
> result = Long.valueOf(input);
|
||||||
> Assertions.assertEquals(77l, result);
|
> Assertions.assertEquals(77l, result);
|
||||||
> }
|
> }
|
||||||
> }
|
> }
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
## @AfterEach
|
## @AfterEach
|
||||||
该注解标明的方法,在每次@Test标注方法执行完成之后都会被执行。
|
该注解标明的方法,在每次@Test标注方法执行完成之后都会被执行。
|
||||||
> ### @AfterEach特性
|
> ### @AfterEach特性
|
||||||
> - 标注方法返回值为空
|
> - 标注方法返回值为空
|
||||||
> - 标注方法不为private
|
> - 标注方法不为private
|
||||||
> - 标注方法不是static
|
> - 标注方法不是static
|
||||||
|
|
||||||
## @BeforeAll
|
## @BeforeAll
|
||||||
类似@AfterAll
|
类似@AfterAll
|
||||||
## @BeforeEach
|
## @BeforeEach
|
||||||
类似@AfterEach
|
类似@AfterEach
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
# Spring Boot JSON
|
# Spring Boot JSON
|
||||||
## Spring Boot JSON简介
|
## Spring Boot JSON简介
|
||||||
在Spring Boot中为JSON提供了三个集成的内置库:Gson、Jackson、JSON-B。
|
在Spring Boot中为JSON提供了三个集成的内置库:Gson、Jackson、JSON-B。
|
||||||
> 其中,Jackson是Spring Boot推荐并且默认的JSON库。
|
> 其中,Jackson是Spring Boot推荐并且默认的JSON库。
|
||||||
|
|
||||||
Spring Boot项目为Jackson提供了自动装配,并且Jackson是spring-boot-starter-json启动器的一部分。当Jackson依赖在classpath下时,ObjectMapper的bean对象会自动的被配置。
|
Spring Boot项目为Jackson提供了自动装配,并且Jackson是spring-boot-starter-json启动器的一部分。当Jackson依赖在classpath下时,ObjectMapper的bean对象会自动的被配置。
|
||||||
## 自定义序列化器和反序列化器
|
## 自定义序列化器和反序列化器
|
||||||
如果使用Jackson进行json数据的序列化和反序列化,你可能需要实现自己的序列化类和反序列化类。可以通过@JsonComponent注解来定义自己的JsonSerializer, JsonDeserializer,JsonObjectSerializer,JsonObjectDeserializer
|
如果使用Jackson进行json数据的序列化和反序列化,你可能需要实现自己的序列化类和反序列化类。可以通过@JsonComponent注解来定义自己的JsonSerializer, JsonDeserializer,JsonObjectSerializer,JsonObjectDeserializer
|
||||||
```java
|
```java
|
||||||
@JsonComponent
|
@JsonComponent
|
||||||
public class MyJsonComponent {
|
public class MyJsonComponent {
|
||||||
|
|
||||||
public static class Serializer extends JsonSerializer<MyObject> {
|
public static class Serializer extends JsonSerializer<MyObject> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
|
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
|
||||||
jgen.writeStartObject();
|
jgen.writeStartObject();
|
||||||
jgen.writeStringField("name", value.getName());
|
jgen.writeStringField("name", value.getName());
|
||||||
jgen.writeNumberField("age", value.getAge());
|
jgen.writeNumberField("age", value.getAge());
|
||||||
jgen.writeEndObject();
|
jgen.writeEndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Deserializer extends JsonDeserializer<MyObject> {
|
public static class Deserializer extends JsonDeserializer<MyObject> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
|
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
|
||||||
ObjectCodec codec = jsonParser.getCodec();
|
ObjectCodec codec = jsonParser.getCodec();
|
||||||
JsonNode tree = codec.readTree(jsonParser);
|
JsonNode tree = codec.readTree(jsonParser);
|
||||||
String name = tree.get("name").textValue();
|
String name = tree.get("name").textValue();
|
||||||
int age = tree.get("age").intValue();
|
int age = tree.get("age").intValue();
|
||||||
return new MyObject(name, age);
|
return new MyObject(name, age);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonComponent
|
@JsonComponent
|
||||||
public class MyJsonComponent {
|
public class MyJsonComponent {
|
||||||
|
|
||||||
public static class Serializer extends JsonObjectSerializer<MyObject> {
|
public static class Serializer extends JsonObjectSerializer<MyObject> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
|
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
jgen.writeStringField("name", value.getName());
|
jgen.writeStringField("name", value.getName());
|
||||||
jgen.writeNumberField("age", value.getAge());
|
jgen.writeNumberField("age", value.getAge());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
|
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
|
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
|
||||||
JsonNode tree) throws IOException {
|
JsonNode tree) throws IOException {
|
||||||
String name = nullSafeValue(tree.get("name"), String.class);
|
String name = nullSafeValue(tree.get("name"), String.class);
|
||||||
int age = nullSafeValue(tree.get("age"), Integer.class);
|
int age = nullSafeValue(tree.get("age"), Integer.class);
|
||||||
return new MyObject(name, age);
|
return new MyObject(name, age);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## Jackson使用
|
## Jackson使用
|
||||||
Jackson使用ObjectMapper来将json转化为java对象,或者将java对象转化为json。
|
Jackson使用ObjectMapper来将json转化为java对象,或者将java对象转化为json。
|
||||||
### ObjectMapper处理json域字段和java类域字段的映射关系
|
### ObjectMapper处理json域字段和java类域字段的映射关系
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# File Upload and Download in Spring Boot
|
# File Upload and Download in Spring Boot
|
||||||
## SpringBoot上传文件配置
|
## SpringBoot上传文件配置
|
||||||
在application.properties中,可以进行如下配置
|
在application.properties中,可以进行如下配置
|
||||||
```properties
|
```properties
|
||||||
spring.servlet.multipart.enabled=true
|
spring.servlet.multipart.enabled=true
|
||||||
spring.servlet.multipart.max-file-size=10MB
|
spring.servlet.multipart.max-file-size=10MB
|
||||||
spring.servlet.multipart.max-request-size=15MB
|
spring.servlet.multipart.max-request-size=15MB
|
||||||
```
|
```
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
# 分布式事务: Saga模式
|
# 分布式事务: Saga模式
|
||||||
## Saga模型
|
## Saga模型
|
||||||
- 每个Saga都由一系列子事务Ti组成
|
- 每个Saga都由一系列子事务Ti组成
|
||||||
- 每个子事务Ti都有其对应的补偿动作Ci,补偿操作用于对Ti操作产生的结果进行撤销
|
- 每个子事务Ti都有其对应的补偿动作Ci,补偿操作用于对Ti操作产生的结果进行撤销
|
||||||
## Saga的执行顺序
|
## Saga的执行顺序
|
||||||
1. T1, T2, T3, ... ,Tn(全部执行成功)
|
1. T1, T2, T3, ... ,Tn(全部执行成功)
|
||||||
2. T1, T2, T3, ..., Tj, Cj, Cj-1,... , C1 (执行出错之后回滚之前所有成功的子事务)
|
2. T1, T2, T3, ..., Tj, Cj, Cj-1,... , C1 (执行出错之后回滚之前所有成功的子事务)
|
||||||
## 执行出错时,Saga的恢复策略
|
## 执行出错时,Saga的恢复策略
|
||||||
1. 向后恢复(backword recovery),对所有已经完成的子事务进行补偿操作,任一子事务Ti执行失败,撤销掉之前所有执行成功的子事务,使得整个Saga的执行结果都撤销
|
1. 向后恢复(backword recovery),对所有已经完成的子事务进行补偿操作,任一子事务Ti执行失败,撤销掉之前所有执行成功的子事务,使得整个Saga的执行结果都撤销
|
||||||
2. 向前恢复(forward recovery),重新尝试失败的事务。假设每个事务最终都会执行成功。其执行顺序为T1, ... Tj(失败), Tj(重试),...,...,Tn。该种情况下并不需要Ci操作对子事务Ti的操作结果进行撤销
|
2. 向前恢复(forward recovery),重新尝试失败的事务。假设每个事务最终都会执行成功。其执行顺序为T1, ... Tj(失败), Tj(重试),...,...,Tn。该种情况下并不需要Ci操作对子事务Ti的操作结果进行撤销
|
||||||
## Saga模式的结构
|
## Saga模式的结构
|
||||||
Saga的嵌套结构只允许存在两个层次:
|
Saga的嵌套结构只允许存在两个层次:
|
||||||
1. 顶层的Saga
|
1. 顶层的Saga
|
||||||
2. 简单的子事务
|
2. 简单的子事务
|
||||||
## Saga特性
|
## Saga特性
|
||||||
- 在外层(各个Sagas都在执行时,各个Sagas在执行过程中已经提交的子事务Ti,其执行结果对其他Sagas可见),隔离性并无法被满足,Sagas可能看到其他Sagas的执行结果
|
- 在外层(各个Sagas都在执行时,各个Sagas在执行过程中已经提交的子事务Ti,其执行结果对其他Sagas可见),隔离性并无法被满足,Sagas可能看到其他Sagas的执行结果
|
||||||
- 每个子事务都是一个独立事务,各个子事务为独立的原子行为
|
- 每个子事务都是一个独立事务,各个子事务为独立的原子行为
|
||||||
## Saga ACID
|
## Saga ACID
|
||||||
- 原子性:正常情况下可以保证
|
- 原子性:正常情况下可以保证
|
||||||
- 一致性:执行过程中,可能会有A库和B库违反一致性要求的情况,但是最终成功之后是一致的
|
- 一致性:执行过程中,可能会有A库和B库违反一致性要求的情况,但是最终成功之后是一致的
|
||||||
- 隔离性:Sagas A能看到Sagas B部分执行的执行结果
|
- 隔离性:Sagas A能看到Sagas B部分执行的执行结果
|
||||||
- 持久性:对于Sagas中的子事务Ti,其执行完成之后就会commit,而其撤销操作则是会调用Ci对已经提交的操作进行撤销操作
|
- 持久性:对于Sagas中的子事务Ti,其执行完成之后就会commit,而其撤销操作则是会调用Ci对已经提交的操作进行撤销操作
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# Seata
|
# Seata
|
||||||
## Seata简介
|
## Seata简介
|
||||||
Seata是一款开源的分布式事务解决方案,向客户提供了高性能且简单易用的分布式事务服务。Seata向客户提供了AT,TCC,SAGA,XA等模式。
|
Seata是一款开源的分布式事务解决方案,向客户提供了高性能且简单易用的分布式事务服务。Seata向客户提供了AT,TCC,SAGA,XA等模式。
|
||||||
## AT模式
|
## AT模式
|
||||||
### 前提
|
### 前提
|
||||||
- 基于的关系型数据库要支持本地事务的acid
|
- 基于的关系型数据库要支持本地事务的acid
|
||||||
- java应用,通过jdbc访问数据库
|
- java应用,通过jdbc访问数据库
|
||||||
-
|
-
|
||||||
64
分布式事务/TCC.md
64
分布式事务/TCC.md
@@ -1,32 +1,32 @@
|
|||||||
# 分布式事务: TCC模式
|
# 分布式事务: TCC模式
|
||||||
## TCC事务
|
## TCC事务
|
||||||
TCC是Try、Confirm、Cancel三个单词的首字母缩写,TCC分为三个阶段:预处理(Try)、确认(Confirm)、撤销(Cancel)。
|
TCC是Try、Confirm、Cancel三个单词的首字母缩写,TCC分为三个阶段:预处理(Try)、确认(Confirm)、撤销(Cancel)。
|
||||||
## TCC工作流
|
## TCC工作流
|
||||||
## 预处理阶段(Try phase)
|
## 预处理阶段(Try phase)
|
||||||
在预处理阶段,请求者会请求服务的提供者做一些尝试性的操作。在该阶段,服务提供者会完成一些业务检查与验证,并且预占需要的业务资源。
|
在预处理阶段,请求者会请求服务的提供者做一些尝试性的操作。在该阶段,服务提供者会完成一些业务检查与验证,并且预占需要的业务资源。
|
||||||
## 确认阶段(Confirm phase)
|
## 确认阶段(Confirm phase)
|
||||||
- 如果服务提供者成功执行了Try阶段,并且请求者决定继续执行操作,那么请求者可以在确认阶段执行确认操作。
|
- 如果服务提供者成功执行了Try阶段,并且请求者决定继续执行操作,那么请求者可以在确认阶段执行确认操作。
|
||||||
- ***具体的业务在确认阶段被执行***。
|
- ***具体的业务在确认阶段被执行***。
|
||||||
- 在确认阶段,不会执行更多的验证操作,所有验证操作都会在Try阶段执行
|
- 在确认阶段,不会执行更多的验证操作,所有验证操作都会在Try阶段执行
|
||||||
- 在Confirm阶段,只会使用Try阶段预留的业务资源
|
- 在Confirm阶段,只会使用Try阶段预留的业务资源
|
||||||
> 通常,认为Confirm阶段是不会执行失败的,如果Try阶段被成功执行,那么Confirm阶段也能成功执行,如果Try成功而Confirm执行失败,会对Confirm阶段进行重试
|
> 通常,认为Confirm阶段是不会执行失败的,如果Try阶段被成功执行,那么Confirm阶段也能成功执行,如果Try成功而Confirm执行失败,会对Confirm阶段进行重试
|
||||||
## 撤销阶段(Cancel phase)
|
## 撤销阶段(Cancel phase)
|
||||||
在撤销阶段,如果Try阶段未正确完成且请求者决定不再继续执行,请求者可以在服务提供者上执行撤销操作。在Try阶段预占的业务资源应该在撤销阶段被正确释放
|
在撤销阶段,如果Try阶段未正确完成且请求者决定不再继续执行,请求者可以在服务提供者上执行撤销操作。在Try阶段预占的业务资源应该在撤销阶段被正确释放
|
||||||
***撤销阶段(Cancel)是对预处理阶段(Try)的反向操作***
|
***撤销阶段(Cancel)是对预处理阶段(Try)的反向操作***
|
||||||
## TCC事务
|
## TCC事务
|
||||||
对于TCC事务,全局事务管理器首先会发起所有分支事务的Try操作
|
对于TCC事务,全局事务管理器首先会发起所有分支事务的Try操作
|
||||||
- 若任何一个分支事务的Try操作执行失败,都会导致所有分支事务的Cancel操作被执行
|
- 若任何一个分支事务的Try操作执行失败,都会导致所有分支事务的Cancel操作被执行
|
||||||
- 若所有分支事务的Try操作都执行成功,那么全局事务管理器会发起所有分支事务的Confirm操作
|
- 若所有分支事务的Try操作都执行成功,那么全局事务管理器会发起所有分支事务的Confirm操作
|
||||||
在执行分支事务Confirm/Cancel操作时,如果执行失败,那么全局事务管理器会对失败操作进行重试
|
在执行分支事务Confirm/Cancel操作时,如果执行失败,那么全局事务管理器会对失败操作进行重试
|
||||||
> 通常,Cancel阶段也被认为是一定执行成功的,如果在Cancel执行时发生错误,需要进行重试或进行人工处理
|
> 通常,Cancel阶段也被认为是一定执行成功的,如果在Cancel执行时发生错误,需要进行重试或进行人工处理
|
||||||
## TCC事务中需要注意的要点
|
## TCC事务中需要注意的要点
|
||||||
### 空回滚
|
### 空回滚
|
||||||
若是在没有调用TCC Try阶段的情况下直接调用二阶段的Cancel方法,Cancel方法需要识别出之前并未调用过Try阶段方法,直接返回Cancel成功
|
若是在没有调用TCC Try阶段的情况下直接调用二阶段的Cancel方法,Cancel方法需要识别出之前并未调用过Try阶段方法,直接返回Cancel成功
|
||||||
### 幂等
|
### 幂等
|
||||||
由于在各阶段执行时都存在重试机制,故而要保证Try、Confirm、Cancel接口都要保证幂等性,保证占用资源或释放资源的操作不会被重复执行
|
由于在各阶段执行时都存在重试机制,故而要保证Try、Confirm、Cancel接口都要保证幂等性,保证占用资源或释放资源的操作不会被重复执行
|
||||||
### 悬挂
|
### 悬挂
|
||||||
在RPC调用过程中,由于网络拥堵等原因,Cancel操作可能先于Try操作执行。
|
在RPC调用过程中,由于网络拥堵等原因,Cancel操作可能先于Try操作执行。
|
||||||
> 1. 如果先调用Try时,网络拥堵发生超时,那么TM会通知RM回滚分布式事务,调用Cancel操作
|
> 1. 如果先调用Try时,网络拥堵发生超时,那么TM会通知RM回滚分布式事务,调用Cancel操作
|
||||||
> 2. 因为网络拥堵,可能调用完Cancel后,RPC的Try请求才到来
|
> 2. 因为网络拥堵,可能调用完Cancel后,RPC的Try请求才到来
|
||||||
> 3. 此时到来的Try请求会预留资源,而预留资源无法被其他事务所使用
|
> 3. 此时到来的Try请求会预留资源,而预留资源无法被其他事务所使用
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user