在项目里面引入了 shiro
框架,然而在用户登录的时候始终会出现数据库访问异常,异常信息如下:
从异常日志来看是找错了数据库,因为项目中使用了多数据源技术,通过在service层加上 @DS(Database.DATABASE_CA_SYSTEM)
注解来标示该service需要使用哪个数据源。这个异常说明注解并没有实现它该有的作用。
多数据源使用的是 baomidou
开发的框架,相关依赖如下,它的核心是通过AOP技术来切换每个service需要使用的数据源,具体实现这里先不讨论。
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> </dependency> 复制代码
上面的异常说明 userService
这个 bean
并没有被代理,那么问题出来哪里呢?
springboot
的启动日志中发现一个值得注意的点:
Warn: Could not find @TableId in Class: com.greenet.platform.common.entity.User. 2019-11-14 15:50:42.487 INFO 17752 --- [ main] trationDelegate BeanPostProcessorChecker : Bean 'userMapper' of type [com.sun.proxy. BeanPostProcessorChecker : Bean 'userService' of type [com.greenet.platform.common.service.impl.UserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2019-11-14 15:50:42.555 INFO 17752 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'cacheManager' of type [org.apache.shiro.cache.ehcache.EhCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2019-11-14 15:50:42.556 INFO 17752 --- [ main] o.a.shiro.cache.ehcache.EhCacheManager : Using existing EHCache named [passwordRetryCache
重点在这句话:
trationDelegate$BeanPostProcessorChecker : Bean 'userService' of type [com.greenet.platform.common.service.impl.UserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
这句话意思是 userService
由于不符合某种条件,导致不会被自动代理,那么这个条件是什么呢?
查看 BeanPostProcessorChecker
的源码,打印日志的地方如下:
@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) { if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) && this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { if (logger.isInfoEnabled()) { logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() + "] is not eligible for getting processed by all BeanPostProcessors " + "(for example: not eligible for auto-proxying)"); } } return bean; } 复制代码
日志打印出来的原因是 this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount
返回了 true
,也就是说此时注册到 beanFactory
的后置处理器数量是少于总的后置处理器数量的,也就是说这个时候有其他后置处理器还没准备好, userService
就已经被实例化了;
既然这个地方说了 userService
不会被代理,那么这个 bean
又是在什么时候被 spring
实例化的呢。想搞清楚这个问题很简单,可以在 AbstractBeanFactory.doGetBean
方法里面打一个条件断点,然后看调用栈。
得到如下调用栈:
"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:242) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$121.1862994526.getObject(Unknown Source:-1) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) - locked <0x12b7> (a java.util.concurrent.ConcurrentHashMap) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getSingletonFactoryBeanForTypeCheck(AbstractAutowireCapableBeanFactory.java:991) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:865) at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:574) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:514) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:477) at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:227) at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1411) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1210) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$121.1862994526.getObject(Unknown Source:-1) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:228) at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:721) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:534) - locked <0x12ea> (a java.lang.Object) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) at com.greenet.platform.Application.main(Application.java:24) 复制代码
从调用栈可以看到两个重点信息:
AbstractBeanFactory.doGetBean AbstractApplicationContext.registerBeanPostProcessors
这两点说明 userService
不是在正常的 AbstractApplicationContext.finishBeanFactoryInitialization
阶段通过实例化,而是在注册后置处理器的时候就实例化了。
为什么会提前实例化呢?
MethodValidationPostProcessor
从上面打的断点回头追溯到 registerBeanPostProcessors
的地方,可以看到此时注册的后置处理器 MethodValidationPostProcessor
,调试截图如下:
是对应的代码如下:
for (String ppName : orderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); orderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } 复制代码
也就是说 userService
在 MethodValidationPostProcessor
这个后置处理器注册前的实例化的过程中,因为某种原因被提前实例化了。
那么到底是为啥实例化 MethodValidationPostProcessor
的时候需要实例化 userService
呢
通过一波调试发现,问题出来我的 shiro
配置里面
@Bean("securityManager") public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager, EhCacheManager cacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); securityManager.setSessionManager(sessionManager); securityManager.setRememberMeManager(null); securityManager.setCacheManager(cacheManager); return securityManager; } /** * shiro权限验证 * * @param securityManager 安全管理器 * @return 安全管理factorybean */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setLoginUrl("/login"); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/static/**", "anon"); filterMap.put("/", "anon"); filterMap.put("/login/getVerifyCodeImage", "anon"); filterMap.put("/**", "authc"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } 复制代码
罪魁祸首是这个 ShiroFilterFactoryBean
,这个东西的定义如下:
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor { 复制代码
它实现了 FactoryBean, BeanPostProcessor
这两个特殊的接口,所有的 FactoryBean
会在后置处理器 MethodValidationPostProcessor
实例化过程中被实例化,为啥呢,这跟这个后置处理器的定义的地方有关系,这个后置处理器在 ValidationAutoConfiguration
类中定义,具体方法代码如下:
@Bean @ConditionalOnMissingBean public static MethodValidationPostProcessor methodValidationPostProcessor( Environment environment, @Lazy Validator validator) { MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); boolean proxyTargetClass = environment .getProperty("spring.aop.proxy-target-class", Boolean.class, true); processor.setProxyTargetClass(proxyTargetClass); processor.setValidator(validator); return processor; } 复制代码
想要实例化这个类,需要指导这个方法的参数,问题就出在这个 Environment
里面,为了找到类型为 Environment
的参数,spring会遍历整个 beanDefinitionNames
,然后去找到符合 Environment
这个类型的 bean
,找的过程中发现有些 bean
是 factoryBean
,那不好意思,需要把这个 factoryBean
给实例化出来,再看上面 ShiroFilterFactoryBean
定义的方法可以看到,它依赖了 securityManager
,而 securityManager
又依赖了 userRealm
,而 userRealm
有依赖了 userService
:
@Component public class UserRealm extends AuthorizingRealm { private static final String ACCOUNT_TYPE_WEB = "2"; private static final String ACCOUNT_TYPE_ALL = "255"; @Autowired UserService userService; 复制代码
就这样一步步把 userService
给实例化了,这个时候还没到后置处理器处理bean的时候,那负责做AOP代理的后置处理器自然无法再去处理 userService
了。