引入shiro后userService事务不生效原因(is no

2018-11-08  本文已影响0人  多关心老人

系统spring集成了shiro,配置shiroFilter:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
</bean>

shiroFilter依赖了securityManagersecurityManager依赖了userRealmuserRealm为了获取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:
获取 AuthenticationInfoAuthorizationInfo不要通过UserService/UserRoleService,可以单独写个Service或Dao供登录使用。

上一篇下一篇

猜你喜欢

热点阅读