1、前言
- 我几乎查阅了所有平台的shiro配置,发现99%的shiro配置都存在问题,具体问题请看下面的分析
- 技术栈:springboot+shiro+redis
- jdk版本1.8
2、事由
在一次排查代码注入失败的过程中,发现代码总是重复执行,但是最终结果却是正确的。这给调试带来了麻烦,所以决定一探究竟。
问题:bean 被代理了两次,所以代理执行了两次,方法执行了一次,所以最终结果是正确的。
举例:源对象 →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的生命周期熟悉,才能更加深入的理解这个问题。