springboot 集成 shiro 导致事务无效

问题描述

前两天测试一个写事务,发现这个事务出现异常不会回滚了,一直在事务上找问题,一直没有找到,结果发现是shirobean先于Spring事务将userService实例化了,结果导致Spring事务初始化时好无法扫描到该bean,导致这个bean上没有绑定事务,导致事务无效

寻找问题在哪

一、在事务本身找问题

通过百度发现,大家都有以下几个原因导致事务失效

  1. 数据库的引擎是否是innoDB
  2. 启动类上是否加入@EnableTransactionManagement注解
  3. 方法是否为public
  4. 是否是因为抛出了Exceptionchecked异常

经过排查,发现以上原因都通过了,那么应该不是写的问题。

二、在运行中找问题

在上面4个原因检查时,发现将已有的service 类 copy下现在有两个除了名字其他都一模一样的类,这时运行下发现,在原来的类中@Transational失效,在新copy中的类中@Transational就起效了,这个问题好莫名奇妙,什么都没改就一个有效一个无效,现在的思路就是比较下这两个类在运行时有什么不同

通过log发现打出了一下信息,说是jdbcconnection 不是Spring管的
springboot 集成 shiro 导致事务无效

而正常回归的service类则是,调用了 JtaTransactionManager 类,而且 Spring是管理jdbcconnection
springboot 集成 shiro 导致事务无效

通过这个分析,可以知道这Spring对于这两个类的处理是不一样的,应该是spring代理或者初始化的问题,翻了下log 发现service
ProxyTransactionManagementConfiguration 配置之前就被创建了,那应该是这里的问题了,这里就要分析下service为啥提前被创建了,发现在开始启动的是shiro ,而shiro中有个realm中引用了这些服务,所以这些服务在Transaction创建扫描之前创建了
springboot 集成 shiro 导致事务无效

引发问题原因总结

导致问题的真正原因是bean创建顺序问题,解决问题方法就是,在Transaction之后创建service
ps:呵呵,但是我还是不知道咋样才能解决创建顺序问题,继续百度之,关键词shiro 导致 事务不生效果然有解决方案

解决方案

经过百度找到了以下的解决方法,和以下解释

  1. shiro导致springboot事务不起效解决办法
  2. BeanPostProcessor加载次序及其对Bean造成的影响分析
  3. spring boot shiro 事务无效
  4. Shrio 多realms集成:No realms have been configured! One or more realms must be present
  5. spring + shiro 配置中部分事务失效分析及解决方案(这个方案不管用)

解决方法一:

realm引用的service服务上加@lazy注解,但是这个方法我测试了下并没有起效!!!

解决方法二:

把在 ShiroConfig里面初始化的realmbeansecurityManagerbean方法移动到一个新建的ShiroComponent中,利用监听器中去初始化,主要配置如下,其中ShiroComponentUserNamePassWordRealmWeiXinRealm是我自定义的两个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);
    }
}

总结

以后需要补课的地方

  1. spring bean 初始化顺序
  2. spring 事务原理
  3. spring bean 预加载 BeanPostProces 原理
  4. @Lazy 原理和为啥不起效

相关推荐