This commit is contained in:
wu xiangkai
2023-01-16 18:33:39 +08:00
34 changed files with 5399 additions and 5217 deletions

View File

@@ -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;
} }
``` ```

View 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组合可以有如下常用方法
- thenApplyT -> U)对CompletableFuture对象的返回结果执行操作并且产生一个返回值
- thenAccept(T -> void)类似thenApply操作上一个future的返回值但是返回类型为void
- handle(T,Throwable)->U处理上一个future返回的result并且产生一个返回值
- thenCompose(T->CompletableFuture\<U\>):将上一个Future返回值作为参数传递并且返回CompletableFuture\<U\>
- whenCompleteT,Throwable->void类似于handle但是不产生返回值
- exceptionallyThrowable->T):处理异常并返回一个结果
- completeOnTimeoutT,long,TimeUnit)当超时时将传入的参数T作为返回结果
- orTimeOutlong,TimeUnit)当超时时产生一个TimeoutExcetpion作为结果
- thenRunRunnable:执行该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
- runAfterBothCompletableFuture\<\>,Runnable当两个future都执行完之后执行Runnable
- applyToEither(CompletableFuture\<T\>T-> U)当任一future执行完成通过该result产生返回值
- acceptEitherCompletableFuture\<T\>T->Void):类似applyToEither但是返回值为void
- runAfterEitherCompletableFuture\<\>,Runnable在任一future执行完成之后执行Runnable
- static allOf(CompletableFuture\<?\>...)在参数中所有future执行完成之后返回的future处于完成状态
- static anyOf(CompletableFuture\<?\>...)在参数中任一future执行完成之后返回的future处于完成状态
### completedFuture
CompletableFuture.completedFuture会返回一个已经执行完成的CompletableFuture对象并且该future对象返回值为T

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# binlog 日志 # binlog 日志
* ## binlog二进制日志 * ## binlog二进制日志
* 定义binlog日志文件也称之为变更日志文件记录了数据库记录的所有DDL、DML等数据库更新事件的语句但是不包括任何没有更新数据的语句。 * 定义binlog日志文件也称之为变更日志文件记录了数据库记录的所有DDL、DML等数据库更新事件的语句但是不包括任何没有更新数据的语句。
* 用途: * 用途:
* 数据恢复如果mysql数据库进程意外停止可以通过binlog中的记录来恢复数据库中的数据 * 数据恢复如果mysql数据库进程意外停止可以通过binlog中的记录来恢复数据库中的数据
* 数据复制可以通过binlog来实现mysql数据库的主从一致 * 数据复制可以通过binlog来实现mysql数据库的主从一致

View File

@@ -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中关于修改该条数据的记录
* ReadViewReadView是MVCC机制中事务对数据进行快照读时产生的一个读视图。 * ReadViewReadView是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_idtrx_ids中最小的事务id * up_limit_idtrx_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在读已提交的隔离级别下并不能够解决幻读的问题。

View File

@@ -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操作会生成显示锁

View File

@@ -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的内容回滚事务时也能将数据恢复到崩溃前尚未被事务修改的状态。

View File

@@ -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

View File

@@ -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环境中使用rememberMelogout默认会从浏览器中删除rememberMe cookie。 当执行登出操作时Shiro会关闭当前session并且会移除当前subject的任何identity。如果在web环境中使用rememberMelogout默认会从浏览器中删除rememberMe cookie。

View File

@@ -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、deleteCRUD 对于与数据相关的资源权限通常有create、read、update、deleteCRUD
#### 权限粒度级别 #### 权限粒度级别
在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 {
//dont show the button?) //dont 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 {
//dont show the button? //dont 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 {
//dont show the button? //dont 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 callers roles imply the Account //of the callers 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
//doesnt have the teller role: //doesnt 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
} }
``` ```

View File

@@ -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();
``` ```

View File

@@ -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和可自定义的customizableAuthenticationRealm支持CredentialsMatcher的概念通过CredentialsMatcher来进行credentials的比较。 为了确保在credentials匹配的过程中该过程是可插入pluggable和可自定义的customizableAuthenticationRealm支持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其实现都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。 Shiro中所有开箱即用的Realm其实现都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher简单会对存储在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方法会被调用用来检测待检测权限是否隐含在其中

View File

@@ -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
SubjectSubject是一个安全术语通常意味着当前执行的用户。 SubjectSubject是一个安全术语通常意味着当前执行的用户。
```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。
对每个应用中只存在一个SecurityManagerSecurityManager是应用范围内的单例。默认SecurityManager的实现是POJO可以通过java代码、Spring xml、yaml、properties等方式来进行配置。 对每个应用中只存在一个SecurityManagerSecurityManager是应用范围内的单例。默认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 {
//dont delete jsmith //dont 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 files bytes: //encrypt a files 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时的任何代码。**

View File

@@ -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会通过BCtyptHash解密算法来对密码的存储进行保护 > Spring Security会通过Spring Security会通过BCtyptHash解密算法来对密码的存储进行保护
## 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支持FilterChainProxyFilterChainProxy是一个由Spring Security提供的特殊FilterFilterChainProxy通过SecurityFilterChain允许向多个Filters委托工作。 Spring Security支持FilterChainProxyFilterChainProxy是一个由Spring Security提供的特殊FilterFilterChainProxy通过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是相当安全的如果想要在该已认证主体的请求被处理完成之后清除SecurityContextSpring Security中的FilterChainProxy会保证该SecurityContext被清除。 > 使用ThreadLocal来存储SecurityContext是相当安全的如果想要在该已认证主体的请求被处理完成之后清除SecurityContextSpring 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
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联 # 创建用户表和权限表,并且将用户表和权限表之间用外键关联
# 用户表需要提供usernamepassword、用户状态 # 用户表需要提供usernamepassword、用户状态
# 权限表需要提供用户名和权限名称 # 权限表需要提供用户名和权限名称
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一起认证的信息。对于UserDetailsServiceSpring Security提供了in-memory和jdbc两种实现形式。 UserDetailsService被DaoAuthenticationProvider调用用来获取username、password和其他随着password/username一起认证的信息。对于UserDetailsServiceSpring 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中传递给AuthenticationManagerProviderManager实现了AuthenticationManager 1. authentication filter会读取username和password并且将其封装到UsernamePasswordAuthenticationToken中传递给AuthenticationManagerProviderManager实现了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中保存

View 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注解可以用于嵌套类的校验

View File

@@ -1,15 +1,15 @@
# POJO # POJO
## POJO定义 ## POJO定义
POJOPlain Old Java Object是一种直接的类型POJO并不包含对任何框架的引用。 POJOPlain 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接口也不一定要拥有无参构造方法。

View File

@@ -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);
``` ```

View File

@@ -1,177 +1,177 @@
# Spring AOP # Spring AOP
- ## Spring AOP的核心概念 - ## Spring AOP的核心概念
- Aspect切面一个模块化的考虑 - Aspect切面一个模块化的考虑
- Joint Point连接点程序执行时的一个时间点通常是方法的执行 - Joint Point连接点程序执行时的一个时间点通常是方法的执行
- Advice当切面在一个切入点执行多做时执行的动作被称之为AdviceAdvice有不同的类型before、after、around - Advice当切面在一个切入点执行多做时执行的动作被称之为AdviceAdvice有不同的类型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上两种的结合不管连接点是正常退出还是抛出异常退出都会在其之后执行
- aroundaround可以自定义连接点之前和之后的执行内容其也能够选择时候执行连接点的方法 - aroundaround可以自定义连接点之前和之后的执行内容其也能够选择时候执行连接点的方法
- ## 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

View File

@@ -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容器接口
- BeanFactoryBeanFactory是一个接口提供了高级配置功能来管理任何类型的对象 - BeanFactoryBeanFactory是一个接口提供了高级配置功能来管理任何类型的对象
- ApplicationContextApplicationContext是BeanFactory的一个子接口在BeanFactory的基础上其添加了一些更为特殊的特性。 - ApplicationContextApplicationContext是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属性将会被作为其qualifierautowired时会根据@Qualifier注解中指定的值匹配具有相同name的bean对象 - 作为一种回退机制当bean的qualifier未被定义时bean的name属性将会被作为其qualifierautowired时会根据@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到类型匹配

View File

@@ -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进行回滚对于ErrorSpring事务也会执行回滚操作 - Spring事务在默认情况下只会针对unchecked异常RuntimeException进行回滚对于ErrorSpring事务也会执行回滚操作
- 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_REQUIREDPROPAGATION.REQUIRES_NEW传播行为会一直使用独立的物理事务而不会尝试区加入外部已经存在的物理事务。 - 相对于PROPAGATION_REQUIREDPROPAGATION.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

View File

@@ -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、数字类型默认值为0boolean类型其默认值为false - 当反序列化过程中如果一个字段在json串中并没有被设置反序列化得到的对象中该字段将会被设置为默认值引用类型默认值为null、数字类型默认值为0boolean类型其默认值为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 APIJsonParser底层parser api将json串中数组转化为JsonArray并且为每个元素调用Gson.fromJson。该方法是推荐的方法 - 使用Gson Parser APIJsonParser底层parser api将json串中数组转化为JsonArray并且为每个元素调用Gson.fromJson。该方法是推荐的方法
> gson.fromJson可以针对String、Reader、JsonElement来调用 > gson.fromJson可以针对String、Reader、JsonElement来调用

View File

@@ -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.&lt;logger-name&gt;=&lt;level&gt;来定义事务级别事务级别可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。 所有的日志系统都可以通过Application.properties定义logging.level.&lt;logger-name&gt;=&lt;level&gt;来定义事务级别事务级别可以是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

View File

@@ -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隐藏这些方法

View File

@@ -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中线程池的概念。在SpringTaskExecutor接口和java.util.concurrent.Executor接口相同该接口中只有一个execute方法execute(Runnable task))接受一个task。
``` ### TaskExecutor接口的实现种类
Spring中包含许多预制的TaskExecutor实现类该实现类如下
## Spring TaskScheduler Abstraction - SyncTaskExecutor:该实现类不会执行异步的调用所有任务的执行都会发生在调用该executor的线程中。通常使用在不需要多线程的场景
为了在特定的时间点执行taskSpring引入了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
> 为了在特定的时间点执行taskSpring引入了TaskScheduler接口接口定义如下
> 实例如下: ```java
> - fixed rateTTWWWTTTWWT...(开始时间间隔为5) public interface TaskScheduler {
> - fixed delayTTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为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 rateTTWWWTTTWWT...(开始时间间隔为5)
Date lastActualExecutionTime(); > - fixed delayTTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为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)\*/)
> 规则如下:
> - 所有字段都可以用(*)来匹配所有值
> - 逗号(,)可以用来分隔同一字段中多个值
> - 分号(-可以用来指定范围指定的范围左右都包含eg1-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 month1W代表该月的第一个工作日
> - 如果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 * * * *) |

View File

@@ -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两个对象
- 相比较于RedisTemplateStringRedisTemplate底层使用StringRedisSerializer来进行序列化其序列化的key和value都是可读的 - 相比较于RedisTemplateStringRedisTemplate底层使用StringRedisSerializer来进行序列化其序列化的key和value都是可读的
- ## 序列化Serializer选择 - ## 序列化Serializer选择
- 除了先前提到的jdk默认的序列化机制和StringRedisSerializerSpring Data Redis还提供了其他Serializer - 除了先前提到的jdk默认的序列化机制和StringRedisSerializerSpring Data Redis还提供了其他Serializer
- 例如可以选择将key和value化为json字符串格式可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer - 例如可以选择将key和value化为json字符串格式可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer
> 相对于Jackson2JsonSerializerGenericJackson2JsonSerializer在序列化对象为json串时添加了对象的java类型信息故而在将json串反序列化并且转换为原有类型时不会抛出异常 > 相对于Jackson2JsonSerializerGenericJackson2JsonSerializer在序列化对象为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;
} }
} }
``` ```

View File

@@ -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那么自动装配将不会发生。

View File

@@ -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

View File

@@ -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类域字段的映射关系

View File

@@ -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
``` ```

View File

@@ -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对已经提交的操作进行撤销操作

View File

@@ -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访问数据库
- -

View File

@@ -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请求会预留资源而预留资源无法被其他事务所使用