引入shiro后userService事务不生效原因(is no
系统spring集成了shiro,配置shiroFilter
:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
</bean>
shiroFilter
依赖了securityManager
,securityManager
依赖了userRealm
,userRealm
为了获取AuthenticationInfo和AuthorizationInfo又依赖了userService
。
启动项目,日志级别info,会出现下面的提示:
PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization(327) - Bean 'userService' of type [com.xxx.service.impl.UserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization(327) - Bean 'userJdbcRealm' of type [com.xxx.UserJdbcRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization(327) - Bean 'securityManager' of type [com.xxx.LoginCustomSecurityManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
上面是截取的3段,表面意思是:这几个bean(称为业务bean)不会被所有BeanPostProcessor(BPP)处理。BPP是spring的内置的基础bean,用于在实例化业务bean前后处理一些逻辑,可以更改业务bean的行为甚至返回另一个bean(大名鼎鼎的AOP就是通过BPP实现)。
上面的ShiroFitlerFactoryBean
就实现了BPP接口,会对所有bean判断,如果实现了Filter
接口,会加入到filterMap
中,后续构造filterChainManager
时会用到。
/**
* Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
* instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
* later during filter chain construction.
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Filter) {
log.debug("Found filter chain candidate filter '{}'", beanName);
Filter filter = (Filter) bean;
applyGlobalPropertiesIfNecessary(filter);
getFilters().put(beanName, filter);
} else {
log.trace("Ignoring non-Filter bean '{}'", beanName);
}
return bean;
}
上面的日志是在BeanPostProcessorChecker
中输出的,这个类的注释说明:
/**
* BeanPostProcessor that logs an info message when a bean is created during
* BeanPostProcessor instantiation, i.e. when a bean is not eligible for
* getting processed by all BeanPostProcessors.
*/
private static class BeanPostProcessorChecker implements BeanPostProcessor
这个类只在一个地方用到:
// Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
看到这2段注释,我们知道出问题的原因:业务bean在BPP实例化前被实例化了。理想情况下应该让spring先实例化所有
的BPP后,再实例化我们的业务bean,这样业务bean才能被所有
BPP处理,如果有业务bean先于任何BPP实例化,那么这个业务bean就不会被还未实例化的BPP处理了,这个就是日志提示的原因。
到现在你应该理解了是shiroFilter
导致了userService
没有被@Transactional 对应的BPP处理,导致事务不起作用。虽然spring打印很多bean,一般最后打印的那个bean是导致问题的罪魁祸首,因为spring是先实例化被依赖的bean。
(参考 https://stackoverflow.com/questions/1201726/tracking-down-cause-of-springs-not-eligible-for-auto-proxying 第二个作者的回答)。
解决方法1:
shiroFilter
中的securityManager
可以先用一个代理bean。
public class DelegatingSecurityManager extends AbstractFactoryBean<WebSecurityManager> implements InvocationHandler, BeanClassLoaderAware {
@Autowired
@Lazy
private DefaultWebSecurityManager defaultWebSecurityManager;
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getDeclaringClass() == Object.class){
return method.invoke(proxy, args);
}
try{
return method.invoke(this.defaultWebSecurityManager, args);
}catch(InvocationTargetException e){
//代理调用,要剥掉最外层的InvocationTargetException
throw e.getTargetException();
}
}
@Override
public Class<?> getObjectType() {
return WebSecurityManager.class;
}
@Override
protected WebSecurityManager createInstance() throws Exception {
return (WebSecurityManager) Proxy.newProxyInstance(this.classLoader, new Class<?>[]{WebSecurityManager.class}, this);
}
}
解决方法2:
获取 AuthenticationInfo
和AuthorizationInfo
不要通过UserService/UserRoleService,可以单独写个Service或Dao供登录使用。