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;
}