Bootstrap

SpringBoot整合Shiro 'ProxyTransactionManagementConfiguration': BeanPostProcessor before instantiation

SpringBoot使用@EnableTransactionManagement开启事务再整合Shiro时,如果在AuthorizingRealm中注入了Service。代码如下:

// SpringBoot启动类
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.boge.mapper")
public class DingTalkAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(DingTalkAdminApplication.class, args);
    }
}

// Mapper映射类
package com.boge.mapper;

import org.apache.ibatis.annotations.Select;

import com.boge.entity.User;

public interface UserMapper extends BaseDaoMapper<User, Long> {
    @Select("select * from t_user where username = #{username}")
    User findByUsername(String username);
}

// EhCache缓存配置类
@Configuration
@EnableCaching
public class EhCacheConfig {
    @Bean
    public EhCacheManagerFactoryBean ehCacheManager() {
        EhCacheManagerFactoryBean ehCacheManager = new EhCacheManagerFactoryBean();
        //注:如果application.yml配置文件中设置了spring.cache.ehcache.config=classpath:ehcache.xml
        //则此处不需要setConfigLocation,SpringBoot会自动将ehcache.xml加入到EhCacheManagerFactoryBean
        ehCacheManager.setConfigLocation(new ClassPathResource("ehcache.xml"));
        ehCacheManager.setShared(true);
        return ehCacheManager;
    }
	
    @Bean
    public EhCacheCacheManager cacheManager(EhCacheManagerFactoryBean bean) {
        CacheManager cacheManager = bean.getObject();
        return new EhCacheCacheManager(cacheManager);
    }
}

// 用户Service实现类
@Service
public class UserServiceImpl extends BaseServiceImpl<User, Long> implements IUserService {	
    @Inject
    private UserMapper userMapper;

    @Override
    @Transactional(readOnly = true)
    public User findByUsername(String username) {
        return userMapper.findByUsername(StringUtils.lowerCase(username));
    }
}

// Shrio授权域类-AuthorizingRealm
public class AuthorizingRealm extends org.apache.shiro.realm.AuthorizingRealm {
    @Inject
    private IUserService userService;
    ...
}

// Shiro配置类
package com.boge.config;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;

import com.boge.security.AuthorizingRealm;

@Configuration
public class ShiroConfig {
    @Bean
    public EhCacheManager shiroCacheManager(EhCacheManagerFactoryBean bean) {
        EhCacheManager shiroCacheManager = new EhCacheManager();
        shiroCacheManager.setCacheManager(bean.getObject());
        return shiroCacheManager;
    }
	 
    @Bean
    public AuthorizingRealm authorizingRealm() {
        AuthorizingRealm authorizingRealm = new AuthorizingRealm();
        authorizingRealm.setAuthenticationCacheName("authorization");
        return authorizingRealm;
    }

    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(EhCacheManager shiroCacheManager, AuthorizingRealm authorizingRealm) {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        methodInvokingFactoryBean.setArguments(securityManager(shiroCacheManager, authorizingRealm));

        return methodInvokingFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager(EhCacheManager shiroCacheManager, AuthorizingRealm authorizingRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(shiroCacheManager);
        securityManager.setRealm(authorizingRealm);
        return securityManager;
    }
}

运行时报如下异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroEventBusAwareBeanPostProcessor' defined in class path resource [org/apache/shiro/spring/boot/autoconfigure/ShiroBeanAutoConfiguration.class]: BeanPostProcessor before instantiation of bean failed;
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.cache.annotation.ProxyCachingConfiguration': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': BeanPostProcessor before instantiation of bean failed;...

分析:

注意上面异常中红色标识的 "ProxyTransactionManagementConfiguration" 由此判断出,事务管理器Bean还没有创建成功。对应到本例就是:在SpringBoot创建Shiro相关Bean时,某个ShiroBean中有使用到事务(如本例的AuthorizingRealm类中有注入userService,在userService中又有用到@Transactional),而此时的事务管理器Bean还未被注入。因此,导致Shiro相关的依赖Bean(如此处的userService)注入失败,最终导致Siro本身的Bean注入失败。

分析结果:造成如上异常是由Bean的加载顺序造成。因ShiroConfig类上面标注了@Configuration注解,说明它就是个配置类。而@EnableTransactionManagement是在ApplicationContext中的配置中,此时ShiroConfig中的Bean要比ApplicationContext配置中的事务管理器Bean先一步被注入。

两种解决方案:

1、通过在ShiroConfig配置类中往securityManager添加@DependsOn(TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)

表示是等待@DependsOn注解中指定的Bean注入完成后再来注入securityManager

注意:本例是使用的SpringBoot默认的基于JDK的动态代理模式(transaction advisor),因此此处的事务管理器Bean的名字使用的是TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME

对应的全限定名称就是:org.springframework.transaction.config.internalTransactionAdvisor

如果使用Spring的cglib切面动态代理模式(transaction aspect),则此处的事务管理器Bean的名字需要换成TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME

对应的全限定名称就是:org.springframework.transaction.config.internalTransactionAspect

代码片段如下:

@Bean
@DependsOn(TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
public DefaultWebSecurityManager securityManager(EhCacheManager shiroCacheManager, AuthorizingRealm authorizingRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setCacheManager(shiroCacheManager);
    securityManager.setRealm(authorizingRealm);
    return securityManager;
}

2、通过创建一个InitListener来监听ApplicationContext是否加载完成,等待其加载完成后再进行Shiro相关设置。代码如下:

package com.boge.listener;

import javax.inject.Named;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;

import com.boge.security.AuthorizingRealm;

@Named
public class InitListener {
    /**
     * 事件处理
     *
     * @param event
     *        ContextRefreshedEvent
     */
    @EventListener
    public void handle(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        
        if (context == null || event.getApplicationContext().getParent() != null) {
            return;
        }

        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) context.getBean("securityManager");
        AuthorizingRealm authorizingRealm = (AuthorizingRealm) context.getBean("authorizingRealm");
        // 在这里将authorizingRealm注入到securityManager
        securityManager.setRealm(authorizingRealm);
    }
}

最后不要忘了删除SiroConfig中的securityManager注入authorizingRealm的相关操作,修改后的代码如下:

@Bean
public DefaultWebSecurityManager securityManager(EhCacheManager shiroCacheManager) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setCacheManager(shiroCacheManager);
    return securityManager;
}

 

;