市场上99%的shiro框架使用都有bug

264 阅读2分钟

1、前言

  • 我几乎查阅了所有平台的shiro配置,发现99%的shiro配置都存在问题,具体问题请看下面的分析
  • 技术栈:springboot+shiro+redis
  • jdk版本1.8

2、事由

在一次排查代码注入失败的过程中,发现代码总是重复执行,但是最终结果却是正确的。这给调试带来了麻烦,所以决定一探究竟。

问题:bean 被代理了两次,所以代理执行了两次,方法执行了一次,所以最终结果是正确的。

33.png

举例:源对象 →A →B那么代理对象B是源对象的最终的代理对象,在执行时,B → A → 源对象,但是执行结果却是正确的。

3、经过排查,很快发现问题,我们系统在融合shiro时候,引入了一个默认的代理类创建器

@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // 强制使用cglib,防止重复代理和可能引起代理出错的问题
    // https://zhuanlan.zhihu.com/p/29161098
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
}

@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
}

但是在引入了,@EnableAspectJAutoProxy(proxyTargetClass = true)  注解,相当于系统帮我们已经注入了一个代理创建器。AnnotationAwareAspectJAutoProxyCreator类。这时候系统就会有两个代理生成器,就会出现二次代理的问题。

系统先通过AnnotationAwareAspectJAutoProxyCreator  给 源对象生成代理A,紧接着再通过DefaultAdvisorAutoProxyCreator  生成B。最终B对象就是源对象的代理对象。

源码分析:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
       throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
       result = beanProcessor.postProcessAfterInitialization(result, beanName);
       if (result == null) {
          return result;
       }
    }
    return result;
}


protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
       return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
       return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
       this.advisedBeans.put(cacheKey, Boolean.FALSE);
       return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
       this.advisedBeans.put(cacheKey, Boolean.TRUE);
       Object proxy = createProxy(
             bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
       this.proxyTypes.put(cacheKey, proxy.getClass());
       return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

3、二次代理对系统的影响

  • 系统启动比较慢
  • 代码执行效率变低
  • 生成大量无用的代理类,影响jvm  堆大小和metaspace  的大小

4、解决方案

删除手动创建的代理器的代码

删除的代码如下:

@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // 强制使用cglib,防止重复代理和可能引起代理出错的问题
    // https://zhuanlan.zhihu.com/p/29161098
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
}

总结:

1、该问题并不好排查,因为程序的执行逻辑和执行结果是正确的。所以在springmvc  和  spring  boot混合使用的时候,一定要保证系统只能有一个代理类创建器。

2、需要对springaop和spring的生命周期熟悉,才能更加深入的理解这个问题。