springboot 集成 shiro 导致事务无效
问题描述
前两天测试一个写事务,发现这个事务出现异常不会回滚了,一直在事务上找问题,一直没有找到,结果发现是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 原理和为啥不起效