事务(十六)
使用了shiro,导致事务失效的情形
场景
shiroconfig中配置如下:
1 /** 2 * 安全管理器 3 */ 4 @Bean 5 public DefaultWebSecurityManager securityManager(ApplicationContext context, CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager defaultWebSessionManager) { 6 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 7 Object shiroDbRealm = context.getBean("testService"); //代码一 8 ShiroDbRealm bean = context.getBean(ShiroDbRealm.class); //代码二 9 securityManager.setAuthenticator(modularRealmAuthenticator()); 10 List<Realm> realms = new ArrayList<>(2); 11 //密码登录realm 12 realms.add(this.shiroDbRealm()); 13 //免密登录realm 14 realms.add(this.shiroFreeRealm()); //代码三 15 securityManager.setRealms(realms); 16 securityManager.setCacheManager(cacheShiroManager); 17 securityManager.setRememberMeManager(rememberMeManager); 18 securityManager.setSessionManager(defaultWebSessionManager); 19 return securityManager; 20 }
/**
* 自定义的免密登录Realm
*/
@Bean
public ShiroFreeRealm shiroFreeRealm() {
return new ShiroFreeRealm();
}
1 /** 2 * Shiro的过滤器链 3 */ 4 @Bean 5 public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { 6 ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); 7 shiroFilter.setSecurityManager(securityManager); 8 }
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
1 public class ShiroFreeRealm extends AuthorizingRealm { 2 3 private Logger log = LoggerFactory.getLogger(this.getClass()); 4 5 @Resource 6 private UserWxService userWxService; 7 @Resource 8 private UserService userService; 9 }
分析:
1.shiro的过滤器实现了BeanPostProcessor,会在spring 初始化bean之前进行初始化,而此时事务的bean后置处理器并没有加载进来。
2.shiro过滤器需要注入DefaultWebSecurityManager,所以会进而初始化上面的DefaultWebSecurityManager
3.代码一中在初始化DefaultWebSecurityManager的时候初始化了业务的testService 的bean,而此时事务bean后置处理器不存在,所以导致初始化的testService对象,并不是代理对象,不存在事务,导致事务失效了;
4.代码二也是一样的道理
注意:只要是在shirofilter中初始化的所有的bean都会事务失效,包括代码一和代码二,由于spring初始化对象时,如果对象中需要依赖注入别的对象,那么也会将依赖对象进行初始化,也就是说如果testService类中通过@Autowired等注解依赖注入了其他对象,那么初始化testService的时候也会将依赖对象初始化,依赖的对象也会事务失效,会一直层层依赖下去,所有的对象都会进行初始化,事务会失效;
我们项目中就是代码三处自定义的免密登录Realm,其中注入了两个servcie:UserWxService和userService,而这两个service中依赖了别的service,所以shirofilter初始化时依次初始化了大量的业务service,导致这些servcie都事务失效了,我们进行了一下修改,讲注入service修改为注入mapper,这样就不会初始化service 的bean对象,从而不会让事务失效;修改如下:
1 public class ShiroFreeRealm extends AuthorizingRealm { 2 3 private Logger log = LoggerFactory.getLogger(this.getClass()); 4 5 @Resource 6 private UserWxMapper userWxMapper; 7 @Resource 8 private UserMapper userMapper; 9 }
其他解决方案
既然ShiroFreeRealm中不能通过@Autowired
注入userService,那我们变通一下,不用第一时间注入,等需要用到的时候再向Spring索取就好了。
这里第一个想到的肯定就是ApplicationContext了,这好办,写一个ApplicationContext工具类:
1 @Component 2 public class ApplicationContextUtils implements ApplicationContextAware { 3 public static ApplicationContext applicationContext; 4 @Override 5 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 6 ApplicationContextUtils.applicationContext = applicationContext; 7 } 8 public static Object getBean(String beanName) { 9 return applicationContext.getBean(beanName); 10 } 11 public static <T> T getBean(Class<T> type) { 12 return applicationContext.getBean(type); 13 } 14 }
通过实现ApplicationContextAware接口拿到ApplicationContext,后面就可以随心所以了,ShiroFreeRealm中需要用到userService的时候我们可以这么写:
UserService userService = ApplicationContextUtils.getBean(UserService.class);
在其他类似的地方,如果需要支持事务或者用到代理对象的地方,都可以通过这种方式获取。另外顺带提一下,如果需要用到对象原始的实例(非代理对象),我们可以通过在Bean名称前面加一个&
获取,还是以UserService举例子:
UserService userService = ApplicationContextUtils.getBean("&userService");
这样拿到的就是常规实例对象了。
参考:
https://www.guitu18.com/post/2019/10/30/56.html
https://blog.csdn.net/qq_30930805/article/details/104059732