spring boot整合shiro后,部分注解(Cache緩存、Transaction事務等)失效的問題


版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/elonpage/article/details/78965176

前言

  1. 整合有緩存、事務的spring boot項目一切正常。
  2. 在該項目上整合shiro安全框架,發現部分類的緩存Cache不能正常使用。
  3. 然后發現該類的注解基本失效,包括事務Transaction注解。事務不能正常運行。

分析

  1. 注解失效的類,都是在shiro框架中(UserRealm)使用過@Autowire注入的類。
  2. 基本確定是shiro框架與spring框架的BeanFactory有所沖突,導致注入shiro框架的類不能被spring正確初始化。

參考
stackoverflow網站上的一些文章認為,Shiro框架初始化比Spring框架的某些部件早,導致使用@Autowire注入Shiro框架的某些類不能被Spring正確初始化。

文章鏈接:
https://stackoverflow.com/questions/21512791/spring-service-with-cacheable-methods-gets-initialized-without-cache-when-autowi

解決方法

  1. 在Shiro框架中注入Bean時,不使用@Autowire,使用ApplicationContextRegister.getBean()方法,手動注入bean。保證該方法只有在程序完全啟動運行時,才被注入。
  2. 使用@Autowire+@Lazy注解,設置注入到Shiro框架的Bean延時加載(即在第一次使用的時候加載)。

示例
1.Shiro中會出問題的代碼

public class MyAuthRealm extends AuthorizingRealm {

//UserService中的注解可能會出現無效的情況
@Autowired
private UserService userService;

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
String username = utoken.getUsername();
User user = userService.getUserByName(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
}

 


2.手動注入bean

public class UserRealm extends AuthorizingRealm {
//該代碼僅作手動注入bean的說明,前后略有省略代碼
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
Long userId = ShiroUtils.getUserId();
//這里手動注入MenuService 
MenuService menuService = ApplicationContextRegister.getBean(MenuService.class);
Set<String> perms = menuService.listPerms(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
return info;
}

}

 


3.延時加載(懶加載)

@Component(value = "myAuthRealm")
public class MyAuthRealm extends AuthorizingRealm {

//延時加載(懶加載)
@Autowired
@Lazy
private UserService userService;

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
String username = utoken.getUsername();
User user = userService.getUserByName(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
}

 

Spring Boot 中使用 @Transactional 注解配置事務管理、

事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編程式和聲明式的兩種方式。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於 @Transactional 注解的方式。本文將着重介紹基於 @Transactional 注解的事務管理。

需要明確幾點:

1.默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。參考這里

2.@Transactional 注解只能應用到 public 方法才有效。參考這里 Method visibility and @Transactional
以下的示例使用的是 mybatis,所以 spring boot 會自動配置一個 DataSourceTransactionManager,我們只需在方法(或者類)加上 @Transactional 注解,就自動納入 Spring 的事務管理了。

3.事務在spring中利用的是aop,故事務調用需要在不同service類中。如果非要這樣,可以注入自己。

 

簡單的使用方法
只需在方法加上 @Transactional 注解就可以了。

如下有一個保存用戶的方法,加入 @Transactional 注解,使用默認配置,拋出異常之后,事務會自動回滾,數據不會插入到數據庫。

@Transactional
@Override
public void save() {
User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}
1
2
3
4
5
6
7
8
9
10
我們可以從日志里面看出這些信息

 

@Transactional 注解的屬性介紹
下面分別介紹一下 @Transactional 的幾個屬性。

value 和 transactionManager 屬性
它們兩個是一樣的意思。當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。

propagation 屬性
事務的傳播行為,默認值為 Propagation.REQUIRED。

可選的值有:

Propagation.REQUIRED

如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。

Propagation.SUPPORTS

如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。

Propagation.MANDATORY

如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。

Propagation.REQUIRES_NEW

重新創建一個新的事務,如果當前存在事務,暫停當前的事務。

Propagation.NOT_SUPPORTED

以非事務的方式運行,如果當前存在事務,暫停當前的事務。

Propagation.NEVER

以非事務的方式運行,如果當前存在事務,則拋出異常。

Propagation.NESTED

和 Propagation.REQUIRED 效果一樣。

這些概念理解起來實在是有點兒抽象,后文會用代碼示例解釋說明。

isolation 屬性
事務的隔離級別,默認值為 Isolation.DEFAULT。

可選的值有:

Isolation.DEFAULT

使用底層數據庫默認的隔離級別。

Isolation.READ_UNCOMMITTED

Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout 屬性
事務的超時時間,默認值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。

readOnly 屬性
指定事務是否為只讀事務,默認值為 false;為了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 為 true。

rollbackFor 屬性
用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。

noRollbackFor 屬性
拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。

@Transactional 的 propagation 屬性代碼示例
比如如下代碼,save 方法首先調用了 method1 方法,然后拋出了異常,就會導致事務回滾,如下兩條數據都不會插入數據庫。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}

public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
現在有需求如下,就算 save 方法的后面拋異常了,也不能影響 method1 方法的數據插入。或許很多人的想法如下,給 method1 頁加入一個新的事務,這樣 method1 就會在這個新的事務中執行,原來的事務不會影響到新的事務。比如 method1 方法上面再加入注解 @Transactional,設置 propagation 屬性為 Propagation.REQUIRES_NEW,代碼如下。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
運行之后,發現然並卵,數據也是沒有插入數據庫。怎么肥四,看起來很不科學。我們先來看看日志內容。

 

從日志內容可以看出,其實兩個方法都是處於同一個事務中,method1 方法並沒有創建一個新的事務。

這就得看看 Spring 官方文檔了。

 

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

大概意思:在默認的代理模式下,只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接調用,是不會被 Spring 的事務攔截器攔截,就像上面的 save 方法直接調用了同一個類中的 method1方法,method1 方法不會被 Spring 的事務攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來解決這個問題,但是這里暫不討論。

為了解決這個問題,我們可以新建一個類。

@Service
public class OtherServiceImpl implements OtherService {

@Autowired
private UserMapper userMapper;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void method1() {
User user = new User("風魔小太郎");
userMapper.insertSelective(user);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
然后在 save 方法中調用 otherService.method1 方法

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

otherService.method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
這下,otherService.method1 方法的數據插入成功,save 方法的數據未插入,事務回滾。

繼續看一下日志內容

 

從日志可以看出,首先創建了 save 方法的事務,由於 otherService.method1 方法的 @Transactional 的 propagation 屬性為 Propagation.REQUIRES_NEW ,所以接着暫停了 save 方法的事務,重新創建了 otherService.method1 方法的事務,接着 otherService.method1 方法的事務提交,接着 save 方法的事務回滾。這就印證了只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。

還有幾個示例如下。

接着把 save 方法的 @Transactional 注解去掉,otherService.method1 的 @Transactional 注解保持不變,從日志就可以看出,只會創建一個 otherService.method1 方法的事務,兩條數據都會插入。

@Autowired
private OtherService otherService;

// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

otherService.method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
接着把 save 方法的 @Transactional 注解去掉,save 方法改為調用內部的 method1 方法,從日志就可以看出,完全沒有創建任何事務,兩條數據都會插入。

// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 拋異常了");
}
}


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
這樣,其他的幾個 propagation 屬性值也就比較好理解了。

@Transactional 事務實現機制
在應用系統調用聲明了 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最后根據執行情況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以 CglibAopProxy 為例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。

 

正如上文提到的,事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現,由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不同的事務管理器管理不同的數據資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

 

源碼地址
https://github.com/nextyu/spring-transaction-demo
參考資料
Spring 官方文檔
Spring boot 官方文檔
Mybatis
資料
資料
資料
結語
由於本人知識和能力有限,文中如有沒說清楚的地方,希望大家能在評論區指出,以幫助我將博文寫得更好
---------------------
作者:皂白
來源:CSDN
原文:https://blog.csdn.net/nextyu/article/details/78669997
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM