在項目裏面引入了shiro
框架,然而在用戶登陸的時候始終會出現數據庫訪問異常,異常信息以下:java
從異常日誌來看是找錯了數據庫,由於項目中使用了多數據源技術,經過在service層加上@DS(Database.DATABASE_CA_SYSTEM)
註解來標示該service須要使用哪一個數據源。這個異常說明註解並無實現它該有的做用。web
多數據源使用的是baomidou
開發的框架,相關依賴以下,它的核心是經過AOP技術來切換每一個service須要使用的數據源,具體實現這裏先不討論。spring
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
複製代碼
上面的異常說明userService
這個bean
並無被代理,那麼問題出來哪裏呢?數據庫
springboot
的啓動日誌中發現一個值得注意的點:apache
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安全
重點在這句話:springboot
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)
bash
這句話意思是userService
因爲不符合某種條件,致使不會被自動代理,那麼這個條件是什麼呢?session
查看BeanPostProcessorChecker
的源碼,打印日誌的地方以下:mybatis
@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
了。
那麼如何去解決這個問題呢,思路是不要在factoryBean
裏面去注入業務類,一種比較簡單的辦法是去注入ApplicationContext
,而後經過getBean
方法拿到業務的service
private UserService getUserService() {
return (UserService) applicationContext.getBean("userService");
}
複製代碼