1. 程式人生 > >SpringBoot+Shiro引起事務失效、錯誤原因、解決方法

SpringBoot+Shiro引起事務失效、錯誤原因、解決方法

一、問題
今天發現使用者註冊的Service的事務並沒有起到作用,再丟擲一個RuntimeException後,並沒有發生回滾,下面是除錯步驟:

1、檢查資料庫的引擎是否是innoDB

2、啟動類上是否加入@EnableTransactionManagement註解

3、是否在方法上加入@Transactional註解或Service的類上是否有@Transactional註解

4、方法是否為public

5、是否是因為丟擲了Exception等checked異常

然而事務失效都不是這些原因引起的,並且發現其他Service的事務都可以正常使用。在檢視列印的異常呼叫鏈的時候,發現這個Service是沒有被AOP代理過的,所以推測可能是因為其他整合Spring的框架提前引用了這個Service。

為了驗證,新建了一個Service,並且把程式碼copy到新建的類中,測試其事務,發現事務可以正常使用,下面是列印的異常資訊:

 

從上面可以明顯看到,Spring為這個service生成了代理類,證明事務是可以正常使用的,並且原service的失效應該是其他的整合Spring的框架提前引用造成的。

因為專案還使用了Shiro作為許可權管理,並且在編寫Shiro的自定義驗證器Realm中引用了該UserService,後來把Realm中的Service換成了Mapper後,該Service的事務可以正常使用了。

 

錯誤原因:
Spring中事務是通過AOP建立代理物件來完成的,有BeanFactoryTransactionAttributeSourceAdvisor完成對需要事務的方法織入對事務的處理。完成建立AOP代理物件的功能由一個特殊的BeanPostProcessor完成--AnnotationAwareAspectJAutoProxyCreator。該類實現了BeanPostProcessor介面,在bean建立完成並將屬性設定好之後,攔截bean,並建立代理物件,在原物件的方法功能上新增增強器中增強方法的處理。對於事務增強器BeanFactoryTransactionAttributeSourceAdvisor而言,也就是在原有方法上加入事務的功能。

但是,在ApplicationContext重新整理上下文過程(refresh)中,上下文會呼叫registerBeanPostProcessors方法將BeanFactory中的所有BeanPostProcessor後處理器註冊到BeanFactory中,使其後面流程中建立bean的時候生效。下面是其實現原始碼:

public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

// Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}

// First, register the BeanPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

// AnnotationAwareAspectJAutoProxyCreator實現了Ordered介面,所以會在這裡排序
// 但是,實現Ordered介面的BeanPostProcessor中,有一個是MethodValidationPostProcessor
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);

// Now, register all regular BeanPostProcessors.
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

// Finally, re-register all internal BeanPostProcessors.
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);

// Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}
上面流程為:

例項化所有實現了PriorityOrdered介面的BeanPostProcessor,排序後註冊到BeanFactory
例項化所有實現了Ordered介面的BeanPostProcessor,排序後註冊到BeanFactory
例項化剩餘的BeanPostProcessor,註冊到BeanFactory
註冊內部使用的BeanPostProcessor
新增一個特殊的後處理器--ApplicationListenerDetector
由於AnnotationAwareAspectJAutoProxyCreator實現了Ordered介面,所以會在第2步中註冊到BeanFactory,然後生效,可以攔截bean建立並生成代理物件。但是,在其註冊前,有一個同樣實現了Ordered介面的MethodValidationPostProcessor。在該類的例項化過程中,會由ValidationAutoConfiguration通過工廠方法來建立,建立過程中,需要傳入Environment物件作為引數,然後Spring會從BeanFactory中查詢所有符合Environment型別的bean,下面是查詢過程:

//type為需要的引數,型別為Environment
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
List<String> result = new ArrayList<>();

//遍歷BeanFactory中儲存的所有beanName
for (String beanName : this.beanDefinitionNames) {
if (!isAlias(beanName)) {
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 檢查是否是合格的bean
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
// 是否是FactoryBean.
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
isTypeMatch(beanName, type);
if (!matchFound && isFactoryBean) {
beanName = FACTORY_BEAN_PREFIX + beanName;
// 如果是FactoryBean,在比較型別是,會例項化FactoryBean物件,用作比對
// isTypeMatch會比較FactoryBean對應的實際型別是否符合,所以會例項化FactoryBean
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}
if (matchFound) {
result.add(beanName);
}
}
}
//catch...略
}
}

// Check manually registered singletons too.
//省略直接註冊的單例檢查

return StringUtils.toStringArray(result);
}
如果存在FactoryBean的話,在比對過程中會例項化FactoryBean(isTypeMatch中實現)

在isTypeMatch中有這麼一段程式碼:

if (FactoryBean.class.isAssignableFrom(beanType)) {
if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
// 如果是FactoryBean型別,需要對其例項化後才能知道到底它建立的bean是什麼型別
beanType = getTypeForFactoryBean(beanName, mbd);
if (beanType == null) {
return false;
}
}
}
下面是getTypeForFactoryBean中的建立FactoryBean例項部分的程式碼:

FactoryBean<?> fb = (mbd.isSingleton() ?
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
下面是getSingletonFactoryBeanForTypeCheck實現:

private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
synchronized (getSingletonMutex()) {
//先嚐試從快取中獲取
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
return (FactoryBean<?>) bw.getWrappedInstance();
}
//從單例快取中獲取
Object beanInstance = getSingleton(beanName, false);
if (beanInstance instanceof FactoryBean) {
return (FactoryBean<?>) beanInstance;
}
if (isSingletonCurrentlyInCreation(beanName) ||
(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
return null;
}

//建立物件
Object instance;
try {
beforeSingletonCreation(beanName);
instance = resolveBeforeInstantiation(beanName, mbd);
if (instance == null) {
//例項化
bw = createBeanInstance(beanName, mbd, null);
instance = bw.getWrappedInstance();
}
}
finally {
afterSingletonCreation(beanName);
}

FactoryBean<?> fb = getFactoryBean(beanName, instance);
if (bw != null) {
this.factoryBeanInstanceCache.put(beanName, bw);
}
return fb;
}
}
從上面程式碼看以看到,Spring會例項化FactoryBean,以確定其建立的bean的型別

由於ShiroFilterFactoryBean實現了FactoryBean介面,所以它會提前被初始化。又因為SecurityManager,SecurityManager依賴於Realm實現類、Realm實現類又依賴於UserService,所以引發所有相關的bean提前初始化。

ShiroFilterFactoryBean -> SecurityManager -> Realm實現類 -> UserService

但是此時還只是ApplicationContext中registerBeanPostProcessors註冊BeanPostProcessor處理器的階段,此時AnnotationAwareAspectJAutoProxyCreator還沒有註冊到BeanFactory中,UserService無法享受到事務處理!

三、解決辦法
在Realm實現中使用Mapper,而不是直接使用Service物件。缺點:直接和資料庫互動,並且也沒有Service中的邏輯互動以及快取
在Realm中Service宣告上加入@Lazy註解,延遲Realm實現中Service物件的初始化時間,這樣就可以保證Service實際初始化的時候會被BeanPostProcessor攔截,建立具有事務功能的代理物件


原文:https://blog.csdn.net/finalcola/article/details/81197584