问题描述
前两天测试一个写事务,发现这个事务出现异常不会回滚了,一直在事务上找问题,一直没有找到,结果发现是 shiro 的 bean 先于 Spring 事务将 userService 实例化了,结果导致 spring 事务初始化时好无法扫描到该 bean,导致这个 bean 上没有绑定事务,导致事务无效
寻找问题在哪
一、在事务本身找问题
通过百度发现,大家都有以下几个原因导致事务失效
数据库的引擎是否是 innoDB
启动类上是否加入 @EnableTransactionManagement 注解
方法是否为 public
是否是因为抛出了 Exception 等 checked 异常
经过排查,发现以上原因都通过了,那么应该不是写的问题。
二、在运行中找问题
在上面 4 个原因检查时,发现将已有的 service 类 copy 下现在有两个除了名字其他都一模一样的类,这时运行下发现,在原来的类中 @Transational 失效,在新 copy 中的类中 @Transational 就起效了,这个问题好莫名奇妙,什么都没改就一个有效一个无效,现在的思路就是比较下这两个类在运行时有什么不同
通过 log 发现打出了一下信息,说是 jdbc 的 connection 不是 Spring 管的
而正常回归的 service 类则是, 调用了 JtaTransactionManager 类,而且 spring 是管理 jdbc 的 connection 的
通过这个分析,可以知道这 spring 对于这两个类的处理是不一样的,应该是 spring 代理或者初始化的问题,翻了下 log 发现 service 在 ProxyTransactionManagementConfiguration 配置之前就被创建了,那应该是这里的问题了,这里就要分析下 service 为啥提前被创建了,发现在开始启动的是 shiro,而 shiro 中有个 realm 中引用了这些服务,所以这些服务在 Transaction 创建扫描之前创建了
引发问题原因总结
导致问题的真正原因是 bean 创建顺序问题,解决问题方法就是,在 Transaction 之后创建 service。ps:呵呵,但是我还是不知道咋样才能解决创建顺序问题,继续百度之, 关键词 shiro 导致 事务不生效果然有解决方案
解决方案
经过百度找到了以下的解决方法,和以下解释
shiro 导致 springboot 事务不起效解决办法
BeanPostProcessor 加载次序及其对 Bean 造成的影响分析
spring boot shiro 事务无效
Shrio 多 realms 集成:No realms have been configured! One or more realms must be present
spring + shiro 配置中部分事务失效分析及解决方案 (这个方案不管用)
解决方法一:
在 realm 引用的 service 服务上加 @lazy 注解,但是这个方法我测试了下并没有起效!!!
解决方法二:
把在 ShiroConfig 里面初始化的 Realm 的 bean 和 securityManager 的 bean 方法移动到一个新建的 ShiroComponent 中,利用监听器中去初始化,主要配置如下,其中 ShiroComponent 中 UserNamePassWordRealm 和 WeiXinRealm 是我自定义的两个 Realm,换成自己的就好,
ShiroConfig.java
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* 自定义继承 shiro 没有使用 shiro-spring-boot-web-starter 的 shiro 套件
*
* @author gaoxiuya
*
*/
@Configuration
public class ShiroConfig {
/**
* FilterRegistrationBean
*
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy(“shiroFilter”));
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns(“/*”);
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistration;
}
/**
* @param securityManager
* @see org.apache.shiro.spring.web.ShiroFilterFactorupload.visit.pathyBean
* @return
*/
@Bean(name = “shiroFilter”)
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl(“/”);
bean.setSuccessUrl(“/index”);
bean.setUnauthorizedUrl(“/403”);
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put(“permsc”, new CustomPermissionsAuthorizationFilter());
bean.setFilters(filters);
Map<String, String> chains = new LinkedHashMap<>();
chains.put(“/favicon.ico”, “anon”);
bean.setFilterChainDefinitionMap(chains);
return bean;
}
@Bean
public EhCacheManager cacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile(“classpath:ehcache.xml”);
return cacheManager;
}
/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name = “sessionManager”)
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(cacheManager());
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
ShiroComponent.java
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ShiroComponent {
@Bean
public Realm userNamePassWordRealm(CacheManager cacheManager) {
UserNamePassWordRealm userNamePassWordRealm = new UserNamePassWordRealm();
userNamePassWordRealm.setCacheManager(cacheManager);
return userNamePassWordRealm;
}
@Bean
public Realm myWeiXinRealm(CacheManager cacheManager) {
WeiXinRealm weiXinRealm = new WeiXinRealm();
weiXinRealm.setCacheManager(cacheManager);
return weiXinRealm;
}
@Bean(name = “securityManager”)
public DefaultWebSecurityManager securityManager(Authenticator modularRealmAuthenticator, CacheManager cacheManager,
SessionManager defaultWebSessionManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setAuthenticator(modularRealmAuthenticator);
manager.setCacheManager(cacheManager);
manager.setSessionManager(defaultWebSessionManager);
return manager;
}
@Bean
public Authenticator modularRealmAuthenticator() {
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return modularRealmAuthenticator;
}
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
DefaultWebSecurityManager manager = (DefaultWebSecurityManager) context.getBean(“securityManager”);
Realm userNamePassWordRealm = (Realm) context.getBean(“userNamePassWordRealm”);
Realm myWeiXinRealm = (Realm) context.getBean(“myWeiXinRealm”);
ModularRealmAuthenticator modularRealmAuthenticator = (ModularRealmAuthenticator) context
.getBean(“modularRealmAuthenticator”);
List<Realm> realms = new ArrayList<>();
realms.add(userNamePassWordRealm);
realms.add(myWeiXinRealm);
modularRealmAuthenticator.setRealms(realms);
manager.setAuthenticator(modularRealmAuthenticator);
manager.setRealms(realms);
}
}
总结
以后需要补课的地方
spring bean 初始化顺序
spring 事务原理
spring bean 预加载 BeanPostProces 原理
@Lazy 原理和为啥不起效