切换默认shell到wsl

This commit is contained in:
2023-01-10 21:59:13 +08:00
parent 1d6198e9d8
commit be40280c8c
33 changed files with 5387 additions and 5387 deletions

View File

@@ -1,96 +1,96 @@
- [Apache Shiro Authentication](#apache-shiro-authentication)
- [Apache Shiro Authentication简介](#apache-shiro-authentication简介)
- [Apache Authentication概念](#apache-authentication概念)
- [subject](#subject)
- [principals](#principals)
- [credentials](#credentials)
- [realms](#realms)
- [Shiro Authentication过程](#shiro-authentication过程)
- [Shiro框架的Authentication过程](#shiro框架的authentication过程)
- [收集用户的principals和credentials](#收集用户的principals和credentials)
- [将收集的principals和credentials提交给认证系统](#将收集的principals和credentials提交给认证系统)
- [身份认证后对访问进行allow/retry authentication/block](#身份认证后对访问进行allowretry-authenticationblock)
- [rememberMe支持](#rememberme支持)
- [remembered和authenticated的区别](#remembered和authenticated的区别)
- [logging out](#logging-out)
# Apache Shiro Authentication
## Apache Shiro Authentication简介
Authentication是一个对用户进行身份认证的过程在认证过程中用户需要向应用提供用于证明用户的凭据。
## Apache Authentication概念
### subject
在应用的角度subject即是一个用户
### principals
主体用于标识一个用户可以是username、social security nubmer等
### credentials
凭据,在用户认证过程中用于认证用户的身份,可以是密码、生物识别数据(如指纹、面容等)
### realms
专用于security的dao对象用于和后端的datasource进行沟通。
## Shiro Authentication过程
### Shiro框架的Authentication过程
1. 收集用户的principals和credentials
2. 向应用的认证系统提交用户的principals和credentials
3. 认证结束之后,根据认证结果允许访问、重试访问请求或者阻塞访问
### 收集用户的principals和credentials
可以通过UsernamePasswordToken来存储用户提交的username和password并可以调用UsernamePasswordToken.rememberMe方法来启用Shiro的“remember-me”功能。
```java
//Example using most common scenario:
//String username and password. Acquire in
//system-specific manner (HTTP request, GUI, etc)
UsernamePasswordToken token = new UsernamePasswordToken( username, password );
//”Remember Me” built-in, just do this:
token.setRememberMe(true);
```
### 将收集的principals和credentials提交给认证系统
在收集完用户的principals和credentials之后需要将其提交给应用的认证系统。
在Shiro中代表认证系统的是Realm其从存放安全数据的datasource中获取数据并且对用户提交的principals和credentials进行校验。
在Shiro中该过程用如下代码表示
```java
//With most of Shiro, you'll always want to make sure you're working with the currently
//executing user, referred to as the subject
Subject currentUser = SecurityUtils.getSubject();
//Authenticate the subject by passing
//the user name and password token
//into the login method
currentUser.login(token);
```
> 在Shiro中subject可以被看做是用户**在当前执行的线程中永远有一个subject与其相关联。**
> **可以通过SecurityUtils.getSubject()方法来获取当前执行线程相关联的subject。**
> 在获取当前执行线程关联subject之后需要对当前subject进行身份认证通过subject.login(token)来对用户提交的principals和credentials进行Authentication
### 身份认证后对访问进行allow/retry authentication/block
在调用subject.login(token)之后如果身份认证成功用户将在seesion的生命周期内维持他们的identity。但是如果身份认证失败可以为抛出的异常指定不同的异常处理逻辑来定义登录失败之后的行为。
```java
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... your own ...
} catch ( AuthenticationException ae ) {
//unexpected error?
}
//No problems, show authenticated view…
```
## rememberMe支持
Apache Shiro除了正常的Authentication流程外还支持rememberMe功能。
Shiro中Subject对象拥有两个方法isRemembered()和isAuthenticated
> - 一个remembered subject其identity和principals自上次session成功认证后就被记住
> - 一个authenticated subject其identity只在本次会话中有效
### remembered和authenticated的区别
在Shiro中一个remembered subject并不代表该subject已经被authenticated。如果一个subject被remembered仅仅会向系统提示该subject可能是系统的某个用户但是不会对subject的身份提供保证。但是如果subject被authenticated该subject的identity在当前会话中已经被认证。
> 故而isRemembered校验可以用来执行一些非敏感的操作如用户自定义界面视图等。但是敏感性操作如金额信息和变动操作等必须通过isAuthenticated校验而不是isRemembered校验敏感性操作的用户身份必须得到认证。
## logging out
在Shiro中登出操作可以通过如下代码实现
```java
currentUser.logout(); //removes all identifying information and invalidates their session too.
```
当执行登出操作时Shiro会关闭当前session并且会移除当前subject的任何identity。如果在web环境中使用rememberMelogout默认会从浏览器中删除rememberMe cookie。
- [Apache Shiro Authentication](#apache-shiro-authentication)
- [Apache Shiro Authentication简介](#apache-shiro-authentication简介)
- [Apache Authentication概念](#apache-authentication概念)
- [subject](#subject)
- [principals](#principals)
- [credentials](#credentials)
- [realms](#realms)
- [Shiro Authentication过程](#shiro-authentication过程)
- [Shiro框架的Authentication过程](#shiro框架的authentication过程)
- [收集用户的principals和credentials](#收集用户的principals和credentials)
- [将收集的principals和credentials提交给认证系统](#将收集的principals和credentials提交给认证系统)
- [身份认证后对访问进行allow/retry authentication/block](#身份认证后对访问进行allowretry-authenticationblock)
- [rememberMe支持](#rememberme支持)
- [remembered和authenticated的区别](#remembered和authenticated的区别)
- [logging out](#logging-out)
# Apache Shiro Authentication
## Apache Shiro Authentication简介
Authentication是一个对用户进行身份认证的过程在认证过程中用户需要向应用提供用于证明用户的凭据。
## Apache Authentication概念
### subject
在应用的角度subject即是一个用户
### principals
主体用于标识一个用户可以是username、social security nubmer等
### credentials
凭据,在用户认证过程中用于认证用户的身份,可以是密码、生物识别数据(如指纹、面容等)
### realms
专用于security的dao对象用于和后端的datasource进行沟通。
## Shiro Authentication过程
### Shiro框架的Authentication过程
1. 收集用户的principals和credentials
2. 向应用的认证系统提交用户的principals和credentials
3. 认证结束之后,根据认证结果允许访问、重试访问请求或者阻塞访问
### 收集用户的principals和credentials
可以通过UsernamePasswordToken来存储用户提交的username和password并可以调用UsernamePasswordToken.rememberMe方法来启用Shiro的“remember-me”功能。
```java
//Example using most common scenario:
//String username and password. Acquire in
//system-specific manner (HTTP request, GUI, etc)
UsernamePasswordToken token = new UsernamePasswordToken( username, password );
//”Remember Me” built-in, just do this:
token.setRememberMe(true);
```
### 将收集的principals和credentials提交给认证系统
在收集完用户的principals和credentials之后需要将其提交给应用的认证系统。
在Shiro中代表认证系统的是Realm其从存放安全数据的datasource中获取数据并且对用户提交的principals和credentials进行校验。
在Shiro中该过程用如下代码表示
```java
//With most of Shiro, you'll always want to make sure you're working with the currently
//executing user, referred to as the subject
Subject currentUser = SecurityUtils.getSubject();
//Authenticate the subject by passing
//the user name and password token
//into the login method
currentUser.login(token);
```
> 在Shiro中subject可以被看做是用户**在当前执行的线程中永远有一个subject与其相关联。**
> **可以通过SecurityUtils.getSubject()方法来获取当前执行线程相关联的subject。**
> 在获取当前执行线程关联subject之后需要对当前subject进行身份认证通过subject.login(token)来对用户提交的principals和credentials进行Authentication
### 身份认证后对访问进行allow/retry authentication/block
在调用subject.login(token)之后如果身份认证成功用户将在seesion的生命周期内维持他们的identity。但是如果身份认证失败可以为抛出的异常指定不同的异常处理逻辑来定义登录失败之后的行为。
```java
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... your own ...
} catch ( AuthenticationException ae ) {
//unexpected error?
}
//No problems, show authenticated view…
```
## rememberMe支持
Apache Shiro除了正常的Authentication流程外还支持rememberMe功能。
Shiro中Subject对象拥有两个方法isRemembered()和isAuthenticated
> - 一个remembered subject其identity和principals自上次session成功认证后就被记住
> - 一个authenticated subject其identity只在本次会话中有效
### remembered和authenticated的区别
在Shiro中一个remembered subject并不代表该subject已经被authenticated。如果一个subject被remembered仅仅会向系统提示该subject可能是系统的某个用户但是不会对subject的身份提供保证。但是如果subject被authenticated该subject的identity在当前会话中已经被认证。
> 故而isRemembered校验可以用来执行一些非敏感的操作如用户自定义界面视图等。但是敏感性操作如金额信息和变动操作等必须通过isAuthenticated校验而不是isRemembered校验敏感性操作的用户身份必须得到认证。
## logging out
在Shiro中登出操作可以通过如下代码实现
```java
currentUser.logout(); //removes all identifying information and invalidates their session too.
```
当执行登出操作时Shiro会关闭当前session并且会移除当前subject的任何identity。如果在web环境中使用rememberMelogout默认会从浏览器中删除rememberMe cookie。

View File

@@ -1,94 +1,94 @@
- [Apache Shiro Authorization](#apache-shiro-authorization)
- [Authorization简介](#authorization简介)
- [Authorization的核心元素](#authorization的核心元素)
- [Permission](#permission)
- [权限粒度级别](#权限粒度级别)
- [Roles](#roles)
- [Role分类](#role分类)
- [User](#user)
- [在Apache Shiro中实行Authorization](#在apache-shiro中实行authorization)
- [通过java code实现authorization](#通过java-code实现authorization)
- [基于String的权限鉴定](#基于string的权限鉴定)
- [通过注解实现Authorization](#通过注解实现authorization)
# Apache Shiro Authorization
## Authorization简介
Authorization访问控制分配访问某资源的特定权限。
## Authorization的核心元素
### Permission
Permission是最原子级别的安全策略用来控制用户与应用进行交互时可以执行哪些操作。**格式良好的permission描述了资源的类型和与该资源交互时可以执行的操作。**
对于与数据相关的资源权限通常有create、read、update、deleteCRUD
#### 权限粒度级别
在Shiro中可以在任何粒度对permission进行定义。如下是permission粒度的一些定义
1. Resource级别该级别是最广泛和最容易构建的粒度级别在该级别用户可以对资源执行特定的操作。**在Resource级别该资源类型被指定但是没有限制用户操作特定的资源实例即用户可以对该Resource类型的所有实例进行操作**
2. Instance级别该级别限定了Permission可以操作的Resource Instance在该级别用户只能够对特定的Resource实例进行操作。
3. Attribute级别该级别比限定了Permission可以操作Resouce类型或Resource实例的某个属性
### Roles
Roles是一个Permission的集合用于简化权限和用户管理过程。用户可以被授予特定的角色来获得操作某些资源的权限。
#### Role分类
1. Role不实际关联具体的Permission当你具有banker的角色时其角色隐含你可以对账户进行操作的权限当你具有waiter的角色时默认可以对厨房的door进行open/close操作
2. Role实际关联具体的Permission在该情况下Role即为一系列Permission的集合你可以对银行账号进行create、delete操作因为操作银行账号是你已分配的admin角色的一个下属权限
### User
在Shiro中User即是一个Subject实例。在Shiro中Subject可以是任何与系统进行交互的主体可以是浏览器、客户端、crond定时任务等。
## 在Apache Shiro中实行Authorization
在Apache Shiro中Authorization可以通过如下方式执行
1. 通过代码实现即在java程序中通过代码实现访问控制
2. jdk注解可以在你的方法上加上authorization注解
3. jsp/gsp taglibs
### 通过java code实现authorization
可以通过如下代码进行角色鉴定:
```java
//get the current Subject
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("administrator")) {
//show a special button
} else {
//dont show the button?)
}
```
可以通过如下代码实现对权限的鉴定操作:
```java
Subject currentUser = SecurityUtils.getSubject();
Permission printPermission = new PrinterPermission("laserjet3000n","print");
If (currentUser.isPermitted(printPermission)) {
//do one thing (show the print button?)
} else {
//dont show the button?
}
```
#### 基于String的权限鉴定
如果不想构造Permission对象可以通过构造一个字符串来代表权限。该字符串可以是任何格式只要你的Realm能够识别该格式并且与权限进行交互。
```java
String perm = "printer:print:laserjet4400n";
if(currentUser.isPermitted(perm)){
//show the print button?
} else {
//dont show the button?
}
```
### 通过注解实现Authorization
可以通过java注解来实现Authorization过程**在使用注解之前必须先开启aop**。
如果在执行openAccount之前当前Subject必须拥有account:create权限那么可以通过如下方式来实现权限鉴定。如果当前用户未被直接授予或通过role间接授予该权限那么会抛出AuthorizationException异常。
```java
//Will throw an AuthorizationException if none
//of the callers roles imply the Account
//'create' permission
@RequiresPermissions("account:create")
public void openAccount( Account acct ) {
//create the account
}
```
如果要在执行方法之前进行角色校验,可以通过如下方式加上注解达到预期功能。
```java
//Throws an AuthorizationException if the caller
//doesnt have the teller role:
@RequiresRoles( "teller" )
public void openAccount( Account acct ) {
//do something in here that only a teller
//should do
}
- [Apache Shiro Authorization](#apache-shiro-authorization)
- [Authorization简介](#authorization简介)
- [Authorization的核心元素](#authorization的核心元素)
- [Permission](#permission)
- [权限粒度级别](#权限粒度级别)
- [Roles](#roles)
- [Role分类](#role分类)
- [User](#user)
- [在Apache Shiro中实行Authorization](#在apache-shiro中实行authorization)
- [通过java code实现authorization](#通过java-code实现authorization)
- [基于String的权限鉴定](#基于string的权限鉴定)
- [通过注解实现Authorization](#通过注解实现authorization)
# Apache Shiro Authorization
## Authorization简介
Authorization访问控制分配访问某资源的特定权限。
## Authorization的核心元素
### Permission
Permission是最原子级别的安全策略用来控制用户与应用进行交互时可以执行哪些操作。**格式良好的permission描述了资源的类型和与该资源交互时可以执行的操作。**
对于与数据相关的资源权限通常有create、read、update、deleteCRUD
#### 权限粒度级别
在Shiro中可以在任何粒度对permission进行定义。如下是permission粒度的一些定义
1. Resource级别该级别是最广泛和最容易构建的粒度级别在该级别用户可以对资源执行特定的操作。**在Resource级别该资源类型被指定但是没有限制用户操作特定的资源实例即用户可以对该Resource类型的所有实例进行操作**
2. Instance级别该级别限定了Permission可以操作的Resource Instance在该级别用户只能够对特定的Resource实例进行操作。
3. Attribute级别该级别比限定了Permission可以操作Resouce类型或Resource实例的某个属性
### Roles
Roles是一个Permission的集合用于简化权限和用户管理过程。用户可以被授予特定的角色来获得操作某些资源的权限。
#### Role分类
1. Role不实际关联具体的Permission当你具有banker的角色时其角色隐含你可以对账户进行操作的权限当你具有waiter的角色时默认可以对厨房的door进行open/close操作
2. Role实际关联具体的Permission在该情况下Role即为一系列Permission的集合你可以对银行账号进行create、delete操作因为操作银行账号是你已分配的admin角色的一个下属权限
### User
在Shiro中User即是一个Subject实例。在Shiro中Subject可以是任何与系统进行交互的主体可以是浏览器、客户端、crond定时任务等。
## 在Apache Shiro中实行Authorization
在Apache Shiro中Authorization可以通过如下方式执行
1. 通过代码实现即在java程序中通过代码实现访问控制
2. jdk注解可以在你的方法上加上authorization注解
3. jsp/gsp taglibs
### 通过java code实现authorization
可以通过如下代码进行角色鉴定:
```java
//get the current Subject
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("administrator")) {
//show a special button
} else {
//dont show the button?)
}
```
可以通过如下代码实现对权限的鉴定操作:
```java
Subject currentUser = SecurityUtils.getSubject();
Permission printPermission = new PrinterPermission("laserjet3000n","print");
If (currentUser.isPermitted(printPermission)) {
//do one thing (show the print button?)
} else {
//dont show the button?
}
```
#### 基于String的权限鉴定
如果不想构造Permission对象可以通过构造一个字符串来代表权限。该字符串可以是任何格式只要你的Realm能够识别该格式并且与权限进行交互。
```java
String perm = "printer:print:laserjet4400n";
if(currentUser.isPermitted(perm)){
//show the print button?
} else {
//dont show the button?
}
```
### 通过注解实现Authorization
可以通过java注解来实现Authorization过程**在使用注解之前必须先开启aop**。
如果在执行openAccount之前当前Subject必须拥有account:create权限那么可以通过如下方式来实现权限鉴定。如果当前用户未被直接授予或通过role间接授予该权限那么会抛出AuthorizationException异常。
```java
//Will throw an AuthorizationException if none
//of the callers roles imply the Account
//'create' permission
@RequiresPermissions("account:create")
public void openAccount( Account acct ) {
//create the account
}
```
如果要在执行方法之前进行角色校验,可以通过如下方式加上注解达到预期功能。
```java
//Throws an AuthorizationException if the caller
//doesnt have the teller role:
@RequiresRoles( "teller" )
public void openAccount( Account acct ) {
//do something in here that only a teller
//should do
}
```

View File

@@ -1,92 +1,92 @@
- [Apache Shiro Quick Start](#apache-shiro-quick-start)
- [Apache Shiro常用API](#apache-shiro常用api)
- [获取当前用户](#获取当前用户)
- [设置用户Session](#设置用户session)
- [通过用户名和密码对用户进行身份认证](#通过用户名和密码对用户进行身份认证)
- [对身份认证失败的情况进行异常处理](#对身份认证失败的情况进行异常处理)
- [对已经登录的用户进行role检验](#对已经登录的用户进行role检验)
- [检测某用户是否具有某项特定权限](#检测某用户是否具有某项特定权限)
- [在实例级别对用户的权限进行检测](#在实例级别对用户的权限进行检测)
- [用户登出](#用户登出)
# Apache Shiro Quick Start
## Apache Shiro常用API
### 获取当前用户
在任何环境中,都可以通过如下代码来获取当前执行的用户:
```java
Subject currentUser = SecurityUtils.getSubject();
```
### 设置用户Session
可以通过如下代码获取用户的Shiro Session并可以向Session中设置属性和值设置的值在用户会话期间内都可以使用。
**Shiro Session在使用时并不要求当前位于HTTP环境下**
```java
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
```
> 如果当前应用部署于Web环境下那么Shiro Session默认会使用HttpSession但是如果当前应用部署在非Web环境下时Shiro Session会使用其Enterprise Session Management。
### 通过用户名和密码对用户进行身份认证
通过如下代码可以通过UsernamePasswordToken来对未认证的用户进行身份认证。
```java
if ( !currentUser.isAuthenticated() ) {
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
//(do you know what movie this is from? ;)
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
currentUser.login(token);
}
```
### 对身份认证失败的情况进行异常处理
如果在身份认证的过程中失败,可以通过如下代码捕获认证失败抛出的异常,并对异常进行异常处理
```java
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
```
### 对已经登录的用户进行role检验
如果用户已经登录如果要检测该用户是否被授予某role权限可以通过如下代码进行检验
```java
if ( currentUser.hasRole( "schwartz" ) ) {
log.info("May the Schwartz be with you!" );
} else {
log.info( "Hello, mere mortal." );
}
```
### 检测某用户是否具有某项特定权限
如果要对已经登录的用户执行检测,检测其是否被授予某项特定的前线,可以通过如下方式进行检测。
```java
if ( currentUser.isPermitted( "lightsaber:wield" ) ) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
```
### 在实例级别对用户的权限进行检测
在Shiro中可以检测用户是否对某实例具有特定权限通过如下代码
```java
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
```
### 用户登出
如果已经登录的用户想要执行登出操作,可以通过如下代码进行登录:
```java
currentUser.logout();
- [Apache Shiro Quick Start](#apache-shiro-quick-start)
- [Apache Shiro常用API](#apache-shiro常用api)
- [获取当前用户](#获取当前用户)
- [设置用户Session](#设置用户session)
- [通过用户名和密码对用户进行身份认证](#通过用户名和密码对用户进行身份认证)
- [对身份认证失败的情况进行异常处理](#对身份认证失败的情况进行异常处理)
- [对已经登录的用户进行role检验](#对已经登录的用户进行role检验)
- [检测某用户是否具有某项特定权限](#检测某用户是否具有某项特定权限)
- [在实例级别对用户的权限进行检测](#在实例级别对用户的权限进行检测)
- [用户登出](#用户登出)
# Apache Shiro Quick Start
## Apache Shiro常用API
### 获取当前用户
在任何环境中,都可以通过如下代码来获取当前执行的用户:
```java
Subject currentUser = SecurityUtils.getSubject();
```
### 设置用户Session
可以通过如下代码获取用户的Shiro Session并可以向Session中设置属性和值设置的值在用户会话期间内都可以使用。
**Shiro Session在使用时并不要求当前位于HTTP环境下**
```java
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
```
> 如果当前应用部署于Web环境下那么Shiro Session默认会使用HttpSession但是如果当前应用部署在非Web环境下时Shiro Session会使用其Enterprise Session Management。
### 通过用户名和密码对用户进行身份认证
通过如下代码可以通过UsernamePasswordToken来对未认证的用户进行身份认证。
```java
if ( !currentUser.isAuthenticated() ) {
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
//(do you know what movie this is from? ;)
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
currentUser.login(token);
}
```
### 对身份认证失败的情况进行异常处理
如果在身份认证的过程中失败,可以通过如下代码捕获认证失败抛出的异常,并对异常进行异常处理
```java
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
```
### 对已经登录的用户进行role检验
如果用户已经登录如果要检测该用户是否被授予某role权限可以通过如下代码进行检验
```java
if ( currentUser.hasRole( "schwartz" ) ) {
log.info("May the Schwartz be with you!" );
} else {
log.info( "Hello, mere mortal." );
}
```
### 检测某用户是否具有某项特定权限
如果要对已经登录的用户执行检测,检测其是否被授予某项特定的前线,可以通过如下方式进行检测。
```java
if ( currentUser.isPermitted( "lightsaber:wield" ) ) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
```
### 在实例级别对用户的权限进行检测
在Shiro中可以检测用户是否对某实例具有特定权限通过如下代码
```java
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
```
### 用户登出
如果已经登录的用户想要执行登出操作,可以通过如下代码进行登录:
```java
currentUser.logout();
```

View File

@@ -1,133 +1,133 @@
- [Apache Shiro Realm](#apache-shiro-realm)
- [Realm简介](#realm简介)
- [Realm配置](#realm配置)
- [Realm Authentication](#realm-authentication)
- [支持Authentication](#支持authentication)
- [处理AuthenticationToken](#处理authenticationtoken)
- [credentials匹配](#credentials匹配)
- [简单比较是否相等](#简单比较是否相等)
- [Hash Credentials](#hash-credentials)
- [通过sha-256算法来生成账户信息](#通过sha-256算法来生成账户信息)
- [指定HashedCredentialsMatcher](#指定hashedcredentialsmatcher)
- [SaltedAuthenticationInfo](#saltedauthenticationinfo)
- [关闭Realm的Authentication](#关闭realm的authentication)
- [Realm Authorization](#realm-authorization)
- [基于role的authorization](#基于role的authorization)
- [基于permission的authorization](#基于permission的authorization)
# Apache Shiro Realm
## Realm简介
Realm是一个组件用来访问针对特定应用的安全数据例如user、role或permission。Realm负责将这些安全信息翻译为Shiro能够理解的格式。
由于大多数数据源都会同时存储authentication和authorization信息故而Realm能够同时执行authentication和authorization操作。
## Realm配置
对于Realm的配置可以在ini文件中进行如下配置
```ini
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm
; 如下指定的顺序会影响Authentication/Authorization过程中的顺序
securityManager.realms = $fooRealm, $barRealm, $bazRealm
```
## Realm Authentication
### 支持Authentication
在Realm被询问去执行Authentication时首先会调用该Realm的supports方法如果supports方法的返回值是true时getAuthenticationInfo方法才会被调用。
通常情况下Realm会对提交的Token类型进行检测并查看当前Realm是否能对该类型Token进行处理。
### 处理AuthenticationToken
如果Realm支持该提交的Token类型那么Authenticator会调用Realm的getAuthenticationInfo方法该方法代表了通过Realm数据库来进行认证尝试。
该方法会按照如下顺序进行执行:
1. 查看Token中存储的principals信息
2. 根据Token中的principals在data source中查找对应的账户信息
3. 确保提交Token中的credentials和data source中查找出的credentials相匹配
4. 如果credentials匹配那么会将用户账户的信息封装到AuthenticationInfo中并返回
5. 如果credentials不匹配会抛出AuthenticationException
### credentials匹配
为了确保在credentials匹配的过程中该过程是可插入pluggable和可自定义的customizableAuthenticationRealm支持CredentialsMatcher的概念通过CredentialsMatcher来进行credentials的比较。
在从data source中查询到账户数据之后会将其和提交Token中的credentials一起传递给CredentialsMatcher由CredentialsMatcher来判断credentials是否相等。
可以通过如下方式来定义CredentialsMatcher的比较逻辑
```java
Realm myRealm = new com.company.shiro.realm.MyRealm();
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
myRealm.setCredentialsMatcher(customMatcher);
```
```ini
[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...
```
### 简单比较是否相等
Shiro中所有开箱即用的Realm其实现都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。
### Hash Credentials
将用户提交的principals不做任何转换直接存储在data source中是一种不安全的做法通常是将其进行单向hash之后再存入数据库。
这样可以确保用户的credentials不会以raw text的方式存储再data source中即使数据库数据被泄露用户credentials的原始值也不会被任何人知道。
为了支持Token中credentials和data source中hash之后credentials的比较Shiro提供了HashedCredentialsMatcher实现可以通过配置HashedCredentialsMatcher来取代SimpleCredentialsMatcher。
#### 通过sha-256算法来生成账户信息
```java
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
...
//We'll use a Random Number Generator to generate salts. This
//is much more secure than using a username as a salt or not
//having a salt at all. Shiro makes this easy.
//
//Note that a normal app would reference an attribute rather
//than create a new RNG every time:
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
//Now hash the plain-text password with the random salt and multiple
//iterations and then Base64-encode the value (requires less space than Hex):
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();
User user = new User(username, hashedPasswordBase64);
//save the salt with the new account. The HashedCredentialsMatcher
//will need it later when handling login attempts:
user.setPasswordSalt(salt);
userDAO.create(user);
```
#### 指定HashedCredentialsMatcher
可以通过如下方式来指定特定HashedCredentialsMatcher实现类。
```ini
[main]
...
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# This next property is only needed in Shiro 1.0\. Remove it in 1.1 and later:
credentialsMatcher.hashSalted = true
...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...
```
#### SaltedAuthenticationInfo
如果制定了HashedCredentialsMatcher那么Realm.getAuthenticationInfo必须返回一个SaltedAuthenticationInfo实例而不是普通的Authentication实例。该SaltedAuthenticationInfo确保在创建用户信息时使用的salt可以在CredentialsMatcher中被使用。
HashedCredentialsMatcher在对Token中提交的credentials进行hash时需要使用到salt值来将用户提交的credentials进行和创建用户时相同的散列。
### 关闭Realm的Authentication
如果对某个Realm想要对该realm不执行Authentication可以将其实现类的supports方法只返回false此时该realm在authentication过程中绝对不会被询问。
## Realm Authorization
SecurityManager将校验permission和role的工作委托给了Authorizer默认是ModularRealmAuthorizer。
### 基于role的authorization
当subject的hasRoles或checkRoles被调用其具体的执行流程如下
1. subject将校验role的任务委托给SecurityManager
2. SecurityManager将任务委托给Authorizer
3. Authorizier会调用所有的Authorizing Realm直到该role被分配给subject。如果所有realm都没有授予subject该role那么访问失败返回false。
4. Authorizing Realm的AuthorizationInfo.getRoles方法会获取所有分配给该subject的role
5. 如果待检测的role在getRoles返回的role list中那么授权成功subject可以对该资源进行访问
### 基于permission的authorization
当subject的isPermitted或checkPermission方法被调用时其执行流程如下
1. subject将检测Permission的任务委托给SecurityManager
2. SecurityManager将该任务委托给Authorizer
3. Authorizer会以此访问所有的Authorizer Realm直到Permission被授予。如果所有的realm都没有授予该subject权限那么subject授权失败。
4. Realm按照如下顺序来检测Permission是否被授予
1. 其会调用AuthorizationInfo的getObjectPermissions方法和getStringPermissions方法并聚合结果从而获取直接分配给该subject的所有权限
2. 如果RolePermissionRegister被注册那么会根据subject被授予的role来获取role相关的permission根据RolePermissionResolver.resolvePermissionsInRole()方法
3. 对于上述返回的权限集合implies方法会被调用用来检测待检测权限是否隐含在其中
- [Apache Shiro Realm](#apache-shiro-realm)
- [Realm简介](#realm简介)
- [Realm配置](#realm配置)
- [Realm Authentication](#realm-authentication)
- [支持Authentication](#支持authentication)
- [处理AuthenticationToken](#处理authenticationtoken)
- [credentials匹配](#credentials匹配)
- [简单比较是否相等](#简单比较是否相等)
- [Hash Credentials](#hash-credentials)
- [通过sha-256算法来生成账户信息](#通过sha-256算法来生成账户信息)
- [指定HashedCredentialsMatcher](#指定hashedcredentialsmatcher)
- [SaltedAuthenticationInfo](#saltedauthenticationinfo)
- [关闭Realm的Authentication](#关闭realm的authentication)
- [Realm Authorization](#realm-authorization)
- [基于role的authorization](#基于role的authorization)
- [基于permission的authorization](#基于permission的authorization)
# Apache Shiro Realm
## Realm简介
Realm是一个组件用来访问针对特定应用的安全数据例如user、role或permission。Realm负责将这些安全信息翻译为Shiro能够理解的格式。
由于大多数数据源都会同时存储authentication和authorization信息故而Realm能够同时执行authentication和authorization操作。
## Realm配置
对于Realm的配置可以在ini文件中进行如下配置
```ini
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm
; 如下指定的顺序会影响Authentication/Authorization过程中的顺序
securityManager.realms = $fooRealm, $barRealm, $bazRealm
```
## Realm Authentication
### 支持Authentication
在Realm被询问去执行Authentication时首先会调用该Realm的supports方法如果supports方法的返回值是true时getAuthenticationInfo方法才会被调用。
通常情况下Realm会对提交的Token类型进行检测并查看当前Realm是否能对该类型Token进行处理。
### 处理AuthenticationToken
如果Realm支持该提交的Token类型那么Authenticator会调用Realm的getAuthenticationInfo方法该方法代表了通过Realm数据库来进行认证尝试。
该方法会按照如下顺序进行执行:
1. 查看Token中存储的principals信息
2. 根据Token中的principals在data source中查找对应的账户信息
3. 确保提交Token中的credentials和data source中查找出的credentials相匹配
4. 如果credentials匹配那么会将用户账户的信息封装到AuthenticationInfo中并返回
5. 如果credentials不匹配会抛出AuthenticationException
### credentials匹配
为了确保在credentials匹配的过程中该过程是可插入pluggable和可自定义的customizableAuthenticationRealm支持CredentialsMatcher的概念通过CredentialsMatcher来进行credentials的比较。
在从data source中查询到账户数据之后会将其和提交Token中的credentials一起传递给CredentialsMatcher由CredentialsMatcher来判断credentials是否相等。
可以通过如下方式来定义CredentialsMatcher的比较逻辑
```java
Realm myRealm = new com.company.shiro.realm.MyRealm();
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
myRealm.setCredentialsMatcher(customMatcher);
```
```ini
[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...
```
### 简单比较是否相等
Shiro中所有开箱即用的Realm其实现都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。
### Hash Credentials
将用户提交的principals不做任何转换直接存储在data source中是一种不安全的做法通常是将其进行单向hash之后再存入数据库。
这样可以确保用户的credentials不会以raw text的方式存储再data source中即使数据库数据被泄露用户credentials的原始值也不会被任何人知道。
为了支持Token中credentials和data source中hash之后credentials的比较Shiro提供了HashedCredentialsMatcher实现可以通过配置HashedCredentialsMatcher来取代SimpleCredentialsMatcher。
#### 通过sha-256算法来生成账户信息
```java
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
...
//We'll use a Random Number Generator to generate salts. This
//is much more secure than using a username as a salt or not
//having a salt at all. Shiro makes this easy.
//
//Note that a normal app would reference an attribute rather
//than create a new RNG every time:
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
//Now hash the plain-text password with the random salt and multiple
//iterations and then Base64-encode the value (requires less space than Hex):
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();
User user = new User(username, hashedPasswordBase64);
//save the salt with the new account. The HashedCredentialsMatcher
//will need it later when handling login attempts:
user.setPasswordSalt(salt);
userDAO.create(user);
```
#### 指定HashedCredentialsMatcher
可以通过如下方式来指定特定HashedCredentialsMatcher实现类。
```ini
[main]
...
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# This next property is only needed in Shiro 1.0\. Remove it in 1.1 and later:
credentialsMatcher.hashSalted = true
...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...
```
#### SaltedAuthenticationInfo
如果制定了HashedCredentialsMatcher那么Realm.getAuthenticationInfo必须返回一个SaltedAuthenticationInfo实例而不是普通的Authentication实例。该SaltedAuthenticationInfo确保在创建用户信息时使用的salt可以在CredentialsMatcher中被使用。
HashedCredentialsMatcher在对Token中提交的credentials进行hash时需要使用到salt值来将用户提交的credentials进行和创建用户时相同的散列。
### 关闭Realm的Authentication
如果对某个Realm想要对该realm不执行Authentication可以将其实现类的supports方法只返回false此时该realm在authentication过程中绝对不会被询问。
## Realm Authorization
SecurityManager将校验permission和role的工作委托给了Authorizer默认是ModularRealmAuthorizer。
### 基于role的authorization
当subject的hasRoles或checkRoles被调用其具体的执行流程如下
1. subject将校验role的任务委托给SecurityManager
2. SecurityManager将任务委托给Authorizer
3. Authorizier会调用所有的Authorizing Realm直到该role被分配给subject。如果所有realm都没有授予subject该role那么访问失败返回false。
4. Authorizing Realm的AuthorizationInfo.getRoles方法会获取所有分配给该subject的role
5. 如果待检测的role在getRoles返回的role list中那么授权成功subject可以对该资源进行访问
### 基于permission的authorization
当subject的isPermitted或checkPermission方法被调用时其执行流程如下
1. subject将检测Permission的任务委托给SecurityManager
2. SecurityManager将该任务委托给Authorizer
3. Authorizer会以此访问所有的Authorizer Realm直到Permission被授予。如果所有的realm都没有授予该subject权限那么subject授权失败。
4. Realm按照如下顺序来检测Permission是否被授予
1. 其会调用AuthorizationInfo的getObjectPermissions方法和getStringPermissions方法并聚合结果从而获取直接分配给该subject的所有权限
2. 如果RolePermissionRegister被注册那么会根据subject被授予的role来获取role相关的permission根据RolePermissionResolver.resolvePermissionsInRole()方法
3. 对于上述返回的权限集合implies方法会被调用用来检测待检测权限是否隐含在其中

View File

@@ -1,175 +1,175 @@
- [Apache Shiro](#apache-shiro)
- [Shiro简介](#shiro简介)
- [Shiro中常用的概念](#shiro中常用的概念)
- [Subject](#subject)
- [SecurityManager](#securitymanager)
- [realms](#realms)
- [Authentication](#authentication)
- [Authorization](#authorization)
- [Session Management](#session-management)
- [Shiro Session可在任何应用中使用](#shiro-session可在任何应用中使用)
- [Shiro加密](#shiro加密)
- [shiro hash](#shiro-hash)
- [Shiro Ciphers](#shiro-ciphers)
- [Shiro框架的Web支持](#shiro框架的web支持)
- [Web Session管理](#web-session管理)
- [Shiro Native Session](#shiro-native-session)
# Apache Shiro
## Shiro简介
Shiro是一个简单易用且功能强大的Java安全框架用于实现认证、授权、加密、session管理等场景并且Shiro可以被用于任何应用包括命令行应用、移动应用、大型web应用或是企业应用。
Shiro在如下方面提供Security API
- Authentication为用户提供身份认证
- Authorization为应用提供用户的访问控制
- 加密:避免应用数据处于明文状态
- session管理每个用户的时间敏感状态
## Shiro中常用的概念
Shiro框架的结构主要分为三个部分Subject、SecurityManager、Realms
### Subject
SubjectSubject是一个安全术语通常意味着当前执行的用户。
```java
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
// 获取Subject对象
Subject currentUser = SecurityUtils.getSubject();
```
### SecurityManager
相对于Subject代表对于当前用户的安全操作SecurityManager代表对所有用户的安全操作。SecurityManager是Shiro结构的核心其由多个security component嵌套组成。
> 一旦SecurityManager和其嵌套的security component被配置完成那么用户将不再使用SecurityManager而是调用Subject API。
对每个应用中只存在一个SecurityManagerSecurityManager是应用范围内的单例。默认SecurityManager的实现是POJO可以通过java代码、Spring xml、yaml、properties等方式来进行配置。
### realms
realms是shiro和应用中security data如账户登录的登录数据或授权管理的权限数据之间的连接器。当Shiro和security data进行交互时shiro会从配置好的一个或者多个realm中获取security data。
> 在上述情况下realm类似于一个安全数据的特定DAO其封装了到数据源连接的详细信息并且为Shiro提供安全数据。当配置Shiro时必须指定至少一个Realm用于身份认证和权限认证。
> Shiro提供了开箱即用的Realm来连接很多种security data source比如LDAP关系型数据库JDBC基于文本配置的data source例如ini或properties文件。可以通过自定义Realm的实现来访问自定义的data source如果默认的Realm不能满足需求。
## Authentication
认证过程用于认证用户的身份。认证过程的主要流程如下:
1. 收集用户的身份标识信息通常称之为主体principal和身份证明通常称之为凭据credentials
2. 向系统提交主体和凭据
3. 如果提交的凭据和系统中该主体对应的凭据相同,那么该用户会被认为是“通过认证的”。如果提交的凭据不符,那么该用户会被认为是“认证失败的”。
```java
// 认证流程代码
//1. Acquire submitted principals and credentials:
AuthenticationToken token =
new UsernamePasswordToken(username, password);
//2. Get the current Subject:
Subject currentUser = SecurityUtils.getSubject();
//3. Login:
currentUser.login(token);
```
当login方法被调用时SecurityMananger将会收到AuthenticationToken并且将其分配到一个或多个已经配置好的Realm中并且让每个Realm执行需要的认证流程。每个Reaml都能对提交的AuthenticaitonToken做出处理。
如果认证过程失败那么会抛出AuthenticationException可以通过捕获该异常来对失败的认证进行处理。
```java
//3. Login:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
} catch (LockedAccountException lae) {
}
catch (AuthenticationException ae) {
}
```
**当用户通过身份认证之后,其被认为是“通过身份认证的”,并且被允许使用应用。但是,通过身份认证并不意味着可以在系统中执行任何操作。通过授权,可以决定用户能够在系统中执行哪些操作。**
## Authorization
Authorization的实质是访问控制通常用于控制用户在系统中能够使用哪些资源。大多数情况下可以通过role或permission等形式来实现访问控制用户通过分配给他们的角色或者权限来执行操作。通过检查用户的role或者permission系统可以决定将哪些功能暴露给用户。
```java
// 通过如下代码Subject可以实现对用户的role检测
if ( subject.hasRole(administrator) ) {
//show the Create User button
} else {
//grey-out the button?
}
// 通过如下代码,可以实现权限分配而不是角色检测
if ( subject.isPermitted(user:create) ) {
//show the Create User button
} else {
//grey-out the button?
}
```
权限控制甚至支持非常细粒度的权限,譬如实例级别的权限控制。
```java
// 如下代码检测用户是否拥有删除jsmith用户的权限
if ( subject.isPermitted(user:delete:jsmith) ) {
//delete the jsmith user
} else {
//dont delete jsmith
}
```
和Authentication类似Authorization也会进入SecurityManager并且通过一个或者多个Realm来决定是否允许访问。
**根据需要Realm既会响应Authentication过程也会响应Authority过程**
## Session Management
Apache提供了一致的会话管理在任何类型和架构的程序中都可以使用该Session API从小型的守护进程到大型集群web应用都可以使用。
并且Shiro提供的Session API是容器无关的在任何容器环境下Session API都相同。Shiro结构也支持可插入的session存储可以将session存储在企业缓存、关系型数据库或者nosql中。
Shiro Seesion的另一个好处是Shiro Session可以在不同技术实现的客户端之间进行共享譬如Swing桌面客户端可以和web客户端一起加入同一个应用会话用户同时使用swing客户端和web客户端时很有用
```java
/**
* 用户获取Session
*/
// 如果当前用户存在会话,则获取已存在会话,
// 如果当前用户不存在会话,则创建新的会话
Session session = subject.getSession();
// 接受一个boolean值该boolean值标识如果当前用户不存在会话是否创建一个新的会话
Session session = subject.getSession(boolean create);
```
### Shiro Session可在任何应用中使用
```java
Session session = subject.getSession();
session.getAttribute(key, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);
```
## Shiro加密
Shiro加密是独立于用户Subject故而Shiro加密可以独立于Subject使用。
Shiro加密主要关注于如下两个模块
- hash加密又名消息摘要message digest
- ciphers加密
### shiro hash
在Shiro hash中如果想要快速计算文件的MD5值可以通过如下方式快速计算
```java
String hex = new Md5Hash(myFile).toHex();
```
在shiro hash中如果想要对密码进行sha-512进行hash操作并且对结果进行base64编码成可打印字符串可以进行如下操作
```java
String encodedPassword =
new Sha512Hash(password, salt, count).toBase64();
```
Shiro框架在很大程度上简化了hash和编码。
### Shiro Ciphers
Ciphers是一种算法可以通过指定的key将加密后的数据还原到加密之前不同于hash操作hash操作通常是不可逆的。通常用Ciphers来保证数据传输时的安全防止数据在传输时被窥探。
Shiro通过引入CipherService API来简化加密的流程**CipherService是一个简单无状态并且线程安全的API可以对应用数据进行加密或者解密操作。**
```java
// Shiro CipherService对数据进行加密操作
AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);
//create a test key:
byte[] testKey = cipherService.generateNewKey();
//encrypt a files bytes:
byte[] encrypted =
cipherService.encrypt(fileBytes, testKey);
```
## Shiro框架的Web支持
### Web 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 Session时其Session是独立与环境的Web应用和非Web应用都可以通过相同的Shiro Session API与Shiro Session进行交互而Shiro Session是否是基于Servlet容器的HttpSession用户是无感知的。
### Shiro Native Session
当启用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来代理这些请求。
> **故而当想要在Web环境中使用Shiro Session API时无需变动Web环境先前未使用Shiro Session API时的任何代码。**
- [Apache Shiro](#apache-shiro)
- [Shiro简介](#shiro简介)
- [Shiro中常用的概念](#shiro中常用的概念)
- [Subject](#subject)
- [SecurityManager](#securitymanager)
- [realms](#realms)
- [Authentication](#authentication)
- [Authorization](#authorization)
- [Session Management](#session-management)
- [Shiro Session可在任何应用中使用](#shiro-session可在任何应用中使用)
- [Shiro加密](#shiro加密)
- [shiro hash](#shiro-hash)
- [Shiro Ciphers](#shiro-ciphers)
- [Shiro框架的Web支持](#shiro框架的web支持)
- [Web Session管理](#web-session管理)
- [Shiro Native Session](#shiro-native-session)
# Apache Shiro
## Shiro简介
Shiro是一个简单易用且功能强大的Java安全框架用于实现认证、授权、加密、session管理等场景并且Shiro可以被用于任何应用包括命令行应用、移动应用、大型web应用或是企业应用。
Shiro在如下方面提供Security API
- Authentication为用户提供身份认证
- Authorization为应用提供用户的访问控制
- 加密:避免应用数据处于明文状态
- session管理每个用户的时间敏感状态
## Shiro中常用的概念
Shiro框架的结构主要分为三个部分Subject、SecurityManager、Realms
### Subject
SubjectSubject是一个安全术语通常意味着当前执行的用户。
```java
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
// 获取Subject对象
Subject currentUser = SecurityUtils.getSubject();
```
### SecurityManager
相对于Subject代表对于当前用户的安全操作SecurityManager代表对所有用户的安全操作。SecurityManager是Shiro结构的核心其由多个security component嵌套组成。
> 一旦SecurityManager和其嵌套的security component被配置完成那么用户将不再使用SecurityManager而是调用Subject API。
对每个应用中只存在一个SecurityManagerSecurityManager是应用范围内的单例。默认SecurityManager的实现是POJO可以通过java代码、Spring xml、yaml、properties等方式来进行配置。
### realms
realms是shiro和应用中security data如账户登录的登录数据或授权管理的权限数据之间的连接器。当Shiro和security data进行交互时shiro会从配置好的一个或者多个realm中获取security data。
> 在上述情况下realm类似于一个安全数据的特定DAO其封装了到数据源连接的详细信息并且为Shiro提供安全数据。当配置Shiro时必须指定至少一个Realm用于身份认证和权限认证。
> Shiro提供了开箱即用的Realm来连接很多种security data source比如LDAP关系型数据库JDBC基于文本配置的data source例如ini或properties文件。可以通过自定义Realm的实现来访问自定义的data source如果默认的Realm不能满足需求。
## Authentication
认证过程用于认证用户的身份。认证过程的主要流程如下:
1. 收集用户的身份标识信息通常称之为主体principal和身份证明通常称之为凭据credentials
2. 向系统提交主体和凭据
3. 如果提交的凭据和系统中该主体对应的凭据相同,那么该用户会被认为是“通过认证的”。如果提交的凭据不符,那么该用户会被认为是“认证失败的”。
```java
// 认证流程代码
//1. Acquire submitted principals and credentials:
AuthenticationToken token =
new UsernamePasswordToken(username, password);
//2. Get the current Subject:
Subject currentUser = SecurityUtils.getSubject();
//3. Login:
currentUser.login(token);
```
当login方法被调用时SecurityMananger将会收到AuthenticationToken并且将其分配到一个或多个已经配置好的Realm中并且让每个Realm执行需要的认证流程。每个Reaml都能对提交的AuthenticaitonToken做出处理。
如果认证过程失败那么会抛出AuthenticationException可以通过捕获该异常来对失败的认证进行处理。
```java
//3. Login:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
} catch (LockedAccountException lae) {
}
catch (AuthenticationException ae) {
}
```
**当用户通过身份认证之后,其被认为是“通过身份认证的”,并且被允许使用应用。但是,通过身份认证并不意味着可以在系统中执行任何操作。通过授权,可以决定用户能够在系统中执行哪些操作。**
## Authorization
Authorization的实质是访问控制通常用于控制用户在系统中能够使用哪些资源。大多数情况下可以通过role或permission等形式来实现访问控制用户通过分配给他们的角色或者权限来执行操作。通过检查用户的role或者permission系统可以决定将哪些功能暴露给用户。
```java
// 通过如下代码Subject可以实现对用户的role检测
if ( subject.hasRole(administrator) ) {
//show the Create User button
} else {
//grey-out the button?
}
// 通过如下代码,可以实现权限分配而不是角色检测
if ( subject.isPermitted(user:create) ) {
//show the Create User button
} else {
//grey-out the button?
}
```
权限控制甚至支持非常细粒度的权限,譬如实例级别的权限控制。
```java
// 如下代码检测用户是否拥有删除jsmith用户的权限
if ( subject.isPermitted(user:delete:jsmith) ) {
//delete the jsmith user
} else {
//dont delete jsmith
}
```
和Authentication类似Authorization也会进入SecurityManager并且通过一个或者多个Realm来决定是否允许访问。
**根据需要Realm既会响应Authentication过程也会响应Authority过程**
## Session Management
Apache提供了一致的会话管理在任何类型和架构的程序中都可以使用该Session API从小型的守护进程到大型集群web应用都可以使用。
并且Shiro提供的Session API是容器无关的在任何容器环境下Session API都相同。Shiro结构也支持可插入的session存储可以将session存储在企业缓存、关系型数据库或者nosql中。
Shiro Seesion的另一个好处是Shiro Session可以在不同技术实现的客户端之间进行共享譬如Swing桌面客户端可以和web客户端一起加入同一个应用会话用户同时使用swing客户端和web客户端时很有用
```java
/**
* 用户获取Session
*/
// 如果当前用户存在会话,则获取已存在会话,
// 如果当前用户不存在会话,则创建新的会话
Session session = subject.getSession();
// 接受一个boolean值该boolean值标识如果当前用户不存在会话是否创建一个新的会话
Session session = subject.getSession(boolean create);
```
### Shiro Session可在任何应用中使用
```java
Session session = subject.getSession();
session.getAttribute(key, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);
```
## Shiro加密
Shiro加密是独立于用户Subject故而Shiro加密可以独立于Subject使用。
Shiro加密主要关注于如下两个模块
- hash加密又名消息摘要message digest
- ciphers加密
### shiro hash
在Shiro hash中如果想要快速计算文件的MD5值可以通过如下方式快速计算
```java
String hex = new Md5Hash(myFile).toHex();
```
在shiro hash中如果想要对密码进行sha-512进行hash操作并且对结果进行base64编码成可打印字符串可以进行如下操作
```java
String encodedPassword =
new Sha512Hash(password, salt, count).toBase64();
```
Shiro框架在很大程度上简化了hash和编码。
### Shiro Ciphers
Ciphers是一种算法可以通过指定的key将加密后的数据还原到加密之前不同于hash操作hash操作通常是不可逆的。通常用Ciphers来保证数据传输时的安全防止数据在传输时被窥探。
Shiro通过引入CipherService API来简化加密的流程**CipherService是一个简单无状态并且线程安全的API可以对应用数据进行加密或者解密操作。**
```java
// Shiro CipherService对数据进行加密操作
AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);
//create a test key:
byte[] testKey = cipherService.generateNewKey();
//encrypt a files bytes:
byte[] encrypted =
cipherService.encrypt(fileBytes, testKey);
```
## Shiro框架的Web支持
### Web 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 Session时其Session是独立与环境的Web应用和非Web应用都可以通过相同的Shiro Session API与Shiro Session进行交互而Shiro Session是否是基于Servlet容器的HttpSession用户是无感知的。
### Shiro Native Session
当启用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来代理这些请求。
> **故而当想要在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 Boot自动配置会做如下工作
- 启用Spring Security的默认配置创建一个的bean对象bean对象名称为“springSecurityFilterChain”bean对象类型为SecurityFilterChain实现了Filter。该bean对象为负责应用中所有与安全相关的操作例如验证提交的username和password保护应用的url等
- 创建一个UserDetailsService的bean对象并且产生一个为”user“的username和一个随机产生的password随机产生的password会输出在console日志中
- 将名为springSecurityFilterChain的bean对象注册到servlet容器中用来对每个servlet请求进行处理
> Spring Security会通过Spring Security会通过BCtyptHash解密算法来对密码的存储进行保护
## Spring Security结构
### DelegatingFilterProxy
Spring提供了Filter的一个实现类DelegatingFilterProxy其将Servlet容器的生命周期和Spring的ApplicationContext连接在一起。
***DelegatingFilterProxy会通过标准的servlet容器机制被注册但是将所有工作都委托给Spring容器中实现了Filter的bean对象***。
> DelegatingFilterProxy会在ApplicationContext中查找实现了Filter的bean对象并且调用该bean对象的doFilter方法
> ```java
> public void doFilter(ServletRequest request,
> ServletResponse response, FilterChain chain) {
> // Lazily get Filter that was registered as a Spring Bean
> // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
> Filter delegate = getFilterBean(someBeanName);
> // delegate work to the Spring Bean
> delegate.doFilter(request, response);
> }
> ```
### FilterChainProxy
Spring Security支持FilterChainProxyFilterChainProxy是一个由Spring Security提供的特殊FilterFilterChainProxy通过SecurityFilterChain允许向多个Filters委托工作。
***FilterChainProxy是一个bean对象通过被包含在DelegatingFilterProxy中。***
### SecurityFilterChain
SecurityFilterChain通常被FilterChainProxy使用用来决定在该次请求中调用那些Spring Security Filters。
### SecurityFilters
Security Filters通过SecurityFilterChain API被插入到FilterChainProxy中。
### 处理Security异常
ExceptionTranslationFilter将认证异常和权限异常翻译为http response。
> ExceptionTranslationFilter被插入到FilterChainProxy中作为SecurityFilterChain中的一个。
> 如果应用程序没有抛出AccessDeniedException或AuthenticationException那么ExceptionTranslationFilter并不会做任何事情。
```java
// ExceptionTranslationFilter的伪代码
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
```
## Spring Security身份认证的结构
### SecurityContextHolder
Spring Security身份认证的核心模型SecurityContextHolder包含有SecurityContext。
> Spring Security将被认证用户的详细信息details存储在SecurityContextHolder中。Spring Security不在乎该SecurityContextHolder是如何被填充的只要该SecurityContextHolder有值那么其将会被用作当前已经被认证的用户。
> ***将用户标识为已认证的最简单的方法是为该用户设置SecurityContextHolder。***
```java
// 设置SecurityContextHolder
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
```
默认情况下SecurityContextHolder通过ThreadLocal来存储SecurityContext故而SecurityContext对于位于同一线程之下的方法来说都可以访问。
> 使用ThreadLocal来存储SecurityContext是相当安全的如果想要在该已认证主体的请求被处理完成之后清除SecurityContextSpring Security中的FilterChainProxy会保证该SecurityContext被清除。
### SecurityContext
SecurityContext从SecurityContextHolder中获得SecurityContext中含有Authentication对象。
### Authentication
Authentication在Spring Security中具有两个目的
- 作为AuthenticationManager的输入用于提供待认证用户的认证凭证。当用于该场景下时isAuthenticated()方法返回值应该为false
- 代表当前已经认证过的用户。当前的Authentication可以从SecurityContext中获取而默认情况下SecurityContext是存储在ThreadLocal中的
Authentication含有如下部分
- 主体principal用于标识用户当通过username/password进行认证时其通常是UserDetails类的实例
- 凭据credentials通常是password在许多场景下凭据会在用户认证成功之后被清空为了保证凭据不会被泄露
- 权限authorities该GrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。
### GrantedAuthority
GrantedAuthority是用户被授予的高层次许可譬如用户角色或者作用域范围。
GrantedAuthority可以通过Authentication.getAuthorities()方法来获得该方法会返回一个GrantedAuthentication的集合。每个GrantedAuthentication都是一项被授予该用户的权限。
### AuthenticationManager
AuthenticationManager的API定义了Security Filters如何来执行身份认证。对于身份认证返回的Authentication会被调用AuthenticationManager的controller设置到SecurityContextHolder中。
> AuthenticationManager的实现可以是任何类但是最通用的实现仍然是ProviderManager
### ProviderManager
ProviderManager是AuthenticationManager的最通用实现。ProviderManager将工作委托给一系列AuthenticationProvider。
> 对于每个ProviderManager都可以决定将该认证标识为成功、失败或者将认证工作委托给下游AuthenticationProvider。
> 如果所有的AuthenticationProvider都没有将该认证标识为成功或者失败那么整个认证流程失败并且抛出ProviderNotFoundException异常。
> ProviderNotFoundException是一个特殊的AuthenticationException该异常代表对传入Authentication的类型并没有配置该类型的ProviderManager
> 在实践中每个AuthenticationProvider知道如何处理一种特定类型的Authentication
默认情况下ProviderManager在认证请求成功后会尝试清除返回的Authentication对象中任何敏感的凭据信息这将会保证password等敏感信息保存时间尽可能地短减少泄露的风险。
### AuthenticationProvider
复数个AuthenticationProvider可以被注入到ProviderManager中每个AuthenticationProvider可以负责一种专门类型的认证例如DaoAuthenticationProvider负责username/password认证JwtAuthenticationProvider负责jwt token的认证。
### AuthenticationEntryPoint
AuthenticationEntryPoint用来发送一个Http Response用来向客户端请求认证凭据。
某些情况下客户端在请求资源时会主动在请求中包含凭据如username/password等。在这种情况下服务端并不需要再专门发送Http Response来向客户端请求认证凭据。
> AuthenticationEntryPoint用来向客户端请求认证凭据AuthenticationEntryPoint的实现可能会执行一个从定向操作将请求重定向到一个登录页面用于获取凭据并且返回一个WWW-Authentication Header。
### AbstractAuthenticationProcessingFilter
该类作为base filter用来对用户的凭据进行认证。再凭据被认证之前Spring Security通常会通过AuthenticationEntryPoint向客户端请求认证凭据。
之后AbstractAuthenticationProcessingFilter会对提交的任何认证请求进行认证。
#### 认证流程
1. 当用户提交认证凭据之后AbstractAuthenticationProcesingFilter会根据HttpServletRequest对象创建一个Authentication对象用于认证该Authentication的类型取决于AbstractAuthenticationProcessingFilter的子类类型。例如UsernamePasswordAuthenticationFilter会通过提交request中的username和password创建UsernamePasswordAuthenticationToken。
2. 然后将构建产生的Authentication对象传入到AuthenticationManager中进行认证
3. 如果认证失败那么会失败SecurityContextHolder会被清空RememberMeService.logFail方法将会被调用AuthenticationFailureHandler也会被调用
4. 如果认证成功那么SessionAuthenticationStrategy将会收到登录的通知
5. 该Authentication对象再认证成功之后将会被设置到SecurityContextHolder中
6. RemeberMeService.logSuccess将会被调用
7. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent.
8. AuthenticationSuccessHandler被调用
## 用户名/密码认证
### Form Login
Spring Security为以表单形式提供的用户名和密码认证提供支持。
#### 用户被重定向到登录页面的过程
1. 用户发送了一个没有经过身份认证的请求到指定资源,并且待请求的资源对该用户来说是未授权的
2. Spring Security中FilterSecurityInterceptor抛出AccessDeniedException代表该未授权的请求被拒绝
3. 因为该用户没有经过认证故而ExceptionTransactionFilter发起了开始认证的过程并且使用配置好的AuthenticationEntryPoint向登录页面发起了重定向。在大多数情况下AuthenticationEntryPoint都是LoginUrlAuthenticationEntryPoint
4. 浏览器接下来会请求重定向到的登陆页面
当用户名和密码提交后UsernamePasswordAuthenticationFilter会对username和password进行认证。UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter。
#### 认证用户名和密码过程
1. 当用户提交了其用户名和密码之后UsernamePasswordAuthenticationFilter会创建一个UsernamePasswordAuthenticationToken
2. 创建的UsernamePasswordAuthenticationToken会传入AuthenticationManager中进行认证
3. 如果认证失败那么SecurityContextHolder会被清除RememberMeService.logFailure和AuthenticationFailureHandler会被调用
4. 如果认证成功那么SessionAuthenticationStrategy将会收到登录的通知RemeberMeService.logSuccess和AuthenticationSuccessHandler会被调用ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent
Spring Security Form Login默认情况下是开启的但是一旦任何基于servlet的配置被提供那么基于表单的login也必须要显式指定。
```java
// 显式指定form login的配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(withDefaults());
// ...
}
```
如果想要自定义login form page可以使用如下配置
```java
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
// ...
}
```
### 基本Authentication
#### 基本Authentication的认证流程
1. 用户向私有资源发送未认证请求,其中对私有资源的访问并没有被授权
2. SpringSecurity的FilterSecurityInterceptor表明该未认证的请求被拒绝访问抛出AccessDeniedException
3. 由于该请求没有经过身份认证故而ExceptionTranslationFilter启动身份认证被配置好的AuthenticationEntryPoint是一个BasicAuthenticationEntryPoint类的实例该实例会发送WWW-Authentication的header。RequestCache通常是一个NullRequestCache不会保存任何的http request请求因为客户端能够重新发送其原来发送过的请求。
4. 当客户端获取到WWW-Authentication的header客户端会知道其接下来会通过username和password重新尝试重新发送http请求。
默认情况下basic authentication是被开启的。但是如果有任何基于基于servlet的配置被提供那么必须通过如下方式显式开启basic authentication。
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
return http.build();
}
```
### Digest Authentication(摘要认证,***不安全***)
在目前不应该在现代应用程序中使用Digest Authentication因为使用摘要认证时必须将password通过纯文本、加密或MD5的格式存储MD5已经被证实不安全。相对的应该使用单向的密码散列如bCrypt, PBKDF2, SCrypt来存储认证凭证但是这些都不被Digest Authentication所支持。
> 摘要认证主要用来解决Basic Authentication中存在的问题摘要认证确保了认证凭证在网络上不会以明文的方式传输。
> 如果想要使用非https的方式并且最大限度的加强认证过程那么可以考虑使用Digest Authentication。
#### 摘要认证中的随机数
摘要认证中的核心是随机数该随机数的值由服务端产生Spring Security中随机数次啊用如下格式
```txt
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
```
需要为存储不安全的密码文本配置使用NoOpPasswordEncoder。可以通过如下方式来配置Digest Authentication。
```java
@Autowired
UserDetailsService userDetailsService;
DigestAuthenticationEntryPoint entryPoint() {
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
result.setRealmName("My App Relam");
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
}
DigestAuthenticationFilter digestAuthenticationFilter() {
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
result.setUserDetailsService(userDetailsService);
result.setAuthenticationEntryPoint(entryPoint());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
.addFilterBefore(digestFilter());
return http.build();
}
```
## 密码存储方式
### 内存中存储密码
Spring Security中InMemoryUserDetailsManager实现了UserDetailsService用于向基于存储在内存中的密码认证提供支持。
InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。基于UserDetails的认证主要用来接受基于用户名/密码的认证。
InMemoryUserDetailsManager可以通过如下方式进行配置
```java
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
```
#### 内存中存储密码时使用defaultPasswordEncoder
***通过defaultPasswordEncoder来指定密码编码器时无法防止通过反编译字节码来获取密码的攻击。***
```java
@Bean
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
```
### JDBC Authentication
Spring Security的JdbcDaoImpl实现了UserDetailsService来基于username/password的认证提供从jdbc获取密码的支持。JdbcUserDetailsManager继承了JdbcDaoImpl来通过DetailsManager的接口提供对UserDetails的管理。
Spring Security为基于jdbc的认证提供了默认的查询语句。
#### User Schema
JdbcDaoImpl需要数据表来导入密码、账户状态和用户的一系列权限。JdbcDaoImpl默认需要的schema如下
```sql
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联
# 用户表需要提供usernamepassword、用户状态
# 权限表需要提供用户名和权限名称
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(500) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
```
#### Group Schema
如果你的程序中使用了Group那么还额外需要一张group的表默认如下
```sql
# 如果要为group配置权限,需要引入三张表,group表,权限表和group_member表
create table groups (
id bigint auto_increment primary key,
group_name varchar_ignorecase(50) not null
);
create table group_authorities (
group_id bigint not null,
authority varchar(50) not null,
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);
create table group_members (
id bigint auto_increment primary key,
username varchar(50) not null,
group_id bigint not null,
constraint fk_group_members_group foreign key(group_id) references groups(id)
);
```
#### 配置Datasource
```java
// 生产环境时,应该通过对外部数据库的连接来建立数据源
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(H2)
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
.build();
}
```
#### 创建JdbcUserDetailsManager Bean对象
```java
@Bean
UserDetailsManager users(DataSource dataSource) {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
users.createUser(user);
users.createUser(admin);
return users;
}
```
### UserDetails
UserDetails是通过UserDetailsService返回的。DaoAuthenticationProvider对UserrDetails进行验证并且返回Authentication.
### UserDetailsService
UserDetailsService被DaoAuthenticationProvider调用用来获取username、password和其他随着password/username一起认证的信息。对于UserDetailsServiceSpring Security提供了in-memory和jdbc两种实现形式。
可以通过自定义UserDetailsService类bean对象的方式来自定义认证过程。
```java
// 自定义UserDetailsService的bean对象
@Bean
CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
```
### PasswordEncoder
Spring Security支持PasswordEncoder来安全的存储密码。可以通过自定义PasswordEncoder类的bean对象的形式来自定义Spring Security安全存储密码的过程。
### DaoAuthenticationProvider
DaoAuthenticationProvider是AuthenticationProvider的一个实现类通过调用UserDetailsService和PasswordEncoder来认证用户名和密码。
Spring Security中DaoAuthenticationProvider的工作流程
1. authentication filter会读取username和password并且将其封装到UsernamePasswordAuthenticationToken中传递给AuthenticationManagerProviderManager实现了AuthenticationManager
2. ProviderManager被配置为使用DaoAuthenticationProvider
3. DaoAuthenticationProvider通过UserDetailsService来查找UserDetails
4. DaoAuthenticationProvider通过PasswordEncoder来验证UserDetails中的密码
5. 当验证成功时会返回UsernamePasswordAuthenticationToken类型的Authentication并且返回的Authentication拥有一个主体为UserDetailsService返回的UserDetails
6. 返回的UsernamePasswordAuthenticationToken会在SecurityContextHolder中保存
# Spring Security
## Spring Security简介
Spring Security作为一个安全框架向使用者提供了用户认证、授权、常规攻击保护等功能。
## Spring Security自动配置
默认情况下在引入Spring Security的启动器依赖之后Spring Boot自动配置会做如下工作
- 启用Spring Security的默认配置创建一个的bean对象bean对象名称为“springSecurityFilterChain”bean对象类型为SecurityFilterChain实现了Filter。该bean对象为负责应用中所有与安全相关的操作例如验证提交的username和password保护应用的url等
- 创建一个UserDetailsService的bean对象并且产生一个为”user“的username和一个随机产生的password随机产生的password会输出在console日志中
- 将名为springSecurityFilterChain的bean对象注册到servlet容器中用来对每个servlet请求进行处理
> Spring Security会通过Spring Security会通过BCtyptHash解密算法来对密码的存储进行保护
## Spring Security结构
### DelegatingFilterProxy
Spring提供了Filter的一个实现类DelegatingFilterProxy其将Servlet容器的生命周期和Spring的ApplicationContext连接在一起。
***DelegatingFilterProxy会通过标准的servlet容器机制被注册但是将所有工作都委托给Spring容器中实现了Filter的bean对象***。
> DelegatingFilterProxy会在ApplicationContext中查找实现了Filter的bean对象并且调用该bean对象的doFilter方法
> ```java
> public void doFilter(ServletRequest request,
> ServletResponse response, FilterChain chain) {
> // Lazily get Filter that was registered as a Spring Bean
> // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
> Filter delegate = getFilterBean(someBeanName);
> // delegate work to the Spring Bean
> delegate.doFilter(request, response);
> }
> ```
### FilterChainProxy
Spring Security支持FilterChainProxyFilterChainProxy是一个由Spring Security提供的特殊FilterFilterChainProxy通过SecurityFilterChain允许向多个Filters委托工作。
***FilterChainProxy是一个bean对象通过被包含在DelegatingFilterProxy中。***
### SecurityFilterChain
SecurityFilterChain通常被FilterChainProxy使用用来决定在该次请求中调用那些Spring Security Filters。
### SecurityFilters
Security Filters通过SecurityFilterChain API被插入到FilterChainProxy中。
### 处理Security异常
ExceptionTranslationFilter将认证异常和权限异常翻译为http response。
> ExceptionTranslationFilter被插入到FilterChainProxy中作为SecurityFilterChain中的一个。
> 如果应用程序没有抛出AccessDeniedException或AuthenticationException那么ExceptionTranslationFilter并不会做任何事情。
```java
// ExceptionTranslationFilter的伪代码
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
```
## Spring Security身份认证的结构
### SecurityContextHolder
Spring Security身份认证的核心模型SecurityContextHolder包含有SecurityContext。
> Spring Security将被认证用户的详细信息details存储在SecurityContextHolder中。Spring Security不在乎该SecurityContextHolder是如何被填充的只要该SecurityContextHolder有值那么其将会被用作当前已经被认证的用户。
> ***将用户标识为已认证的最简单的方法是为该用户设置SecurityContextHolder。***
```java
// 设置SecurityContextHolder
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
```
默认情况下SecurityContextHolder通过ThreadLocal来存储SecurityContext故而SecurityContext对于位于同一线程之下的方法来说都可以访问。
> 使用ThreadLocal来存储SecurityContext是相当安全的如果想要在该已认证主体的请求被处理完成之后清除SecurityContextSpring Security中的FilterChainProxy会保证该SecurityContext被清除。
### SecurityContext
SecurityContext从SecurityContextHolder中获得SecurityContext中含有Authentication对象。
### Authentication
Authentication在Spring Security中具有两个目的
- 作为AuthenticationManager的输入用于提供待认证用户的认证凭证。当用于该场景下时isAuthenticated()方法返回值应该为false
- 代表当前已经认证过的用户。当前的Authentication可以从SecurityContext中获取而默认情况下SecurityContext是存储在ThreadLocal中的
Authentication含有如下部分
- 主体principal用于标识用户当通过username/password进行认证时其通常是UserDetails类的实例
- 凭据credentials通常是password在许多场景下凭据会在用户认证成功之后被清空为了保证凭据不会被泄露
- 权限authorities该GrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。
### GrantedAuthority
GrantedAuthority是用户被授予的高层次许可譬如用户角色或者作用域范围。
GrantedAuthority可以通过Authentication.getAuthorities()方法来获得该方法会返回一个GrantedAuthentication的集合。每个GrantedAuthentication都是一项被授予该用户的权限。
### AuthenticationManager
AuthenticationManager的API定义了Security Filters如何来执行身份认证。对于身份认证返回的Authentication会被调用AuthenticationManager的controller设置到SecurityContextHolder中。
> AuthenticationManager的实现可以是任何类但是最通用的实现仍然是ProviderManager
### ProviderManager
ProviderManager是AuthenticationManager的最通用实现。ProviderManager将工作委托给一系列AuthenticationProvider。
> 对于每个ProviderManager都可以决定将该认证标识为成功、失败或者将认证工作委托给下游AuthenticationProvider。
> 如果所有的AuthenticationProvider都没有将该认证标识为成功或者失败那么整个认证流程失败并且抛出ProviderNotFoundException异常。
> ProviderNotFoundException是一个特殊的AuthenticationException该异常代表对传入Authentication的类型并没有配置该类型的ProviderManager
> 在实践中每个AuthenticationProvider知道如何处理一种特定类型的Authentication
默认情况下ProviderManager在认证请求成功后会尝试清除返回的Authentication对象中任何敏感的凭据信息这将会保证password等敏感信息保存时间尽可能地短减少泄露的风险。
### AuthenticationProvider
复数个AuthenticationProvider可以被注入到ProviderManager中每个AuthenticationProvider可以负责一种专门类型的认证例如DaoAuthenticationProvider负责username/password认证JwtAuthenticationProvider负责jwt token的认证。
### AuthenticationEntryPoint
AuthenticationEntryPoint用来发送一个Http Response用来向客户端请求认证凭据。
某些情况下客户端在请求资源时会主动在请求中包含凭据如username/password等。在这种情况下服务端并不需要再专门发送Http Response来向客户端请求认证凭据。
> AuthenticationEntryPoint用来向客户端请求认证凭据AuthenticationEntryPoint的实现可能会执行一个从定向操作将请求重定向到一个登录页面用于获取凭据并且返回一个WWW-Authentication Header。
### AbstractAuthenticationProcessingFilter
该类作为base filter用来对用户的凭据进行认证。再凭据被认证之前Spring Security通常会通过AuthenticationEntryPoint向客户端请求认证凭据。
之后AbstractAuthenticationProcessingFilter会对提交的任何认证请求进行认证。
#### 认证流程
1. 当用户提交认证凭据之后AbstractAuthenticationProcesingFilter会根据HttpServletRequest对象创建一个Authentication对象用于认证该Authentication的类型取决于AbstractAuthenticationProcessingFilter的子类类型。例如UsernamePasswordAuthenticationFilter会通过提交request中的username和password创建UsernamePasswordAuthenticationToken。
2. 然后将构建产生的Authentication对象传入到AuthenticationManager中进行认证
3. 如果认证失败那么会失败SecurityContextHolder会被清空RememberMeService.logFail方法将会被调用AuthenticationFailureHandler也会被调用
4. 如果认证成功那么SessionAuthenticationStrategy将会收到登录的通知
5. 该Authentication对象再认证成功之后将会被设置到SecurityContextHolder中
6. RemeberMeService.logSuccess将会被调用
7. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent.
8. AuthenticationSuccessHandler被调用
## 用户名/密码认证
### Form Login
Spring Security为以表单形式提供的用户名和密码认证提供支持。
#### 用户被重定向到登录页面的过程
1. 用户发送了一个没有经过身份认证的请求到指定资源,并且待请求的资源对该用户来说是未授权的
2. Spring Security中FilterSecurityInterceptor抛出AccessDeniedException代表该未授权的请求被拒绝
3. 因为该用户没有经过认证故而ExceptionTransactionFilter发起了开始认证的过程并且使用配置好的AuthenticationEntryPoint向登录页面发起了重定向。在大多数情况下AuthenticationEntryPoint都是LoginUrlAuthenticationEntryPoint
4. 浏览器接下来会请求重定向到的登陆页面
当用户名和密码提交后UsernamePasswordAuthenticationFilter会对username和password进行认证。UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter。
#### 认证用户名和密码过程
1. 当用户提交了其用户名和密码之后UsernamePasswordAuthenticationFilter会创建一个UsernamePasswordAuthenticationToken
2. 创建的UsernamePasswordAuthenticationToken会传入AuthenticationManager中进行认证
3. 如果认证失败那么SecurityContextHolder会被清除RememberMeService.logFailure和AuthenticationFailureHandler会被调用
4. 如果认证成功那么SessionAuthenticationStrategy将会收到登录的通知RemeberMeService.logSuccess和AuthenticationSuccessHandler会被调用ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent
Spring Security Form Login默认情况下是开启的但是一旦任何基于servlet的配置被提供那么基于表单的login也必须要显式指定。
```java
// 显式指定form login的配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(withDefaults());
// ...
}
```
如果想要自定义login form page可以使用如下配置
```java
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
// ...
}
```
### 基本Authentication
#### 基本Authentication的认证流程
1. 用户向私有资源发送未认证请求,其中对私有资源的访问并没有被授权
2. SpringSecurity的FilterSecurityInterceptor表明该未认证的请求被拒绝访问抛出AccessDeniedException
3. 由于该请求没有经过身份认证故而ExceptionTranslationFilter启动身份认证被配置好的AuthenticationEntryPoint是一个BasicAuthenticationEntryPoint类的实例该实例会发送WWW-Authentication的header。RequestCache通常是一个NullRequestCache不会保存任何的http request请求因为客户端能够重新发送其原来发送过的请求。
4. 当客户端获取到WWW-Authentication的header客户端会知道其接下来会通过username和password重新尝试重新发送http请求。
默认情况下basic authentication是被开启的。但是如果有任何基于基于servlet的配置被提供那么必须通过如下方式显式开启basic authentication。
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
return http.build();
}
```
### Digest Authentication(摘要认证,***不安全***)
在目前不应该在现代应用程序中使用Digest Authentication因为使用摘要认证时必须将password通过纯文本、加密或MD5的格式存储MD5已经被证实不安全。相对的应该使用单向的密码散列如bCrypt, PBKDF2, SCrypt来存储认证凭证但是这些都不被Digest Authentication所支持。
> 摘要认证主要用来解决Basic Authentication中存在的问题摘要认证确保了认证凭证在网络上不会以明文的方式传输。
> 如果想要使用非https的方式并且最大限度的加强认证过程那么可以考虑使用Digest Authentication。
#### 摘要认证中的随机数
摘要认证中的核心是随机数该随机数的值由服务端产生Spring Security中随机数次啊用如下格式
```txt
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
```
需要为存储不安全的密码文本配置使用NoOpPasswordEncoder。可以通过如下方式来配置Digest Authentication。
```java
@Autowired
UserDetailsService userDetailsService;
DigestAuthenticationEntryPoint entryPoint() {
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
result.setRealmName("My App Relam");
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
}
DigestAuthenticationFilter digestAuthenticationFilter() {
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
result.setUserDetailsService(userDetailsService);
result.setAuthenticationEntryPoint(entryPoint());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
.addFilterBefore(digestFilter());
return http.build();
}
```
## 密码存储方式
### 内存中存储密码
Spring Security中InMemoryUserDetailsManager实现了UserDetailsService用于向基于存储在内存中的密码认证提供支持。
InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。基于UserDetails的认证主要用来接受基于用户名/密码的认证。
InMemoryUserDetailsManager可以通过如下方式进行配置
```java
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
```
#### 内存中存储密码时使用defaultPasswordEncoder
***通过defaultPasswordEncoder来指定密码编码器时无法防止通过反编译字节码来获取密码的攻击。***
```java
@Bean
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
```
### JDBC Authentication
Spring Security的JdbcDaoImpl实现了UserDetailsService来基于username/password的认证提供从jdbc获取密码的支持。JdbcUserDetailsManager继承了JdbcDaoImpl来通过DetailsManager的接口提供对UserDetails的管理。
Spring Security为基于jdbc的认证提供了默认的查询语句。
#### User Schema
JdbcDaoImpl需要数据表来导入密码、账户状态和用户的一系列权限。JdbcDaoImpl默认需要的schema如下
```sql
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联
# 用户表需要提供usernamepassword、用户状态
# 权限表需要提供用户名和权限名称
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(500) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
```
#### Group Schema
如果你的程序中使用了Group那么还额外需要一张group的表默认如下
```sql
# 如果要为group配置权限,需要引入三张表,group表,权限表和group_member表
create table groups (
id bigint auto_increment primary key,
group_name varchar_ignorecase(50) not null
);
create table group_authorities (
group_id bigint not null,
authority varchar(50) not null,
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);
create table group_members (
id bigint auto_increment primary key,
username varchar(50) not null,
group_id bigint not null,
constraint fk_group_members_group foreign key(group_id) references groups(id)
);
```
#### 配置Datasource
```java
// 生产环境时,应该通过对外部数据库的连接来建立数据源
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(H2)
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
.build();
}
```
#### 创建JdbcUserDetailsManager Bean对象
```java
@Bean
UserDetailsManager users(DataSource dataSource) {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
users.createUser(user);
users.createUser(admin);
return users;
}
```
### UserDetails
UserDetails是通过UserDetailsService返回的。DaoAuthenticationProvider对UserrDetails进行验证并且返回Authentication.
### UserDetailsService
UserDetailsService被DaoAuthenticationProvider调用用来获取username、password和其他随着password/username一起认证的信息。对于UserDetailsServiceSpring Security提供了in-memory和jdbc两种实现形式。
可以通过自定义UserDetailsService类bean对象的方式来自定义认证过程。
```java
// 自定义UserDetailsService的bean对象
@Bean
CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
```
### PasswordEncoder
Spring Security支持PasswordEncoder来安全的存储密码。可以通过自定义PasswordEncoder类的bean对象的形式来自定义Spring Security安全存储密码的过程。
### DaoAuthenticationProvider
DaoAuthenticationProvider是AuthenticationProvider的一个实现类通过调用UserDetailsService和PasswordEncoder来认证用户名和密码。
Spring Security中DaoAuthenticationProvider的工作流程
1. authentication filter会读取username和password并且将其封装到UsernamePasswordAuthenticationToken中传递给AuthenticationManagerProviderManager实现了AuthenticationManager
2. ProviderManager被配置为使用DaoAuthenticationProvider
3. DaoAuthenticationProvider通过UserDetailsService来查找UserDetails
4. DaoAuthenticationProvider通过PasswordEncoder来验证UserDetails中的密码
5. 当验证成功时会返回UsernamePasswordAuthenticationToken类型的Authentication并且返回的Authentication拥有一个主体为UserDetailsService返回的UserDetails
6. 返回的UsernamePasswordAuthenticationToken会在SecurityContextHolder中保存

View File

@@ -1,15 +1,15 @@
# POJO
## POJO定义
POJOPlain Old Java Object是一种直接的类型POJO并不包含对任何框架的引用。
> 对于POJO类型该类属性和方法的定义并没有特定的约束和限制
## Java Bean命名约束
由于对POJO本身并没有对POJO类属性和方法的定义强制指定命名约束因而许多框默认支持Java Bean命名约束。
> ### Java Bean命名约束
> 在Java Bean命名约束中为POJO类属性和方法的命名指定了如下规则
> 1. 属性的访问权限都被设置为private属性通过getter和setter向外暴露
> 2. 对于方法的命名getter和setter遵循getXXX/setXXX的命名规范对于boolean属性的getter可以使用isXXX形式
> 3. Java Bean命名规范要求Java Bean对象需要提供无参构造方法
> 4. 实现Serializable接口能够将对象以二进制的格式进行存储
## 其他命名规范
由于Java Bean命名规范中有些规则强制对Java Bean的命名进行限制可能会带来弊端故而如今许多框架在接受Java Bean命名规范之余仍然支持其他的POJO命名规范
> 如在Spring中通过@Component注解注册Bean对象时被@Component注解的类并不一定要实现Serializable接口也不一定要拥有无参构造方法。
# POJO
## POJO定义
POJOPlain Old Java Object是一种直接的类型POJO并不包含对任何框架的引用。
> 对于POJO类型该类属性和方法的定义并没有特定的约束和限制
## Java Bean命名约束
由于对POJO本身并没有对POJO类属性和方法的定义强制指定命名约束因而许多框默认支持Java Bean命名约束。
> ### Java Bean命名约束
> 在Java Bean命名约束中为POJO类属性和方法的命名指定了如下规则
> 1. 属性的访问权限都被设置为private属性通过getter和setter向外暴露
> 2. 对于方法的命名getter和setter遵循getXXX/setXXX的命名规范对于boolean属性的getter可以使用isXXX形式
> 3. Java Bean命名规范要求Java Bean对象需要提供无参构造方法
> 4. 实现Serializable接口能够将对象以二进制的格式进行存储
## 其他命名规范
由于Java Bean命名规范中有些规则强制对Java Bean的命名进行限制可能会带来弊端故而如今许多框架在接受Java Bean命名规范之余仍然支持其他的POJO命名规范
> 如在Spring中通过@Component注解注册Bean对象时被@Component注解的类并不一定要实现Serializable接口也不一定要拥有无参构造方法。

View File

@@ -1,109 +1,109 @@
# SpEL(Spring Expression Language)
- ## SpEL的用法
- SpEL如何将表达式从字符串转化为计算后的值
- 在转化过程中在parseExpression方法执行时可能会抛出ParseException异常在执行getValue方法时可能会抛出EvaluationException
```java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
```
- 在SpEL中获取String的字节数组
```java
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("'Hello World'.bytes");
byte[] bytes=(byte[])exp.getValue();
```
- 在调用Expression类型的getValue方法时可以不用进行强制类型转换而是在getValue方法中传入一个Class参数返回值将会被自动转换成Class对应的目标类型当转换失败时会抛出EvaluationException
```java
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("'Hello World'.bytes.length");
Integer bytes=exp.getValue(Integer.class);
```
- SpEL可以针对特定的对象给出一个表达式并且在getValue方法中传入一个对象那么表达式中的变量将会针对该对象中的特定属性
```java
// 如下步骤会比较waifu对象的name属性是否为"touma"字符串
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("name=='touma'");
Boolean equals=exp.getValue(waifu,Boolean.class);
```
- 可以为parser设置一个parserconfiguration用于处理当列表或集合元素的index操作超过集合长度时的默认行为
```java
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
```
- ## SpEL在bean对象定义时的使用
- 在使用@Value注解时可以结合SpEL表达式进行使用@Value注解可以运用在域变量、方法、方法和构造器的参数上。@Value会指定默认值
- ## SpEL对List、Map的支持
- 可以通过{}来直接表示list
```java
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
```
- 可以通过{key:value}形式来直接表示map空map用{:}来进行表示
```java
// evaluates to a Java map containing the two entries
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);
```
- 可以通过new int[]{}的形式为SpEL指定数组
```java
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
```
- ## SpEL支持的特殊操作符
- instanceof
```java
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
```
- 正则表达式
```java
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
```
- 类型操作符获取类型的Class对象、调用静态方法
```java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
```
- new操作符
- 可以在SpEL表达式中通过new操作符来调用构造器但是除了位于java.lang包中的类对其他的类调用构造器时都必须指定类的全类名
```java
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
# SpEL(Spring Expression Language)
- ## SpEL的用法
- SpEL如何将表达式从字符串转化为计算后的值
- 在转化过程中在parseExpression方法执行时可能会抛出ParseException异常在执行getValue方法时可能会抛出EvaluationException
```java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
```
- 在SpEL中获取String的字节数组
```java
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("'Hello World'.bytes");
byte[] bytes=(byte[])exp.getValue();
```
- 在调用Expression类型的getValue方法时可以不用进行强制类型转换而是在getValue方法中传入一个Class参数返回值将会被自动转换成Class对应的目标类型当转换失败时会抛出EvaluationException
```java
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("'Hello World'.bytes.length");
Integer bytes=exp.getValue(Integer.class);
```
- SpEL可以针对特定的对象给出一个表达式并且在getValue方法中传入一个对象那么表达式中的变量将会针对该对象中的特定属性
```java
// 如下步骤会比较waifu对象的name属性是否为"touma"字符串
ExpressionParser parser=new SpelExpressionParser();
Expression exp=parser.parseExpression("name=='touma'");
Boolean equals=exp.getValue(waifu,Boolean.class);
```
- 可以为parser设置一个parserconfiguration用于处理当列表或集合元素的index操作超过集合长度时的默认行为
```java
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
```
- ## SpEL在bean对象定义时的使用
- 在使用@Value注解时可以结合SpEL表达式进行使用@Value注解可以运用在域变量、方法、方法和构造器的参数上。@Value会指定默认值
- ## SpEL对List、Map的支持
- 可以通过{}来直接表示list
```java
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
```
- 可以通过{key:value}形式来直接表示map空map用{:}来进行表示
```java
// evaluates to a Java map containing the two entries
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);
```
- 可以通过new int[]{}的形式为SpEL指定数组
```java
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
```
- ## SpEL支持的特殊操作符
- instanceof
```java
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
```
- 正则表达式
```java
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
```
- 类型操作符获取类型的Class对象、调用静态方法
```java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
```
- new操作符
- 可以在SpEL表达式中通过new操作符来调用构造器但是除了位于java.lang包中的类对其他的类调用构造器时都必须指定类的全类名
```java
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
```

View File

@@ -1,177 +1,177 @@
# Spring AOP
- ## Spring AOP的核心概念
- Aspect切面一个模块化的考虑
- Joint Point连接点程序执行时的一个时间点通常是方法的执行
- Advice当切面在一个切入点执行多做时执行的动作被称之为AdviceAdvice有不同的类型before、after、around
- Pointcut切入点advice通常运行在满足pointcut的join point上pointcut表达式与join point相关联Spring中默认使用AspectJ切入点表达式
- Introduction在类中声明新的方法、域变量甚至是接口实现
- linking将应用类型或对象和切面链接起来
- ## Spring AOP的类型
- before在连接点之前运行但是无法阻止后续连接点的执行
- after returning在连接点正常返回之后进行
- after throwing在链接点抛出异常正常退出之后进行
- after finally上两种的结合不管连接点是正常退出还是抛出异常退出都会在其之后执行
- aroundaround可以自定义连接点之前和之后的执行内容其也能够选择时候执行连接点的方法
- ## Spring AOP的特点
- 区别于AspectJ AOP框架Spring AOP框架是基于代理来实现的
- 对于实现了接口的类Spring AOP通常是通过JDK动态代理来实现的对于没有实现接口的类Spring AOP是通过cglib来实现的
- 可以强制Spring AOP使用cglib在如下场景
- 如果想要advise类中方法而该方法没有在接口中定义
- 如果想要将代理对象传递给一个具有特定类型的方法作为参数
- ## Spring AOP的AspectJ注解支持
- Spring AOP支持AspectJ注解Spring AOP可以解释和AspectJ 5相同的注解通过使用AspectJ提供的包来进行切入点解析和匹配
- 但是即使使用了AspectJ注解AOP在运行时仍然是纯粹的Spring AOP项目不需要引入AspectJ的编译器和weaver
- Spring AOP对AspectJ注解支持的开启
- 通过@EnableAspectJAutoProxy注解会自动的为满足切入点匹配的连接点bean对象创建移动代理对象
```java
@Configuration
@EnableAspectJAutoProxy
class AspectJConfiguration {
// ...
}
```
- ## 声明Spring AOP切面
- 在容器中任何bean对象如其类型具有@AspectJ注解将会被自动探知到并且用来配置spring aop
- 在Spring AOP中aspect其自身是无法作为其他aspect的目标对象的。被标记为@Aspect的类不仅标明其为aspect并且将其从自动代理中排除
- 如果为某个bean对象配置了切面那么在后续创建该bean对象时实际上是创建该bean对象的代理对象
```java
@Component // 将该类型声明为bean对象
@Aspect // 声明切面
public class ProxyAspect {
}
```
- ## 声明Spring AOP切入点
- 由于Spring AOP仅仅支持方法的连接点故而可以将切入点看做对bean对象方法的匹配
- Join Point expression的种类
- execution匹配目标方法的执行可以在括号中接收一个函数签名包含返回类型、函数名和函数参数类型
```java
// 被@JointPoint注解标注的方法必须具有void的返回类型
@Pointcut("execution(* Point.*(..))")
void methodInjected() {
}
```
- within:匹配声明在某一特定类中的方法
```java
@Pointcut("within(Point)")
```
- this匹配生成的代理对象为该类型的一个实例
- target匹配目标对象为该类型的一个实例
- args匹配特定参数
- @args传递参数的类型具有指定的注解
- @target运行时该对象的类具有指定的注解
- @within运行时执行的方法其方法定义在具有指定注解的类中可以是继承父类的方法父类指定了注解
- @annotation执行的方法具有指定注解
- Spring AOP同样支持将JoinPoint匹配为具有特定name的Spring bean对象
```java
@Pointcut("bean(nameA) || bean(nameB))")
```
- ## Spring AOP中的Advice
- Advice和Pointcut Expresion相关联主要可以分为before、after、around等种类
- Before
```java
@Before("execution(* Point.*(..))")
public void doSomething() {
}
```
- AfterReturning:
```java
// AfterReturning支持获取切入点执行后返回的值
@AfterReturning(
pointcut="execution(* Point.*(..))",
returning="retVal")
public void doSomething(int retVal) {
}
```
- AfterThrowing:
```java
@AfterThrowing(
pointcut="execution(* Point.*())",
throwing="ex"
)
public void doSomething(Throwable ex) {
}
```
- After:After不管是切入点正常返回还是抛出异常都会执行类似于finally
```java
@After("execution(* Point.*())")
public void doSomething() {
}
```
- Around:其方法必须会一个Oject类型的返回值并且方法的第一个参数类型是ProceedingJoinPoint
```java
@Around("execution(* Point.*())")
public Object doSomething(ProceedingJoinPoint pjp) {
return isCacheExisted()?returnFromCache():pjp.proceed();
}
```
- ## Spring AOP中Advice方法对JoinPoint的访问
- 任何advice方法都可以声明声明其第一个参数为JoinPoint类型。@Around标注的adivce方法其第一个参数的类型必须为ProceedingJoinPoint类型该类型为JoinPoint的子类型
- JoinPoint接口具有如下方法
- getArgs返回方法参数
- getThis返回代理对象
- getTarget返回目标对象
- getSignature返回函数的签名
- toString返回该advice方法的描述信息
- ## Advice方法通过参数来获取传递给底层方法的参数
- 在pointcut表达式的args中如果用advice方法中的参数名来代替参数类型那么该类型的参数值会被传递给该参数
```java
@Before("execution(* Point.*(..) && args(position,..))")
public void adviceMethod(Position position) {
}
```
- 或者可以通过如下方式先通过一个Pointcut获取参数在在另一个方法中获取named pointcut已获取的参数
```java
// 此时adviceMethodTwo同样能够获取Position参数
@Pointcut("execution(* Point.*(..)) && args(position,..)")
public void adviceMethodOne(Position position) {
}
@Before("adviceMethodOne(position)")
public void adviceMethodTwo(Position position) {
}
```
- Spring AOP可以通过如下方式来约束泛型的参数
```java
@Before("execution(* GenericsInterface+.method(*) && args(param))")
public void adviceMethod(DesiredType param) {
}
```
- ## 通过Spring AOP对参数进行预处理
```java
@Around("execution(* Point.area(*) && args(width,height))")
public double caculateInCM(ProceedingJoinPoint jp,double width,double height) {
width*=100;
height*=100;
return jp.proceed(width,height);
}
```
- ## Spring AOP中多个advice对应到同一个Pointcut
- 如果多个advice都具有相同的pointcut那么多个advice之间的执行顺序是未定义的。可以为Aspect类实现Ordered接口或者添加@Order标记来定义该advice的执行优先级那么具有具有较小order值的方法将会优先被执行
- ## Spring AOP Introduction
- 在Spring AOP中可以通过Introduction来声明一个对象继承了某接口并且为被代理的对象提供被继承接口的实现
- 可以通过@DeclareParent注解为指定对象添加接口并且指明该接口默认的实现类完成后可以直接将生成的代理对象复制给接口变量
```java
@Aspect
public class MyAspect {
@DeclareParent(value="cc.rikakonatsumi.interfaces.*+",defaultImpl=DefaultImpl.class)
private static MyInterface myInterface;
// 之后可以直接通过this(ref)在pointcut表达式中获取服务对象也可以通过getBean方法获取容器中的对象
}
```
- ## @RestControllerAdvice的使用
- @RestControllerAdvice是@Componnent注解的一个特例@RestControllerAdivce注解的组成包含@Component
- @RestControllerAdivce组合了@ControllerAdvice和@ResponseBody两个注解
- 通常,@RestControllerAdvice用作为spring mvc的所有方法做ExceptionHandler
# Spring AOP
- ## Spring AOP的核心概念
- Aspect切面一个模块化的考虑
- Joint Point连接点程序执行时的一个时间点通常是方法的执行
- Advice当切面在一个切入点执行多做时执行的动作被称之为AdviceAdvice有不同的类型before、after、around
- Pointcut切入点advice通常运行在满足pointcut的join point上pointcut表达式与join point相关联Spring中默认使用AspectJ切入点表达式
- Introduction在类中声明新的方法、域变量甚至是接口实现
- linking将应用类型或对象和切面链接起来
- ## Spring AOP的类型
- before在连接点之前运行但是无法阻止后续连接点的执行
- after returning在连接点正常返回之后进行
- after throwing在链接点抛出异常正常退出之后进行
- after finally上两种的结合不管连接点是正常退出还是抛出异常退出都会在其之后执行
- aroundaround可以自定义连接点之前和之后的执行内容其也能够选择时候执行连接点的方法
- ## Spring AOP的特点
- 区别于AspectJ AOP框架Spring AOP框架是基于代理来实现的
- 对于实现了接口的类Spring AOP通常是通过JDK动态代理来实现的对于没有实现接口的类Spring AOP是通过cglib来实现的
- 可以强制Spring AOP使用cglib在如下场景
- 如果想要advise类中方法而该方法没有在接口中定义
- 如果想要将代理对象传递给一个具有特定类型的方法作为参数
- ## Spring AOP的AspectJ注解支持
- Spring AOP支持AspectJ注解Spring AOP可以解释和AspectJ 5相同的注解通过使用AspectJ提供的包来进行切入点解析和匹配
- 但是即使使用了AspectJ注解AOP在运行时仍然是纯粹的Spring AOP项目不需要引入AspectJ的编译器和weaver
- Spring AOP对AspectJ注解支持的开启
- 通过@EnableAspectJAutoProxy注解会自动的为满足切入点匹配的连接点bean对象创建移动代理对象
```java
@Configuration
@EnableAspectJAutoProxy
class AspectJConfiguration {
// ...
}
```
- ## 声明Spring AOP切面
- 在容器中任何bean对象如其类型具有@AspectJ注解将会被自动探知到并且用来配置spring aop
- 在Spring AOP中aspect其自身是无法作为其他aspect的目标对象的。被标记为@Aspect的类不仅标明其为aspect并且将其从自动代理中排除
- 如果为某个bean对象配置了切面那么在后续创建该bean对象时实际上是创建该bean对象的代理对象
```java
@Component // 将该类型声明为bean对象
@Aspect // 声明切面
public class ProxyAspect {
}
```
- ## 声明Spring AOP切入点
- 由于Spring AOP仅仅支持方法的连接点故而可以将切入点看做对bean对象方法的匹配
- Join Point expression的种类
- execution匹配目标方法的执行可以在括号中接收一个函数签名包含返回类型、函数名和函数参数类型
```java
// 被@JointPoint注解标注的方法必须具有void的返回类型
@Pointcut("execution(* Point.*(..))")
void methodInjected() {
}
```
- within:匹配声明在某一特定类中的方法
```java
@Pointcut("within(Point)")
```
- this匹配生成的代理对象为该类型的一个实例
- target匹配目标对象为该类型的一个实例
- args匹配特定参数
- @args传递参数的类型具有指定的注解
- @target运行时该对象的类具有指定的注解
- @within运行时执行的方法其方法定义在具有指定注解的类中可以是继承父类的方法父类指定了注解
- @annotation执行的方法具有指定注解
- Spring AOP同样支持将JoinPoint匹配为具有特定name的Spring bean对象
```java
@Pointcut("bean(nameA) || bean(nameB))")
```
- ## Spring AOP中的Advice
- Advice和Pointcut Expresion相关联主要可以分为before、after、around等种类
- Before
```java
@Before("execution(* Point.*(..))")
public void doSomething() {
}
```
- AfterReturning:
```java
// AfterReturning支持获取切入点执行后返回的值
@AfterReturning(
pointcut="execution(* Point.*(..))",
returning="retVal")
public void doSomething(int retVal) {
}
```
- AfterThrowing:
```java
@AfterThrowing(
pointcut="execution(* Point.*())",
throwing="ex"
)
public void doSomething(Throwable ex) {
}
```
- After:After不管是切入点正常返回还是抛出异常都会执行类似于finally
```java
@After("execution(* Point.*())")
public void doSomething() {
}
```
- Around:其方法必须会一个Oject类型的返回值并且方法的第一个参数类型是ProceedingJoinPoint
```java
@Around("execution(* Point.*())")
public Object doSomething(ProceedingJoinPoint pjp) {
return isCacheExisted()?returnFromCache():pjp.proceed();
}
```
- ## Spring AOP中Advice方法对JoinPoint的访问
- 任何advice方法都可以声明声明其第一个参数为JoinPoint类型。@Around标注的adivce方法其第一个参数的类型必须为ProceedingJoinPoint类型该类型为JoinPoint的子类型
- JoinPoint接口具有如下方法
- getArgs返回方法参数
- getThis返回代理对象
- getTarget返回目标对象
- getSignature返回函数的签名
- toString返回该advice方法的描述信息
- ## Advice方法通过参数来获取传递给底层方法的参数
- 在pointcut表达式的args中如果用advice方法中的参数名来代替参数类型那么该类型的参数值会被传递给该参数
```java
@Before("execution(* Point.*(..) && args(position,..))")
public void adviceMethod(Position position) {
}
```
- 或者可以通过如下方式先通过一个Pointcut获取参数在在另一个方法中获取named pointcut已获取的参数
```java
// 此时adviceMethodTwo同样能够获取Position参数
@Pointcut("execution(* Point.*(..)) && args(position,..)")
public void adviceMethodOne(Position position) {
}
@Before("adviceMethodOne(position)")
public void adviceMethodTwo(Position position) {
}
```
- Spring AOP可以通过如下方式来约束泛型的参数
```java
@Before("execution(* GenericsInterface+.method(*) && args(param))")
public void adviceMethod(DesiredType param) {
}
```
- ## 通过Spring AOP对参数进行预处理
```java
@Around("execution(* Point.area(*) && args(width,height))")
public double caculateInCM(ProceedingJoinPoint jp,double width,double height) {
width*=100;
height*=100;
return jp.proceed(width,height);
}
```
- ## Spring AOP中多个advice对应到同一个Pointcut
- 如果多个advice都具有相同的pointcut那么多个advice之间的执行顺序是未定义的。可以为Aspect类实现Ordered接口或者添加@Order标记来定义该advice的执行优先级那么具有具有较小order值的方法将会优先被执行
- ## Spring AOP Introduction
- 在Spring AOP中可以通过Introduction来声明一个对象继承了某接口并且为被代理的对象提供被继承接口的实现
- 可以通过@DeclareParent注解为指定对象添加接口并且指明该接口默认的实现类完成后可以直接将生成的代理对象复制给接口变量
```java
@Aspect
public class MyAspect {
@DeclareParent(value="cc.rikakonatsumi.interfaces.*+",defaultImpl=DefaultImpl.class)
private static MyInterface myInterface;
// 之后可以直接通过this(ref)在pointcut表达式中获取服务对象也可以通过getBean方法获取容器中的对象
}
```
- ## @RestControllerAdvice的使用
- @RestControllerAdvice是@Componnent注解的一个特例@RestControllerAdivce注解的组成包含@Component
- @RestControllerAdivce组合了@ControllerAdvice和@ResponseBody两个注解
- 通常,@RestControllerAdvice用作为spring mvc的所有方法做ExceptionHandler

View File

@@ -1,183 +1,183 @@
# Spring Core IOC
- ## IOC容器和bean简介
- IOC简介
- IOC控制反转也被称之为依赖注入DI对象通过构造函数参数、工厂方法参数、或者在构造后通过setter来设置属性来定义依赖。在对象被创建时IOC容器会将依赖注入到bean对象中
- IOC容器
- IOC容器接口
- BeanFactoryBeanFactory是一个接口提供了高级配置功能来管理任何类型的对象
- ApplicationContextApplicationContext是BeanFactory的一个子接口在BeanFactory的基础上其添加了一些更为特殊的特性。
- IOC容器职责
- IOC容器负责来初始化、配置、组装bean对象
- ## 基于注解的Spring容器配置
- @Required
- @Required应用于bean对象属性的setter方法表示该属性在配置时必须被填充通过依赖注入或是用xml定义bean时显式指定值
- 该注解当前已经被弃用
- @Autowired
- 通过在构造函数上标明@Autowired来对方法参数进行注入
- 当在构造函数上标记@Autowired时,如果当前类中只有一个构造函数,那么@Autowired注解可以被省略;如果当前类有多个构造函数,那么应该在某个构造函数上指明@Autowired注解
```java
@Component
class Shiromiya {
private JdbcTemplate jdbcTemplate;
@Autowired
public Shiromiya(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
}
```
- 同样可以在setter上应用@Component
```java
@Component
class Shiromiya {
private JdbcTemplate jdbcTemplate;
@Autowired
public setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
}
```
- 将@Autowired应用于字段
```java
@Component
class Shiromiya {
@Autowired
private JdbcTemplate jdbcTemplate;
}
```
- 通过@Autowired获取相同类型的所有bean对象
```java
@Component
class Shiromiya {
private String[] waifus;
/*
* 这里同样也支持Collections类型
* 例如 List<String>
*/
@Autowired
public Shiromiya(String[] waifus) {
this.waifus=waifus;
}
}
// 同样可以通过Map类型来获取所有相同类型bean对象的name和value
// key对应bean对象的name
// value对应该bean对象的值
@Component
class Shiromiya {
private Map<String,String> waifus;
@Autowired
public void setWaifus(Map<String,String> waifus) {
this.waifus=waifus;
}
}
```
- 在@Autowired标记在构造函数上时即使required为true在参数为多bean类型时即使没有匹配的bean该属性会赋值为{}(空集合)而不是抛出异常
- @Autowired作用与构造函数的规则
- 当required属性为其默认值true时在bean类型中只有一个构造函数可以用@Autowired标注
- 如果bean类型中有多个构造函数标注了@Autowired注解那么那么他们都必须将required属性设置为false并且所有标注了@Autowired属性的构造函数都会被视为依赖注入的候选构造函数
- 如果有多个候选的构造函数那么在IOC容器中可以满足的匹配bean最多的构造函数将会被选中
- 如果没有候选函数被选中,那么其会采用默认构造函数,如无默认构造函数,则抛出异常
- 如果bean有多个构造函数并且所有构造函数都没有标明@Autowired注解那么会采用默认构造函数如果默认构造函数不存在抛出异常
- 如果bean类型只有一个构造函数那么该构造函数会被用来进行依赖注入即使该构造函数没有标注@Autowired注解
- 除了使用@Autowired的required属性还可以使用@Nullable注解来标注可为空
```java
@Component
public class Shiromiya {
@Autowired
@Nullable
private int num;
}
```
- @Primary
- @Autowired注解是通过类型注入如果相同类型存在多个bean时可以通过@Primary注解来表明一个primary bean
```java
@Configuration
public class BeanConfiguration {
@Bean
@Primary
public String name_1() {
return "kazusa";
}
@Bean
public String name_2() {
return "ogiso";
}
}
/*
* 此时,若通过@Autowired注入String类型“kazusa”将会是默认值
*/
```
- @Qualifier
- 可以通过@Qualifier来指定bean的name导入特定bean并且可以为bean指定默认的qualifier
```java
@Component
public class Shiromiya {
@Autowired
@Qualifier("name_2")
private String n_2;
private String n_1;
@Autowired
public Shiromiya(@Qualifier("name_1") String n) {
this.n_1=n;
}
}
```
- bean对象的qualifier并不需要唯一可以为不同的bean对象赋值相同的qualifier并且在注入bean集合的时候根据qualifier过滤
```java
@Configuration
@Qualifier("config")
class BeanConfiguration {
@Bean
@Qualifier("name")
public String name_1() {
return "kazusa";
}
@Bean
@Qualifier("name")
public String name_2() {
return "ogiso";
}
@Bean
@Qualifier("not-name")
public String not_name_1() {
return "fuck";
}
}
@Component
public class Person {
/* 此nameList属性会注入qualifier为name的所有bean
* 在此处为"kazusa"和"ogiso"
*/
@Autowired
@Qualifier("name")
Map<String,String> nameList;
@Autowired
@Qualifier("config")
BeanConfiguration conf;
@Override
public String toString() {
return "Person{" +
"nameList=" + nameList +
", conf=" + conf +
'}';
}
}
```
- 作为一种回退机制当bean的qualifier未被定义时bean的name属性将会被作为其qualifierautowired时会根据@Qualifier注解中指定的值匹配具有相同name的bean对象
- 若想根据bean的name进行匹配无需@Qualifier注解只需要将注入点的name(filed的变量名标注为@Autowired函数的形参名)和bean的name进行比较如果相同则匹配成功否则匹配失败
- @Autowired同样支持自身引用的注入但是自身引用的注入只能作为一种fallback机制。如果当前IOC容器中存在其他的同类型对象那么其他对象会被优先注入对象自己并不会参与候选的对象注入。但是如果IOC中并不存在其他同类型对象那么自身对象将会被作为引用注入。
- @Resource
- @Resource标签类似于@Autowired标签,但是@Resource具有一个name属性用来匹配bean对象的name属性
# Spring Core IOC
- ## IOC容器和bean简介
- IOC简介
- IOC控制反转也被称之为依赖注入DI对象通过构造函数参数、工厂方法参数、或者在构造后通过setter来设置属性来定义依赖。在对象被创建时IOC容器会将依赖注入到bean对象中
- IOC容器
- IOC容器接口
- BeanFactoryBeanFactory是一个接口提供了高级配置功能来管理任何类型的对象
- ApplicationContextApplicationContext是BeanFactory的一个子接口在BeanFactory的基础上其添加了一些更为特殊的特性。
- IOC容器职责
- IOC容器负责来初始化、配置、组装bean对象
- ## 基于注解的Spring容器配置
- @Required
- @Required应用于bean对象属性的setter方法表示该属性在配置时必须被填充通过依赖注入或是用xml定义bean时显式指定值
- 该注解当前已经被弃用
- @Autowired
- 通过在构造函数上标明@Autowired来对方法参数进行注入
- 当在构造函数上标记@Autowired时,如果当前类中只有一个构造函数,那么@Autowired注解可以被省略;如果当前类有多个构造函数,那么应该在某个构造函数上指明@Autowired注解
```java
@Component
class Shiromiya {
private JdbcTemplate jdbcTemplate;
@Autowired
public Shiromiya(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
}
```
- 同样可以在setter上应用@Component
```java
@Component
class Shiromiya {
private JdbcTemplate jdbcTemplate;
@Autowired
public setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
}
```
- 将@Autowired应用于字段
```java
@Component
class Shiromiya {
@Autowired
private JdbcTemplate jdbcTemplate;
}
```
- 通过@Autowired获取相同类型的所有bean对象
```java
@Component
class Shiromiya {
private String[] waifus;
/*
* 这里同样也支持Collections类型
* 例如 List<String>
*/
@Autowired
public Shiromiya(String[] waifus) {
this.waifus=waifus;
}
}
// 同样可以通过Map类型来获取所有相同类型bean对象的name和value
// key对应bean对象的name
// value对应该bean对象的值
@Component
class Shiromiya {
private Map<String,String> waifus;
@Autowired
public void setWaifus(Map<String,String> waifus) {
this.waifus=waifus;
}
}
```
- 在@Autowired标记在构造函数上时即使required为true在参数为多bean类型时即使没有匹配的bean该属性会赋值为{}(空集合)而不是抛出异常
- @Autowired作用与构造函数的规则
- 当required属性为其默认值true时在bean类型中只有一个构造函数可以用@Autowired标注
- 如果bean类型中有多个构造函数标注了@Autowired注解那么那么他们都必须将required属性设置为false并且所有标注了@Autowired属性的构造函数都会被视为依赖注入的候选构造函数
- 如果有多个候选的构造函数那么在IOC容器中可以满足的匹配bean最多的构造函数将会被选中
- 如果没有候选函数被选中,那么其会采用默认构造函数,如无默认构造函数,则抛出异常
- 如果bean有多个构造函数并且所有构造函数都没有标明@Autowired注解那么会采用默认构造函数如果默认构造函数不存在抛出异常
- 如果bean类型只有一个构造函数那么该构造函数会被用来进行依赖注入即使该构造函数没有标注@Autowired注解
- 除了使用@Autowired的required属性还可以使用@Nullable注解来标注可为空
```java
@Component
public class Shiromiya {
@Autowired
@Nullable
private int num;
}
```
- @Primary
- @Autowired注解是通过类型注入如果相同类型存在多个bean时可以通过@Primary注解来表明一个primary bean
```java
@Configuration
public class BeanConfiguration {
@Bean
@Primary
public String name_1() {
return "kazusa";
}
@Bean
public String name_2() {
return "ogiso";
}
}
/*
* 此时,若通过@Autowired注入String类型“kazusa”将会是默认值
*/
```
- @Qualifier
- 可以通过@Qualifier来指定bean的name导入特定bean并且可以为bean指定默认的qualifier
```java
@Component
public class Shiromiya {
@Autowired
@Qualifier("name_2")
private String n_2;
private String n_1;
@Autowired
public Shiromiya(@Qualifier("name_1") String n) {
this.n_1=n;
}
}
```
- bean对象的qualifier并不需要唯一可以为不同的bean对象赋值相同的qualifier并且在注入bean集合的时候根据qualifier过滤
```java
@Configuration
@Qualifier("config")
class BeanConfiguration {
@Bean
@Qualifier("name")
public String name_1() {
return "kazusa";
}
@Bean
@Qualifier("name")
public String name_2() {
return "ogiso";
}
@Bean
@Qualifier("not-name")
public String not_name_1() {
return "fuck";
}
}
@Component
public class Person {
/* 此nameList属性会注入qualifier为name的所有bean
* 在此处为"kazusa"和"ogiso"
*/
@Autowired
@Qualifier("name")
Map<String,String> nameList;
@Autowired
@Qualifier("config")
BeanConfiguration conf;
@Override
public String toString() {
return "Person{" +
"nameList=" + nameList +
", conf=" + conf +
'}';
}
}
```
- 作为一种回退机制当bean的qualifier未被定义时bean的name属性将会被作为其qualifierautowired时会根据@Qualifier注解中指定的值匹配具有相同name的bean对象
- 若想根据bean的name进行匹配无需@Qualifier注解只需要将注入点的name(filed的变量名标注为@Autowired函数的形参名)和bean的name进行比较如果相同则匹配成功否则匹配失败
- @Autowired同样支持自身引用的注入但是自身引用的注入只能作为一种fallback机制。如果当前IOC容器中存在其他的同类型对象那么其他对象会被优先注入对象自己并不会参与候选的对象注入。但是如果IOC中并不存在其他同类型对象那么自身对象将会被作为引用注入。
- @Resource
- @Resource标签类似于@Autowired标签,但是@Resource具有一个name属性用来匹配bean对象的name属性
- @Resource标签首先会对具有相同name的bean对象如果没有匹配到具有相同name的bean对象才会fallback到类型匹配

View File

@@ -1,71 +1,71 @@
# Spring Data Access
- ## Spring事务
- 本地事务和全局事务:
- 全局事务:全局事务允许使用多个事务资源,应用服务器来对全局事务进行管理
- 本地事务:本地事务无法管理多个事务资源
- 本地事务和全局事务的优缺点
- 全局事务的使用需要和服务器环境相绑定,降低了代码的重用性
- 本地事务无法使用多个事务资源无法通过JTA等框架来对多个事务资源进行管理无法使用分布式事务
- ## 声明式事务
- Spring中声明式事务是通过AOP来实现的
- 在声明式事务中,可以为方法级的粒度指定事务行为
- 声明式事务的回滚规则:
- 在Spring声明式事务中可以为事务指定回滚规则即指定针对哪些异常事务会自动执行回滚操作
- 在默认情况下只有抛出unchecked异常通常为RuntimeException声明式事务才会进行回滚
- 声明式事务的实现细节:
- 声明式事务通过aop代理实现并且事务的advice是通过xml元数据配置来驱动的
- aop和事务元数据联合产生了一个aop代理对象并且该代理对象通过使用TransactionInterceptor和TransactionManager来实现事务
- @Transactional通常和线程绑定的事务一起工作线程绑定的事务由PlatformTransactionManager管理。@Transactional会将事务暴露给当前执行线程中所有的dao操作
- 声明式事务的回滚:
- 在Spring事务中推荐让事务回滚的方式是在事务执行的方法中抛出一个异常
- Spring事务在默认情况下只会针对unchecked异常RuntimeException进行回滚对于ErrorSpring事务也会执行回滚操作
- checked异常并不会导致事务的回滚操作可以注册rollback rule来指定对特定的异常包括checked异常进行回滚操作
- rollback rule
- 回滚规则rollback rule通常用来指定当一个异常被抛出时是否为该异常执行事务的回滚操作
-@Transactional注解中可以指定rollbackFor/noRollbackFor、rollbackForClassName/noRollbackForClassName来指定为那些异常类执行回滚操作
> 当指定rollbackFor属性为checked异常时如rollbackFor=FileNotFoundException.class)此时指定的异常不会覆盖其默认行为为RuntimeException和Error异常执行回滚操作
> 故而指定后其默认会为Error、RuntimeException、FileNotFoundException三类异常执行回滚操作
```java
@Transactional(rollbackFor={MyException.class})
public void myOperation() {
// ...
}
```
- ## 基于注解的声明式事务
- @Transactional既可以作用于类上,也可以作用于方法上。当作用于类上时,该声明类中所有的方法都会被声明是事务的,该类子类中所有的方法也都是事务的
- @Transactional是可以继承的,被@Inherited元注解修饰
>@Inherited类是元注解,用来修饰注解类。如果一个注解类被@Inherited注解标识那么在对class查询该注解类时如果当前class没有声明该注解将会在当前class的父类中查找该注解依次递归。直到在父类中找到该注解或是到达继承结构的顶部Object类@Inherited标注的注解仅能在类继承中有效,如注解被标注在接口上,那么将@Inherited标注的注解将不会被递归查询到
- 并且class级别的@Transactional并不应用在从父类继承的方法上,即若一个类被@Transactional注解标注,并且该类从父类继承方法,那么该类从父类继承的方法并不会被看作是事务的,除非在该类中重新声明继承的方法。
- 通过在Configuration类上注解@EnableTransactionManagement,配合@Transactional可以将一个bean对象声明是事务的
- 当基于标准spring配置时应该仅将@Transactional注解标注于public方法,当将@Transactional注解标注于非public方法时无效
- 在Spring中仅推荐将@Transactional注解应用于类上不推荐将其应用在接口接口方法上。如果将其应用在接口上那么该事务配置仅仅对基于接口的动态代理有效对基于class的代理无效。
- 当类级别和方法级别都设置了@Transactional注解时,方法级别的设置会优先被使用
- ## @Transactional注解的配置
- 事务的传播: 默认情况下,@Transactional的propagation属性是PROPAGATION_REQUIRED
- 事务的隔离级别: 默认情况下,@Transactional的isolation属性是ISOLATION_DEFAULT,使用数据库默认的隔离级别
- readOnly 默认情况下,@Transactional的readOnly属性是false,默认事务是可读写的
- timeout 默认况下下,@Transactional的超时属性取决于底层的事务系统如果底层事务系统不支持timeout则timeout属性为none
- rollbackFor 默认情况下,@Transactional会针对unchecked异常和Error进行回滚操作
- transactionManager 默认情况下,@Transactional注解会使用项目中默认的事务管理器即bean name为transactionManager的事务管理器。可以为@Transactional注解指定value属性或是transactionManager属性来指定想要采用的事务管理器的bean name或是qualifier
- ## Transaction Propagation
- ### PROPAGATION.REQUIRED
- 在Spring中事务的传播行为默认是PROPAGATION_REQUIRED默认情况下该选项会强制的要求一个物理事务
- 如果当前作用域中不存在事务,那么会创建一个新的事务
- 如果当前作用域的外层作用域已经存在事务,那么会加入到当前作用域的事务中去
- 在Spring中默认情况下当嵌套事务加入到外层的事务中时会忽略内层事务定义的隔离级别、timeout设置和读写标志等。
> 如果想要对外层事务进行验证可以手动将事务管理器的validateExistingTransaction属性设置为true。这样当加入到一个隔离级别与内层事务完全不同的外层事务中时该加入操作会被拒绝。在该设置下如果read-write内层事务想要加入到外层的read-only事务中时该加入操作也会被拒绝。
- 在事务传播行为被设置为PROPAGATION_REQUIRED的情况下会为每个被设置事务的方法创建一个逻辑的事务作用域。各个逻辑事务作用域之间都是相互独立的在不同逻辑事务作用域之间都可以独立设置事务的rollback-only属性。但是在PROPAGATION_REQUIRED的情况下内层事务和外层事务都映射到同一个物理事务内层事务加入到外层事务中故而在内层逻辑事务中为物理事务设置rollback-only会切实影响到外层事务的提交。
- 当事务传播行为被设置为PROPAGATION_REQUIRED时如果内层事务设置了rollback-only标记那么会导致外层物理事务的回滚。当外层事务尝试提交并失败回滚后会抛出一个UnexceptedRollbackException异常外层事务commit方法的调用者会接受到该UnexceptedRollbackException代表内层发生了非预期的回滚操作
- ### PROPAGATION.REQUIRES_NEW
- 相对于PROPAGATION_REQUIREDPROPAGATION.REQUIRES_NEW传播行为会一直使用独立的物理事务而不会尝试区加入外部已经存在的物理事务。
- 对于PROPAGATION_NEW,其内层事务和外层事务都可以独立的提交或回滚,内层事务的回滚并不会导致外层事务的回滚。
- 将事务传播行为设置为PROPAGATION.REQUIRES_NEW时内层事务可以独立定义自己的隔离级别、timeout值、read-only属性而不必继承外部事务的这些属性。在PROPAGATION_REQUIRED中内部事务自定义这些属性将会被忽略内部事务加入外部事务后会采用外部事务的设置。
- ### PROPAGATION.NESTED
- 和PROPAGATION_REQUIRED类似PROPAGATION_NESTED同样也只有一个物理事务。但是其支持多个savepoint存档点该物理事务可以回滚到特定的存档点而非必须回滚整个事务。
- 由于PROPAGATION_NESTED对存档点的支持故而在PROPAGATION_NESTED条件下可以进行部分回滚。内层事务的回滚操作并不会造成外部事务的回滚内层事务回滚后外层事务仍然能够继续执行和提交。
> 由于PROPAGATION_NESTED需要JDBC savepoint存档点的支持故而该设置仅仅对JDBC事务资源有效。
> 当事务被回滚之后,当前事务无法再被提交,故而:
> 若在子事务中已经回滚子事务传播行为为required那么父事务的状态已经被回滚即使父事务捕获子事务抛出的异常那么在捕获异常之后执行的sql操作也不会被提交到数据库中父事务状态处于已回滚无法再次提交
> ***但是当子事务传播行为为nested时子事务虽然和父事务共用一个事务子事务回滚时只会回滚到子事务开启之前的存档点父事务在捕获子事务抛出异常之后执行的sql语句仍然可以被提交***
# Spring Data Access
- ## Spring事务
- 本地事务和全局事务:
- 全局事务:全局事务允许使用多个事务资源,应用服务器来对全局事务进行管理
- 本地事务:本地事务无法管理多个事务资源
- 本地事务和全局事务的优缺点
- 全局事务的使用需要和服务器环境相绑定,降低了代码的重用性
- 本地事务无法使用多个事务资源无法通过JTA等框架来对多个事务资源进行管理无法使用分布式事务
- ## 声明式事务
- Spring中声明式事务是通过AOP来实现的
- 在声明式事务中,可以为方法级的粒度指定事务行为
- 声明式事务的回滚规则:
- 在Spring声明式事务中可以为事务指定回滚规则即指定针对哪些异常事务会自动执行回滚操作
- 在默认情况下只有抛出unchecked异常通常为RuntimeException声明式事务才会进行回滚
- 声明式事务的实现细节:
- 声明式事务通过aop代理实现并且事务的advice是通过xml元数据配置来驱动的
- aop和事务元数据联合产生了一个aop代理对象并且该代理对象通过使用TransactionInterceptor和TransactionManager来实现事务
- @Transactional通常和线程绑定的事务一起工作线程绑定的事务由PlatformTransactionManager管理。@Transactional会将事务暴露给当前执行线程中所有的dao操作
- 声明式事务的回滚:
- 在Spring事务中推荐让事务回滚的方式是在事务执行的方法中抛出一个异常
- Spring事务在默认情况下只会针对unchecked异常RuntimeException进行回滚对于ErrorSpring事务也会执行回滚操作
- checked异常并不会导致事务的回滚操作可以注册rollback rule来指定对特定的异常包括checked异常进行回滚操作
- rollback rule
- 回滚规则rollback rule通常用来指定当一个异常被抛出时是否为该异常执行事务的回滚操作
-@Transactional注解中可以指定rollbackFor/noRollbackFor、rollbackForClassName/noRollbackForClassName来指定为那些异常类执行回滚操作
> 当指定rollbackFor属性为checked异常时如rollbackFor=FileNotFoundException.class)此时指定的异常不会覆盖其默认行为为RuntimeException和Error异常执行回滚操作
> 故而指定后其默认会为Error、RuntimeException、FileNotFoundException三类异常执行回滚操作
```java
@Transactional(rollbackFor={MyException.class})
public void myOperation() {
// ...
}
```
- ## 基于注解的声明式事务
- @Transactional既可以作用于类上,也可以作用于方法上。当作用于类上时,该声明类中所有的方法都会被声明是事务的,该类子类中所有的方法也都是事务的
- @Transactional是可以继承的,被@Inherited元注解修饰
>@Inherited类是元注解,用来修饰注解类。如果一个注解类被@Inherited注解标识那么在对class查询该注解类时如果当前class没有声明该注解将会在当前class的父类中查找该注解依次递归。直到在父类中找到该注解或是到达继承结构的顶部Object类@Inherited标注的注解仅能在类继承中有效,如注解被标注在接口上,那么将@Inherited标注的注解将不会被递归查询到
- 并且class级别的@Transactional并不应用在从父类继承的方法上,即若一个类被@Transactional注解标注,并且该类从父类继承方法,那么该类从父类继承的方法并不会被看作是事务的,除非在该类中重新声明继承的方法。
- 通过在Configuration类上注解@EnableTransactionManagement,配合@Transactional可以将一个bean对象声明是事务的
- 当基于标准spring配置时应该仅将@Transactional注解标注于public方法,当将@Transactional注解标注于非public方法时无效
- 在Spring中仅推荐将@Transactional注解应用于类上不推荐将其应用在接口接口方法上。如果将其应用在接口上那么该事务配置仅仅对基于接口的动态代理有效对基于class的代理无效。
- 当类级别和方法级别都设置了@Transactional注解时,方法级别的设置会优先被使用
- ## @Transactional注解的配置
- 事务的传播: 默认情况下,@Transactional的propagation属性是PROPAGATION_REQUIRED
- 事务的隔离级别: 默认情况下,@Transactional的isolation属性是ISOLATION_DEFAULT,使用数据库默认的隔离级别
- readOnly 默认情况下,@Transactional的readOnly属性是false,默认事务是可读写的
- timeout 默认况下下,@Transactional的超时属性取决于底层的事务系统如果底层事务系统不支持timeout则timeout属性为none
- rollbackFor 默认情况下,@Transactional会针对unchecked异常和Error进行回滚操作
- transactionManager 默认情况下,@Transactional注解会使用项目中默认的事务管理器即bean name为transactionManager的事务管理器。可以为@Transactional注解指定value属性或是transactionManager属性来指定想要采用的事务管理器的bean name或是qualifier
- ## Transaction Propagation
- ### PROPAGATION.REQUIRED
- 在Spring中事务的传播行为默认是PROPAGATION_REQUIRED默认情况下该选项会强制的要求一个物理事务
- 如果当前作用域中不存在事务,那么会创建一个新的事务
- 如果当前作用域的外层作用域已经存在事务,那么会加入到当前作用域的事务中去
- 在Spring中默认情况下当嵌套事务加入到外层的事务中时会忽略内层事务定义的隔离级别、timeout设置和读写标志等。
> 如果想要对外层事务进行验证可以手动将事务管理器的validateExistingTransaction属性设置为true。这样当加入到一个隔离级别与内层事务完全不同的外层事务中时该加入操作会被拒绝。在该设置下如果read-write内层事务想要加入到外层的read-only事务中时该加入操作也会被拒绝。
- 在事务传播行为被设置为PROPAGATION_REQUIRED的情况下会为每个被设置事务的方法创建一个逻辑的事务作用域。各个逻辑事务作用域之间都是相互独立的在不同逻辑事务作用域之间都可以独立设置事务的rollback-only属性。但是在PROPAGATION_REQUIRED的情况下内层事务和外层事务都映射到同一个物理事务内层事务加入到外层事务中故而在内层逻辑事务中为物理事务设置rollback-only会切实影响到外层事务的提交。
- 当事务传播行为被设置为PROPAGATION_REQUIRED时如果内层事务设置了rollback-only标记那么会导致外层物理事务的回滚。当外层事务尝试提交并失败回滚后会抛出一个UnexceptedRollbackException异常外层事务commit方法的调用者会接受到该UnexceptedRollbackException代表内层发生了非预期的回滚操作
- ### PROPAGATION.REQUIRES_NEW
- 相对于PROPAGATION_REQUIREDPROPAGATION.REQUIRES_NEW传播行为会一直使用独立的物理事务而不会尝试区加入外部已经存在的物理事务。
- 对于PROPAGATION_NEW,其内层事务和外层事务都可以独立的提交或回滚,内层事务的回滚并不会导致外层事务的回滚。
- 将事务传播行为设置为PROPAGATION.REQUIRES_NEW时内层事务可以独立定义自己的隔离级别、timeout值、read-only属性而不必继承外部事务的这些属性。在PROPAGATION_REQUIRED中内部事务自定义这些属性将会被忽略内部事务加入外部事务后会采用外部事务的设置。
- ### PROPAGATION.NESTED
- 和PROPAGATION_REQUIRED类似PROPAGATION_NESTED同样也只有一个物理事务。但是其支持多个savepoint存档点该物理事务可以回滚到特定的存档点而非必须回滚整个事务。
- 由于PROPAGATION_NESTED对存档点的支持故而在PROPAGATION_NESTED条件下可以进行部分回滚。内层事务的回滚操作并不会造成外部事务的回滚内层事务回滚后外层事务仍然能够继续执行和提交。
> 由于PROPAGATION_NESTED需要JDBC savepoint存档点的支持故而该设置仅仅对JDBC事务资源有效。
> 当事务被回滚之后,当前事务无法再被提交,故而:
> 若在子事务中已经回滚子事务传播行为为required那么父事务的状态已经被回滚即使父事务捕获子事务抛出的异常那么在捕获异常之后执行的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库通过Maven引入](#gson库通过maven引入)
- [基本类型的序列化和反序列化](#基本类型的序列化和反序列化)
- [对象的序列化和反序列化](#对象的序列化和反序列化)
- [gson和对象联用的使用规范](#gson和对象联用的使用规范)
- [gson和嵌套类的关联使用](#gson和嵌套类的关联使用)
- [gson和数组的关联使用](#gson和数组的关联使用)
- [gson对java中的集合进行序列化和反序列化](#gson对java中的集合进行序列化和反序列化)
- [gson对Map类型的序列化和反序列化](#gson对map类型的序列化和反序列化)
- [序列化和反序列化泛型对象](#序列化和反序列化泛型对象)
- [序列化和反序列化集合,集合中保存任意类型的对象](#序列化和反序列化集合集合中保存任意类型的对象)
# gson
## gson简介
gson是一个java库通常用来将java对象转化为其json表示的字符串或者将json格式的字符串转化为其等价的java对象。
## gson使用
在gson中使用最频繁的类是Gson。可以通过new Gson()构造函数来创建Gson对象也可以通过GsonBuilder来创建Gson对象GsonBuidler在创建Gson对象时能够指定一些设置如版本控制等。
由于Gson对象在执行json操作时并不会保存任何状态故而Gson对象是线程安全的单一的Gson对象可以在多线程环境下被重复使用。
### Gson库通过Maven引入
```xml
<dependencies>
<!-- Gson: Java to JSON conversion -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
```
### 基本类型的序列化和反序列化
```java
// Serialization
Gson gson = new Gson();
gson.toJson(1); // ==> 1
gson.toJson("abcd"); // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values); // ==> [1]
// Deserialization
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);
```
### 对象的序列化和反序列化
类似于java自带的序列化机制当成员字段被transient修饰时并不会序列化该字段
```java
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
// Deserialization
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 is just like obj
```
### gson和对象联用的使用规范
- gson使用过程中待序列化或反序列化的成员字段可以是private的同时也推荐将待序列化或反序列化的成员字段声明为private
- 没有必要对成员字段使用注解来特定标明在序列化或者反序列化中包含该字段,默认情况下该对象所有成员字段和该对象父类对象所包含的所有字段都会被序列化
- 类似于jdk自带的序列化和反序列化机制如果一个字段被标明为transient该字段将不会被包含到序列化和反序列化过程中
- gson实现能够正确处理字段为空的情况
- 当序列化过程中为null的字段将会被省略
- 当反序列化过程中如果一个字段在json串中并没有被设置反序列化得到的对象中该字段将会被设置为默认值引用类型默认值为null、数字类型默认值为0boolean类型其默认值为false
- 在内部类、匿名类、本地类中关联外部类的字段将会被忽略,并不会包含在序列化和反序列化过程中
### gson和嵌套类的关联使用
gson可以单独的对静态内部类进行序列化和反序列化因为静态内部类并不包含对外部类的引用但是gson无法单独的反序列化内部类因为在反序列化内部类的过程中其无参构造器需要一个指向其外部类对象的引用但是该外部类对象在对内部类进行反序列化时是不可访问的。
可以通过将内部类改为静态的内部类,此时对内部类的反序列化将不会需要指向外部类对象的引用。
### gson和数组的关联使用
gson支持多维数组并且支持任意的复杂元素类型
```java
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
// Serialization
gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 will be same as ints
```
### gson对java中的集合进行序列化和反序列化
gson可以序列化任意对象的集合但是无法对其进行反序列化因为在反序列化时用户没有任何方法去指定其生成的集合中元素的类型。因而需要通过typeToken来告知Gson需要反序列化的类型。
```java
Gson gson = new Gson();
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
// Serialization
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
// Deserialization
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints
```
### gson对Map类型的序列化和反序列化
默认情况下gson会将java中任意的Map实现类型序列化为JSON对象。由于JSON对象其key只支持字符串类型gson会将待序列化的Map key调用toString转化为字符串。如果map中的key为null则序列化后的key为"null"
```java
/**
* gson对map进行序列化
**/
Gson gson = new Gson();
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
stringMap.put(null, "null-entry");
// Serialization
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
Map<Integer, Integer> intMap = new LinkedHashMap<>();
intMap.put(2, 4);
intMap.put(3, 6);
// Serialization
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
```
在反序列化的过程中gson会使用为Map key类型注册的TypeAdapter的read方法来进行反序列化。为了让gson知道反序列化得到的Map对象的key和value类型需要使用TypeToken。
```java
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
String json = "{\"key\": \"value\"}";
// Deserialization
Map<String, String> stringMap = gson.fromJson(json, mapType);
// ==> stringMap is {key=value}
```
默认情况下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对象
> 在对枚举型key进行反序列化的过程中如果enum找不到一个具有匹配name()值的常量时其会采用一个回退机制根据枚举常量的toString()值来进行反序列化的匹配。
### 序列化和反序列化泛型对象
当gson对object对象调用toJson方法时gson会调用object.getClass()来获取需要序列化的字段信息。类似的在gson调用fromJson时会向fromJson方法传递一个MyClass.class对象。该方法在序列化和反序列化类型是非泛型类型时能够正常运行。
但是,当待序列化和反序列化的类型是泛型类型时,在序列化和反序列化对象时泛型类型信息会丢失,因为泛型采用的是类型擦除。
```java
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
```
> 上述代码中foo.getClass()方法返回的只是Foo.class对象并不会包含泛型类型Bar的信息故而在反序列化时gson并不知道应该将value反序列化为Bar类型
可以通过向fromJson中传入Type参数来详细指定想要将json串转化成的泛型类型信息
```java
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
```
### 序列化和反序列化集合,集合中保存任意类型的对象
当JSON串中数组包含各种类型元素时将其转化为包含任意类型的java集合可以有如下方法
- 使用Gson Parser APIJsonParser底层parser api将json串中数组转化为JsonArray并且为每个元素调用Gson.fromJson。该方法是推荐的方法
> gson.fromJson可以针对String、Reader、JsonElement来调用
- [gson](#gson)
- [gson简介](#gson简介)
- [gson使用](#gson使用)
- [Gson库通过Maven引入](#gson库通过maven引入)
- [基本类型的序列化和反序列化](#基本类型的序列化和反序列化)
- [对象的序列化和反序列化](#对象的序列化和反序列化)
- [gson和对象联用的使用规范](#gson和对象联用的使用规范)
- [gson和嵌套类的关联使用](#gson和嵌套类的关联使用)
- [gson和数组的关联使用](#gson和数组的关联使用)
- [gson对java中的集合进行序列化和反序列化](#gson对java中的集合进行序列化和反序列化)
- [gson对Map类型的序列化和反序列化](#gson对map类型的序列化和反序列化)
- [序列化和反序列化泛型对象](#序列化和反序列化泛型对象)
- [序列化和反序列化集合,集合中保存任意类型的对象](#序列化和反序列化集合集合中保存任意类型的对象)
# gson
## gson简介
gson是一个java库通常用来将java对象转化为其json表示的字符串或者将json格式的字符串转化为其等价的java对象。
## gson使用
在gson中使用最频繁的类是Gson。可以通过new Gson()构造函数来创建Gson对象也可以通过GsonBuilder来创建Gson对象GsonBuidler在创建Gson对象时能够指定一些设置如版本控制等。
由于Gson对象在执行json操作时并不会保存任何状态故而Gson对象是线程安全的单一的Gson对象可以在多线程环境下被重复使用。
### Gson库通过Maven引入
```xml
<dependencies>
<!-- Gson: Java to JSON conversion -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
```
### 基本类型的序列化和反序列化
```java
// Serialization
Gson gson = new Gson();
gson.toJson(1); // ==> 1
gson.toJson("abcd"); // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values); // ==> [1]
// Deserialization
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);
```
### 对象的序列化和反序列化
类似于java自带的序列化机制当成员字段被transient修饰时并不会序列化该字段
```java
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
// Deserialization
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 is just like obj
```
### gson和对象联用的使用规范
- gson使用过程中待序列化或反序列化的成员字段可以是private的同时也推荐将待序列化或反序列化的成员字段声明为private
- 没有必要对成员字段使用注解来特定标明在序列化或者反序列化中包含该字段,默认情况下该对象所有成员字段和该对象父类对象所包含的所有字段都会被序列化
- 类似于jdk自带的序列化和反序列化机制如果一个字段被标明为transient该字段将不会被包含到序列化和反序列化过程中
- gson实现能够正确处理字段为空的情况
- 当序列化过程中为null的字段将会被省略
- 当反序列化过程中如果一个字段在json串中并没有被设置反序列化得到的对象中该字段将会被设置为默认值引用类型默认值为null、数字类型默认值为0boolean类型其默认值为false
- 在内部类、匿名类、本地类中关联外部类的字段将会被忽略,并不会包含在序列化和反序列化过程中
### gson和嵌套类的关联使用
gson可以单独的对静态内部类进行序列化和反序列化因为静态内部类并不包含对外部类的引用但是gson无法单独的反序列化内部类因为在反序列化内部类的过程中其无参构造器需要一个指向其外部类对象的引用但是该外部类对象在对内部类进行反序列化时是不可访问的。
可以通过将内部类改为静态的内部类,此时对内部类的反序列化将不会需要指向外部类对象的引用。
### gson和数组的关联使用
gson支持多维数组并且支持任意的复杂元素类型
```java
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
// Serialization
gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 will be same as ints
```
### gson对java中的集合进行序列化和反序列化
gson可以序列化任意对象的集合但是无法对其进行反序列化因为在反序列化时用户没有任何方法去指定其生成的集合中元素的类型。因而需要通过typeToken来告知Gson需要反序列化的类型。
```java
Gson gson = new Gson();
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
// Serialization
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
// Deserialization
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints
```
### gson对Map类型的序列化和反序列化
默认情况下gson会将java中任意的Map实现类型序列化为JSON对象。由于JSON对象其key只支持字符串类型gson会将待序列化的Map key调用toString转化为字符串。如果map中的key为null则序列化后的key为"null"
```java
/**
* gson对map进行序列化
**/
Gson gson = new Gson();
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
stringMap.put(null, "null-entry");
// Serialization
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
Map<Integer, Integer> intMap = new LinkedHashMap<>();
intMap.put(2, 4);
intMap.put(3, 6);
// Serialization
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
```
在反序列化的过程中gson会使用为Map key类型注册的TypeAdapter的read方法来进行反序列化。为了让gson知道反序列化得到的Map对象的key和value类型需要使用TypeToken。
```java
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
String json = "{\"key\": \"value\"}";
// Deserialization
Map<String, String> stringMap = gson.fromJson(json, mapType);
// ==> stringMap is {key=value}
```
默认情况下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对象
> 在对枚举型key进行反序列化的过程中如果enum找不到一个具有匹配name()值的常量时其会采用一个回退机制根据枚举常量的toString()值来进行反序列化的匹配。
### 序列化和反序列化泛型对象
当gson对object对象调用toJson方法时gson会调用object.getClass()来获取需要序列化的字段信息。类似的在gson调用fromJson时会向fromJson方法传递一个MyClass.class对象。该方法在序列化和反序列化类型是非泛型类型时能够正常运行。
但是,当待序列化和反序列化的类型是泛型类型时,在序列化和反序列化对象时泛型类型信息会丢失,因为泛型采用的是类型擦除。
```java
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
```
> 上述代码中foo.getClass()方法返回的只是Foo.class对象并不会包含泛型类型Bar的信息故而在反序列化时gson并不知道应该将value反序列化为Bar类型
可以通过向fromJson中传入Type参数来详细指定想要将json串转化成的泛型类型信息
```java
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
```
### 序列化和反序列化集合,集合中保存任意类型的对象
当JSON串中数组包含各种类型元素时将其转化为包含任意类型的java集合可以有如下方法
- 使用Gson Parser APIJsonParser底层parser api将json串中数组转化为JsonArray并且为每个元素调用Gson.fromJson。该方法是推荐的方法
> gson.fromJson可以针对String、Reader、JsonElement来调用

View File

@@ -1,85 +1,85 @@
- [Spring Logging](#spring-logging)
- [Log Format](#log-format)
- [控制台输出](#控制台输出)
- [文件输出](#文件输出)
- [File Rotation](#file-rotation)
- [Log Level](#log-level)
- [Log Group](#log-group)
# Spring Logging
## Log Format
默认Spring Boot输出日志的格式如下
```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.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.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: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: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)
```
- 日期与时间 精度为ms
- log level ERROR, WARN, INFO, DEBUG, TRACE
- 进程ID
- 线程名称 [main]
- logger name : 输出日志类的类名(通常为缩写)
- log信息
## 控制台输出
默认情况下log日志信息会回显输出到console中默认ERROR, WARN, DEBUG三个级别的信息将会被日志输出。
可以通过--debug选项来启用“debug”模式
```shell
java -jar xxx.jar --debug
```
通过在application.properties中指定debug=true也可以开启“debug”模式
```properties
debug=true
```
当“debug”模式被开启后一部分核心的logger内嵌容器、Hibernate、SpringBoot将会被配置输出更多的信息。
> ***开启debug模式并不意味着输出所有日志级别为Debug的信息***
> ***同样,也可以通过--trace或者在properties中指定trace=true来开启trace模式***
## 文件输出
默认情况下Spring Boot只会将日志输出到console中如果想要额外定义将日志输出到文件中需要在application.properties中定义logging.file.name或者logging.file.path
| logging.file.name | logging.file.path | example | description |
| :-: | :-: | :-: | :-: |
| (none) | (none) | | 只在控制台输出 |
| 特定文件 | (none) | my.log | 特定log文件路径可以是绝对路径或相对路径 |
| (none) | 特定目录 | /var/log | 将日志输出到该路径下的spring.log文件可以是绝对路径或相对路径|
> 当log文件大小到达10MB时将会旋转重写和console log一样log文件也会输出ERROR, WARN和INFO
> ***logging properties和实际的logging机制是相互独立的因而特定的配置属性如logback.configurationFile)并不由SpringBoot管理***
## File Rotation
如果使用的是Logback则可以在application.properties中定义file rotation行为。
| Name | Description |
| :-: | :-: |
| logging.logback.rollingpolicy.file-name-pattern | 定义创建log归档的命名模式 |
| logging.logback.rollingpolicy.clean-history-on-start | 定义是否应该在项目启动时清理历史日志 |
| logging.logback.rollingpolicy.max-file-size | 定义日志在归档前的最大大小 |
| logging.logback.rollingpolicy.total-size-cap | 日志归档在被删除前可以占用的最大大小 |
| logging.logback.rollingpolicy.max-history | 要保留归档日志文件的最大数量 |
## Log Level
所有的日志系统都可以通过Application.properties定义logging.level.&lt;logger-name&gt;=&lt;level&gt;来定义事务级别事务级别可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。
可以通过logging.level.root来定义root logger的隔离级别。
```properties
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
```
## Log Group
可以通过log group将关联的logger组合在一起并且对log group统一指定日志级别。
```properties
# 定义一个名为“tomcat”的log group
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
# 为名为“tomcat”的log group统一指定log level
logging.level.tomcat=trace
```
> 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
> - sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener
- [Spring Logging](#spring-logging)
- [Log Format](#log-format)
- [控制台输出](#控制台输出)
- [文件输出](#文件输出)
- [File Rotation](#file-rotation)
- [Log Level](#log-level)
- [Log Group](#log-group)
# Spring Logging
## Log Format
默认Spring Boot输出日志的格式如下
```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.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.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: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: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)
```
- 日期与时间 精度为ms
- log level ERROR, WARN, INFO, DEBUG, TRACE
- 进程ID
- 线程名称 [main]
- logger name : 输出日志类的类名(通常为缩写)
- log信息
## 控制台输出
默认情况下log日志信息会回显输出到console中默认ERROR, WARN, DEBUG三个级别的信息将会被日志输出。
可以通过--debug选项来启用“debug”模式
```shell
java -jar xxx.jar --debug
```
通过在application.properties中指定debug=true也可以开启“debug”模式
```properties
debug=true
```
当“debug”模式被开启后一部分核心的logger内嵌容器、Hibernate、SpringBoot将会被配置输出更多的信息。
> ***开启debug模式并不意味着输出所有日志级别为Debug的信息***
> ***同样,也可以通过--trace或者在properties中指定trace=true来开启trace模式***
## 文件输出
默认情况下Spring Boot只会将日志输出到console中如果想要额外定义将日志输出到文件中需要在application.properties中定义logging.file.name或者logging.file.path
| logging.file.name | logging.file.path | example | description |
| :-: | :-: | :-: | :-: |
| (none) | (none) | | 只在控制台输出 |
| 特定文件 | (none) | my.log | 特定log文件路径可以是绝对路径或相对路径 |
| (none) | 特定目录 | /var/log | 将日志输出到该路径下的spring.log文件可以是绝对路径或相对路径|
> 当log文件大小到达10MB时将会旋转重写和console log一样log文件也会输出ERROR, WARN和INFO
> ***logging properties和实际的logging机制是相互独立的因而特定的配置属性如logback.configurationFile)并不由SpringBoot管理***
## File Rotation
如果使用的是Logback则可以在application.properties中定义file rotation行为。
| Name | Description |
| :-: | :-: |
| logging.logback.rollingpolicy.file-name-pattern | 定义创建log归档的命名模式 |
| logging.logback.rollingpolicy.clean-history-on-start | 定义是否应该在项目启动时清理历史日志 |
| logging.logback.rollingpolicy.max-file-size | 定义日志在归档前的最大大小 |
| logging.logback.rollingpolicy.total-size-cap | 日志归档在被删除前可以占用的最大大小 |
| logging.logback.rollingpolicy.max-history | 要保留归档日志文件的最大数量 |
## Log Level
所有的日志系统都可以通过Application.properties定义logging.level.&lt;logger-name&gt;=&lt;level&gt;来定义事务级别事务级别可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。
可以通过logging.level.root来定义root logger的隔离级别。
```properties
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
```
## Log Group
可以通过log group将关联的logger组合在一起并且对log group统一指定日志级别。
```properties
# 定义一个名为“tomcat”的log group
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
# 为名为“tomcat”的log group统一指定log level
logging.level.tomcat=trace
```
> 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
> - sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

View File

@@ -1,13 +1,13 @@
# lombok标签简介
## @NonNull
- 如果用@NonNull标签来修饰一个参数,在方法或构造器的开头会插入一个空校验来检查该参数是否为空。
- 如果将@NonNull标签用来修饰一个field,任何通过注解产生的方法(如@Setter产生的Setter都会在试图分配该field一个值时进行空校验
## @RequiredArgsConstructor
被该注解修饰的类会产生一个包含required args的构造器。required args包含final field和被特殊约束的field(如被@NonNull约束)
## @ToString
产生一个toString方法的实现并且该实现会被所有对象继承
## @Data
@Data是一个快捷的注解,其将@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor等注解整合到了一起
- 对于@Data注解标注的类,如果类中包含一个方法,其方法名和@Data将要产生的方法相同并且参数个数也相同(不需要参数类型相同),那么该方法将不会被产生,并且不会产生任何警告或错误
- 对于@Data标注的类,如果该类显示声明了一个构造器,那么@Data不会再生成任何构造器
# lombok标签简介
## @NonNull
- 如果用@NonNull标签来修饰一个参数,在方法或构造器的开头会插入一个空校验来检查该参数是否为空。
- 如果将@NonNull标签用来修饰一个field,任何通过注解产生的方法(如@Setter产生的Setter都会在试图分配该field一个值时进行空校验
## @RequiredArgsConstructor
被该注解修饰的类会产生一个包含required args的构造器。required args包含final field和被特殊约束的field(如被@NonNull约束)
## @ToString
产生一个toString方法的实现并且该实现会被所有对象继承
## @Data
@Data是一个快捷的注解,其将@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor等注解整合到了一起
- 对于@Data注解标注的类,如果类中包含一个方法,其方法名和@Data将要产生的方法相同并且参数个数也相同(不需要参数类型相同),那么该方法将不会被产生,并且不会产生任何警告或错误
- 对于@Data标注的类,如果该类显示声明了一个构造器,那么@Data不会再生成任何构造器
- 可以通过为@Data标注类中的方法添加@lombok.experimental.Tolerate来为lombok隐藏这些方法

View File

@@ -1,238 +1,238 @@
- [Spring Boot Async](#spring-boot-async)
- [Spring Executor和Scheduler的自动配置](#spring-executor和scheduler的自动配置)
- [Task Execution and Scheduling](#task-execution-and-scheduling)
- [Task Execution Abstraction](#task-execution-abstraction)
- [TaskExecutor接口的实现种类](#taskexecutor接口的实现种类)
- [TaskExecutor的使用](#taskexecutor的使用)
- [Spring TaskScheduler Abstraction](#spring-taskscheduler-abstraction)
- [Trigger接口](#trigger接口)
- [Trigger实现类](#trigger实现类)
- [TaskScheduler实现类](#taskscheduler实现类)
- [对任务调度和异步执行的注解支持](#对任务调度和异步执行的注解支持)
- [启用Scheduling注解](#启用scheduling注解)
- [@Scheduled注解](#scheduled注解)
- [@Async注解](#async注解)
- [@Async方法的异常处理](#async方法的异常处理)
- [Cron表达式](#cron表达式)
- [Macros](#macros宏)
# Spring Boot Async
## Spring Executor和Scheduler的自动配置
当当前上下文中没有Executor类型的bean对象时spring boot会自动配置一个ThreadPoolTaskExecutor类型的bean对象并且将该bean对象和异步task执行@EnableAsync和spring mvc异步请求处理关联在一起。
该默认创建的ThreadPoolTaskExecutor默认使用8个核心线程并且线程数量可以根据负载动态的增加或者减少。
可以通过如下方式对ThreadPoolTaskExecutor进行配置
```properties
# 该线程池最多含有16个线程
spring.task.execution.pool.max-size=16
# 有有界队列存放task上限为100
spring.task.execution.pool.queue-capacity=100
# 当线程空闲10s默认60s时会进行回收
spring.task.execution.pool.keep-alive=10s
# 设置核心线程数量
spring.task.execution.pool.core-size=8
```
如果使用@EnableScheduling一个ThreadPoolTaskScheduler也可以被配置。该线程池默认使用一个线程但是也可以动态设置
```properties
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
```
## Task Execution and Scheduling
Spring通过TaskExecutor和TaskScheduler接口为task的异步执行和调度提供了抽象。
### Task Execution Abstraction
Executor对应的是JDK中线程池的概念。在Spring中TaskExecutor接口和java.util.concurrent.Executor接口相同该接口中只有一个execute方法execute(Runnable task))接受一个task。
### TaskExecutor接口的实现种类
Spring中包含许多预制的TaskExecutor实现类该实现类如下
- SyncTaskExecutor:该实现类不会执行异步的调用所有任务的执行都会发生在调用该executor的线程中。通常使用在不需要多线程的场景
- SimpleAsyncTaskExecutor该实现类不会复用任何的线程相反的对于每次调用该executor都会使用一个全新的线程。但是该实现类的确支持对并发数量的限制该executor会阻塞任何超过并发数量限制的调用直到slot被释放为止。SimpleAsyncTaskExecutor并不支持池化技术
- ConcurrentTaskExecutor该实现类是java.util.concurrent.Executor实例的adapter其可以将java.util.concurrent.Executor实例的配置参数以bean properties的形式暴露。当ThreadPoolTaskExecutor的灵活性无法满足需求时可以使用ConcurrentTaskExecutor
- ThreadPoolTaskExecutor该实现类型是最广泛被使用的。该实现类可以通过bean properties来配置java.util.concurrent.ThreadPoolExecutor实例并且将该实例wrap在TaskExecutor实例中。当想要使用另一种java.util.concurrent.Executor时可以使用ConcurrentTaskExecutor。
- DefaultManagedTaskExecutor该实现使用了通过JNDI获取的ManagedExecutorService
### TaskExecutor的使用
在springboot中当使用@EnableAsync时可以通过配置ThreadPoolExecutor的bean properties来配置线程池的核心线程数和最大线程数等属性。
```properties
# 该线程池最多含有16个线程
spring.task.execution.pool.max-size=16
# 有有界队列存放task上限为100
spring.task.execution.pool.queue-capacity=100
# 当线程空闲10s默认60s时会进行回收
spring.task.execution.pool.keep-alive=10s
# 设置核心线程数量
spring.task.execution.pool.core-size=8
```
## Spring TaskScheduler Abstraction
为了在特定的时间点执行taskSpring引入了TaskScheduler接口接口定义如下
```java
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
```
> scheduleAtFixedRate和scheduleAtFixedDelay区别
> - fixed rate表示两个task执行开始时间的间隔
> - fixed delay表示上一个task结束时间和下一个task开始时间的间隔
>
> 实例如下:
> - fixed rateTTWWWTTTWWT...(开始时间间隔为5)
> - fixed delayTTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为5)
>
> **通常TaskScheduler默认情况下是单线程执行的故而fixed rate执行时如果一个Task执行时间超过period时在当前task执行完成之前下一个task并不会开始执行。下一个task会等待当前task执行完成之后立马执行。**
### Trigger接口
Trigger接口的核心理念是下次执行事件由上次执行的结果决定。上次执行的结果存储在TriggerContext中。
Trigger接口如下
```java
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
```
TriggerContext接口如下其具有默认的实现类SimpleTriggerContext
```java
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
```
### Trigger实现类
Spring为Trigger提供了两个实现类其中CronTrigger允许task的调度按照cron expression来执行类似linux中的crond
```java
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
```
Spring的另一个Trigger实现是PeriodicTrigger其接受一个固定的period期间一个可选的初始delay值并接收一个boolean值标识该period是fixed-rate还是fixed-delay。
### TaskScheduler实现类
如果不需要外部的线程管理可以使用spring提供的ThreadPoolTaskScheduler其会将任务委托给ScheduledExecutorService来提供bean-properties形式的配置。
## 对任务调度和异步执行的注解支持
Spring同时对任务调度和异步方法的执行提供注解支持。
### 启用Scheduling注解
想要启用@Async和@Scheduled注解,必须将@EnableAsync注解和@EnableScheduling注解添加到一个@Configuration类上
```java
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
```
> **@Async注解的实现是通过proxy模式来实现的故而如果在类内部调用位于同一个类中的@Async方法那么代理拦截会失效此时调用的@Async方法将会同步执行而非异步执行**
> @Async可以接收一个value值用于指定目标的执行器Executor或TaskExecutor的beanName
### @Scheduled注解
在将@Scheduled注解标注到方法时可以为其指定一个trigger的元数据使用示例如下
```java
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
```
> 默认情况下fixedDelay、fixedRate、initialDelay的时间单位都是ms可以指定timeUnit来指定其他的时间单位
> ```java
> @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
> public void doSomething() {
> // something that should run periodically
> }
> ```
可以为@Scheduled注解使用cron表达式
```java
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
```
### @Async注解
通过为方法指定@Async注解可以让该方法异步执行该方法的执行通过TaskExecutor。
```java
@Async
void doSomething() {
// this will be run asynchronously
}
```
可以为@Async标注的方法指定参数和返回值但是异步方法的返回值只能为void或是Future类型。
在该异步方法的调用方调用返回Future实例的get方法之前调用方仍然能够执行其他操作异步方法的执行位于TaskExecutor的线程中。
```java
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
```
不能将@Async注解和生命周期回调(例如@PostConstruct进行混用如果想要异步的初始化bean对象需要按照如下方法
```java
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 * * * *) |
- [Spring Boot Async](#spring-boot-async)
- [Spring Executor和Scheduler的自动配置](#spring-executor和scheduler的自动配置)
- [Task Execution and Scheduling](#task-execution-and-scheduling)
- [Task Execution Abstraction](#task-execution-abstraction)
- [TaskExecutor接口的实现种类](#taskexecutor接口的实现种类)
- [TaskExecutor的使用](#taskexecutor的使用)
- [Spring TaskScheduler Abstraction](#spring-taskscheduler-abstraction)
- [Trigger接口](#trigger接口)
- [Trigger实现类](#trigger实现类)
- [TaskScheduler实现类](#taskscheduler实现类)
- [对任务调度和异步执行的注解支持](#对任务调度和异步执行的注解支持)
- [启用Scheduling注解](#启用scheduling注解)
- [@Scheduled注解](#scheduled注解)
- [@Async注解](#async注解)
- [@Async方法的异常处理](#async方法的异常处理)
- [Cron表达式](#cron表达式)
- [Macros](#macros宏)
# Spring Boot Async
## Spring Executor和Scheduler的自动配置
当当前上下文中没有Executor类型的bean对象时spring boot会自动配置一个ThreadPoolTaskExecutor类型的bean对象并且将该bean对象和异步task执行@EnableAsync和spring mvc异步请求处理关联在一起。
该默认创建的ThreadPoolTaskExecutor默认使用8个核心线程并且线程数量可以根据负载动态的增加或者减少。
可以通过如下方式对ThreadPoolTaskExecutor进行配置
```properties
# 该线程池最多含有16个线程
spring.task.execution.pool.max-size=16
# 有有界队列存放task上限为100
spring.task.execution.pool.queue-capacity=100
# 当线程空闲10s默认60s时会进行回收
spring.task.execution.pool.keep-alive=10s
# 设置核心线程数量
spring.task.execution.pool.core-size=8
```
如果使用@EnableScheduling一个ThreadPoolTaskScheduler也可以被配置。该线程池默认使用一个线程但是也可以动态设置
```properties
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
```
## Task Execution and Scheduling
Spring通过TaskExecutor和TaskScheduler接口为task的异步执行和调度提供了抽象。
### Task Execution Abstraction
Executor对应的是JDK中线程池的概念。在Spring中TaskExecutor接口和java.util.concurrent.Executor接口相同该接口中只有一个execute方法execute(Runnable task))接受一个task。
### TaskExecutor接口的实现种类
Spring中包含许多预制的TaskExecutor实现类该实现类如下
- SyncTaskExecutor:该实现类不会执行异步的调用所有任务的执行都会发生在调用该executor的线程中。通常使用在不需要多线程的场景
- SimpleAsyncTaskExecutor该实现类不会复用任何的线程相反的对于每次调用该executor都会使用一个全新的线程。但是该实现类的确支持对并发数量的限制该executor会阻塞任何超过并发数量限制的调用直到slot被释放为止。SimpleAsyncTaskExecutor并不支持池化技术
- ConcurrentTaskExecutor该实现类是java.util.concurrent.Executor实例的adapter其可以将java.util.concurrent.Executor实例的配置参数以bean properties的形式暴露。当ThreadPoolTaskExecutor的灵活性无法满足需求时可以使用ConcurrentTaskExecutor
- ThreadPoolTaskExecutor该实现类型是最广泛被使用的。该实现类可以通过bean properties来配置java.util.concurrent.ThreadPoolExecutor实例并且将该实例wrap在TaskExecutor实例中。当想要使用另一种java.util.concurrent.Executor时可以使用ConcurrentTaskExecutor。
- DefaultManagedTaskExecutor该实现使用了通过JNDI获取的ManagedExecutorService
### TaskExecutor的使用
在springboot中当使用@EnableAsync时可以通过配置ThreadPoolExecutor的bean properties来配置线程池的核心线程数和最大线程数等属性。
```properties
# 该线程池最多含有16个线程
spring.task.execution.pool.max-size=16
# 有有界队列存放task上限为100
spring.task.execution.pool.queue-capacity=100
# 当线程空闲10s默认60s时会进行回收
spring.task.execution.pool.keep-alive=10s
# 设置核心线程数量
spring.task.execution.pool.core-size=8
```
## Spring TaskScheduler Abstraction
为了在特定的时间点执行taskSpring引入了TaskScheduler接口接口定义如下
```java
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
```
> scheduleAtFixedRate和scheduleAtFixedDelay区别
> - fixed rate表示两个task执行开始时间的间隔
> - fixed delay表示上一个task结束时间和下一个task开始时间的间隔
>
> 实例如下:
> - fixed rateTTWWWTTTWWT...(开始时间间隔为5)
> - fixed delayTTWWWWWTTTTWWWWWTTTTTTTWWWWWT...(上次结束和下次开始之间的间隔为5)
>
> **通常TaskScheduler默认情况下是单线程执行的故而fixed rate执行时如果一个Task执行时间超过period时在当前task执行完成之前下一个task并不会开始执行。下一个task会等待当前task执行完成之后立马执行。**
### Trigger接口
Trigger接口的核心理念是下次执行事件由上次执行的结果决定。上次执行的结果存储在TriggerContext中。
Trigger接口如下
```java
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
```
TriggerContext接口如下其具有默认的实现类SimpleTriggerContext
```java
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
```
### Trigger实现类
Spring为Trigger提供了两个实现类其中CronTrigger允许task的调度按照cron expression来执行类似linux中的crond
```java
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
```
Spring的另一个Trigger实现是PeriodicTrigger其接受一个固定的period期间一个可选的初始delay值并接收一个boolean值标识该period是fixed-rate还是fixed-delay。
### TaskScheduler实现类
如果不需要外部的线程管理可以使用spring提供的ThreadPoolTaskScheduler其会将任务委托给ScheduledExecutorService来提供bean-properties形式的配置。
## 对任务调度和异步执行的注解支持
Spring同时对任务调度和异步方法的执行提供注解支持。
### 启用Scheduling注解
想要启用@Async和@Scheduled注解,必须将@EnableAsync注解和@EnableScheduling注解添加到一个@Configuration类上
```java
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
```
> **@Async注解的实现是通过proxy模式来实现的故而如果在类内部调用位于同一个类中的@Async方法那么代理拦截会失效此时调用的@Async方法将会同步执行而非异步执行**
> @Async可以接收一个value值用于指定目标的执行器Executor或TaskExecutor的beanName
### @Scheduled注解
在将@Scheduled注解标注到方法时可以为其指定一个trigger的元数据使用示例如下
```java
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
```
> 默认情况下fixedDelay、fixedRate、initialDelay的时间单位都是ms可以指定timeUnit来指定其他的时间单位
> ```java
> @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
> public void doSomething() {
> // something that should run periodically
> }
> ```
可以为@Scheduled注解使用cron表达式
```java
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
```
### @Async注解
通过为方法指定@Async注解可以让该方法异步执行该方法的执行通过TaskExecutor。
```java
@Async
void doSomething() {
// this will be run asynchronously
}
```
可以为@Async标注的方法指定参数和返回值但是异步方法的返回值只能为void或是Future类型。
在该异步方法的调用方调用返回Future实例的get方法之前调用方仍然能够执行其他操作异步方法的执行位于TaskExecutor的线程中。
```java
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
```
不能将@Async注解和生命周期回调(例如@PostConstruct进行混用如果想要异步的初始化bean对象需要按照如下方法
```java
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框架中与Redis进行通信既提供了低层次的api与字节数据进行交互也提供了高度抽象的api供用户使用
- Redis Connection支持通过连接器和Redis服务端进行低层次的交互
- RedisTemplate则是向用户提供了高层次api与Rdis服务端进行交互
- ## 连接Redis
- 为了通过IOC容器连接Redis需要使用统一的Spring Redis API。对于任何库提供的Redis Connector都会统一向外提供一致的Spring Redis API。
- Spring Redis API通过RedisConnection和RedisConnectionFactory接口进行工作并且从Redis处获取连接
- ## RedisConnection和RedisConnectionFactory
- RdisConnection用于提供和Redis后端的交互。RedisConnection接口会自动将底层Connector库异常转化为统一的Spring DAO异常因而在使用ReidsConnection的过程中如果切换底层链接库并不需要代码的改动。
- RedisConnection时通过RedisConnectionFactory来获取的
- ## RedisTemplate
- RedisTemplate是线程安全的可以在多个线程中被并发安全的使用
- RedisTemplate使用java底层的序列化机制在通过RedisTemplate读取或者写入数据时会通过Java的序列化机制将对象序列化/反序列化。
- RedisTemplate要求key是非空的但是可以接受空的value值
- 当想要使用RedisTemplate的某个视图时可以将RedisTemplate对象注入给该名为xxxOperations的视图。
```java
@Service
public class RedisTemplateOperations {
// 直接注入
@Autowired
private RedisTemplate<String,String> redisTemplate;
// 将RedisTemplate转换成其某一个视图之后再注入
@Resource(name="redisTemplate")
private ListOperations<String,String> opsForList;
}
```
- ## StringRedisTemplate
- 在Redis操作中key和Value通常都是String类型。故而Spring Data Redis提供了操作StringRedisTemplate类
- StringRedisTemplate是RedisTemplate的子类并且在应用启动时IOC容器中会注入redisTemplate和stringRedisTemplate两个对象
- 相比较于RedisTemplateStringRedisTemplate底层使用StringRedisSerializer来进行序列化其序列化的key和value都是可读的
- ## 序列化Serializer选择
- 除了先前提到的jdk默认的序列化机制和StringRedisSerializerSpring Data Redis还提供了其他Serializer
- 例如可以选择将key和value化为json字符串格式可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer
> 相对于Jackson2JsonSerializerGenericJackson2JsonSerializer在序列化对象为json串时添加了对象的java类型信息故而在将json串反序列化并且转换为原有类型时不会抛出异常
```java
/**
* 如果想要自定义redisTemplate的序列化方式可以添加如下配置类
* RedisTemplate支持分别自定义key/value/hashkey/hashvalue的序列化方式
* RedisTemplate也支持设置defaultSerializer当key/value/hashkey/hashvalue的Serializer没有显式指定时会应用defaultSerializer
**/
@Configuration
public class SerializerConfig {
@Bean(name="redisTemplate")
RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
# Spring Data Redis
- ## Spring Data Redis
- 在Spring框架中与Redis进行通信既提供了低层次的api与字节数据进行交互也提供了高度抽象的api供用户使用
- Redis Connection支持通过连接器和Redis服务端进行低层次的交互
- RedisTemplate则是向用户提供了高层次api与Rdis服务端进行交互
- ## 连接Redis
- 为了通过IOC容器连接Redis需要使用统一的Spring Redis API。对于任何库提供的Redis Connector都会统一向外提供一致的Spring Redis API。
- Spring Redis API通过RedisConnection和RedisConnectionFactory接口进行工作并且从Redis处获取连接
- ## RedisConnection和RedisConnectionFactory
- RdisConnection用于提供和Redis后端的交互。RedisConnection接口会自动将底层Connector库异常转化为统一的Spring DAO异常因而在使用ReidsConnection的过程中如果切换底层链接库并不需要代码的改动。
- RedisConnection时通过RedisConnectionFactory来获取的
- ## RedisTemplate
- RedisTemplate是线程安全的可以在多个线程中被并发安全的使用
- RedisTemplate使用java底层的序列化机制在通过RedisTemplate读取或者写入数据时会通过Java的序列化机制将对象序列化/反序列化。
- RedisTemplate要求key是非空的但是可以接受空的value值
- 当想要使用RedisTemplate的某个视图时可以将RedisTemplate对象注入给该名为xxxOperations的视图。
```java
@Service
public class RedisTemplateOperations {
// 直接注入
@Autowired
private RedisTemplate<String,String> redisTemplate;
// 将RedisTemplate转换成其某一个视图之后再注入
@Resource(name="redisTemplate")
private ListOperations<String,String> opsForList;
}
```
- ## StringRedisTemplate
- 在Redis操作中key和Value通常都是String类型。故而Spring Data Redis提供了操作StringRedisTemplate类
- StringRedisTemplate是RedisTemplate的子类并且在应用启动时IOC容器中会注入redisTemplate和stringRedisTemplate两个对象
- 相比较于RedisTemplateStringRedisTemplate底层使用StringRedisSerializer来进行序列化其序列化的key和value都是可读的
- ## 序列化Serializer选择
- 除了先前提到的jdk默认的序列化机制和StringRedisSerializerSpring Data Redis还提供了其他Serializer
- 例如可以选择将key和value化为json字符串格式可以选用Jackson2JsonSerializer或者GenericJackson2JsonSerializer
> 相对于Jackson2JsonSerializerGenericJackson2JsonSerializer在序列化对象为json串时添加了对象的java类型信息故而在将json串反序列化并且转换为原有类型时不会抛出异常
```java
/**
* 如果想要自定义redisTemplate的序列化方式可以添加如下配置类
* RedisTemplate支持分别自定义key/value/hashkey/hashvalue的序列化方式
* RedisTemplate也支持设置defaultSerializer当key/value/hashkey/hashvalue的Serializer没有显式指定时会应用defaultSerializer
**/
@Configuration
public class SerializerConfig {
@Bean(name="redisTemplate")
RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
```

View File

@@ -1,9 +1,9 @@
# Spring Data
- ## Spring Boot中选择连接池的算法
- HikariCP的表现和并发性都很好如果HikariCP可以被获取选择HikariCP
- 如果HikariCP不能获取选用Tomcat Datasource
- 如果Tomcat Datasource也不能获取选用DBCP2
- 如果上述都无法获取选用Oracle UCP
- Spring Boot使用自定义连接池
- 可以通过显式指定自定义的连接池种类来绕过该算法通过指定spring.datasource.type来自定义连接池
# Spring Data
- ## Spring Boot中选择连接池的算法
- HikariCP的表现和并发性都很好如果HikariCP可以被获取选择HikariCP
- 如果HikariCP不能获取选用Tomcat Datasource
- 如果Tomcat Datasource也不能获取选用DBCP2
- 如果上述都无法获取选用Oracle UCP
- Spring Boot使用自定义连接池
- 可以通过显式指定自定义的连接池种类来绕过该算法通过指定spring.datasource.type来自定义连接池
- 可以通过DatasourceBuilder来定义额外的datasource。如果定义了自己的datasouce bean那么自动装配将不会发生。

View File

@@ -1,49 +1,49 @@
# Spring单元测试
## SpringBootTest
在SpringBoot中提供了@SpringBootTest注解。当需要SpringBoot特性时可以通过使用@SpringBootTest注解来作为@ContextConfiguration的替代@SprngBootTest创建ApplicationContext该context在test中被使用。
## @AfterAll
该注解标明的方法会在所有Test执行完成之后再执行
> ### @AfterAll注解特性
> - @AfterAll注解的方法必须含有void类型返回值
> - @AfterAll注解标注方法不能为private
> - 默认情况下,@AfterAll注解的方法必须是static修饰的
> ### 在非static方法中标注@AfterAll
> 在非static方法上标注@AfterAll需要在class上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS)。
> 因为默认情况下TestInstance的默认生命周期是PER_METHOD
> ```JAVA
> @TestInstance(TestInstance.Lifecycle.PER_CLASS)
> public class BeforeAndAfterAnnotationsUnitTest {
>
> String input;
> Long result;
> @BeforeAll
> public void setup() {
> input = "77";
> }
>
> @AfterAll
> public void teardown() {
> input = null;
> result = null;
> }
>
> @Test
> public void whenConvertStringToLong_thenResultShouldBeLong() {
> result = Long.valueOf(input);
> Assertions.assertEquals(77l, result);
> }
> }
> ```
## @AfterEach
该注解标明的方法,在每次@Test标注方法执行完成之后都会被执行
> ### @AfterEach特性
> - 标注方法返回值为空
> - 标注方法不为private
> - 标注方法不是static
## @BeforeAll
类似@AfterAll
## @BeforeEach
# Spring单元测试
## SpringBootTest
在SpringBoot中提供了@SpringBootTest注解。当需要SpringBoot特性时可以通过使用@SpringBootTest注解来作为@ContextConfiguration的替代@SprngBootTest创建ApplicationContext该context在test中被使用。
## @AfterAll
该注解标明的方法会在所有Test执行完成之后再执行
> ### @AfterAll注解特性
> - @AfterAll注解的方法必须含有void类型返回值
> - @AfterAll注解标注方法不能为private
> - 默认情况下,@AfterAll注解的方法必须是static修饰的
> ### 在非static方法中标注@AfterAll
> 在非static方法上标注@AfterAll需要在class上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS)。
> 因为默认情况下TestInstance的默认生命周期是PER_METHOD
> ```JAVA
> @TestInstance(TestInstance.Lifecycle.PER_CLASS)
> public class BeforeAndAfterAnnotationsUnitTest {
>
> String input;
> Long result;
> @BeforeAll
> public void setup() {
> input = "77";
> }
>
> @AfterAll
> public void teardown() {
> input = null;
> result = null;
> }
>
> @Test
> public void whenConvertStringToLong_thenResultShouldBeLong() {
> result = Long.valueOf(input);
> Assertions.assertEquals(77l, result);
> }
> }
> ```
## @AfterEach
该注解标明的方法,在每次@Test标注方法执行完成之后都会被执行
> ### @AfterEach特性
> - 标注方法返回值为空
> - 标注方法不为private
> - 标注方法不是static
## @BeforeAll
类似@AfterAll
## @BeforeEach
类似@AfterEach

View File

@@ -1,71 +1,71 @@
# Spring Boot JSON
## Spring Boot JSON简介
在Spring Boot中为JSON提供了三个集成的内置库Gson、Jackson、JSON-B。
> 其中Jackson是Spring Boot推荐并且默认的JSON库。
Spring Boot项目为Jackson提供了自动装配并且Jackson是spring-boot-starter-json启动器的一部分。当Jackson依赖在classpath下时ObjectMapper的bean对象会自动的被配置。
## 自定义序列化器和反序列化器
如果使用Jackson进行json数据的序列化和反序列化你可能需要实现自己的序列化类和反序列化类。可以通过@JsonComponent注解来定义自己的JsonSerializer, JsonDeserializer,JsonObjectSerializer,JsonObjectDeserializer
```java
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
jgen.writeEndObject();
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
```
## Jackson使用
Jackson使用ObjectMapper来将json转化为java对象或者将java对象转化为json。
### ObjectMapper处理json域字段和java类域字段的映射关系
# Spring Boot JSON
## Spring Boot JSON简介
在Spring Boot中为JSON提供了三个集成的内置库Gson、Jackson、JSON-B。
> 其中Jackson是Spring Boot推荐并且默认的JSON库。
Spring Boot项目为Jackson提供了自动装配并且Jackson是spring-boot-starter-json启动器的一部分。当Jackson依赖在classpath下时ObjectMapper的bean对象会自动的被配置。
## 自定义序列化器和反序列化器
如果使用Jackson进行json数据的序列化和反序列化你可能需要实现自己的序列化类和反序列化类。可以通过@JsonComponent注解来定义自己的JsonSerializer, JsonDeserializer,JsonObjectSerializer,JsonObjectDeserializer
```java
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
jgen.writeEndObject();
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
```
## Jackson使用
Jackson使用ObjectMapper来将json转化为java对象或者将java对象转化为json。
### ObjectMapper处理json域字段和java类域字段的映射关系

View File

@@ -1,8 +1,8 @@
# File Upload and Download in Spring Boot
## SpringBoot上传文件配置
在application.properties中可以进行如下配置
```properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
# File Upload and Download in Spring Boot
## SpringBoot上传文件配置
在application.properties中可以进行如下配置
```properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
```