您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關BeanPostProcessor加載次序及其對Bean造成影響的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
前言
BeanPostProcessor是一個工廠鉤子,允許Spring框架在新創建Bean實例時對其進行定制化修改。例如:通過檢查其標注的接口或者使用代理對其進行包裹。應用上下文會從Bean定義中自動檢測出BeanPostProcessor并將它們應用到隨后創建的任何Bean上。
普通Bean對象的工廠允許在程序中注冊post-processors,應用到隨后在本工廠中創建的所有Bean上。典型的場景如:post-processors使用postProcessBeforeInitialization方法通過特征接口或其他類似的方式來填充Bean;而為創建好的Bean創建代理則一般使用postProcessAfterInitialization方法。
BeanPostProcessor本身也是一個Bean,一般而言其實例化時機要早過普通的Bean,但是BeanPostProcessor也會依賴一些Bean,這就導致了一些Bean的實例化早于BeanPostProcessor,由此會導致一些問題。最近在處理shiro和spring cache整合時就碰到了,導致的結果就是spring cache不起作用。現將問題場景、查找歷程及解決方法展現一下。
1 問題場景
打算在項目中將shiro與spring cache整合,使用spring cache統一管理緩存,也包括shiro認證時的用戶信息查詢。項目中將service分層,outter層負責權限和session,inner層主打事務和緩存并與DAO交互,兩層之間也可以較容易的擴展為RPC或微服務模式。因此在shiro的authRealm中依賴了innerUserService,并在innerUserService中配置了spring cache的標注,使用cache進行緩存。配置如下(摘錄重要部分):
@Bean(name="shiroFilter") public ShiroFilterFactoryBean shiroFilter( @Qualifier("securityManager") SecurityManager manager ) { ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); bean.setSecurityManager(manager); .............. return bean; } //配置核心安全事務管理器 @Bean(name="securityManager") public SecurityManager securityManager(@Qualifier("authRealm") AuthorizingRealm authRealm, @Qualifier("sessionManager") SessionManager sessionManager, @Qualifier("cookieRememberMeManager") RememberMeManager rememberMeManager, @Qualifier("cacheManager") CacheManager cacheManager) { System.err.println("--------------shiro已經加載----------------"); DefaultWebSecurityManager manager=new DefaultWebSecurityManager(); manager.setRealm(authRealm); manager.setSessionManager(sessionManager); manager.setRememberMeManager(rememberMeManager); manager.setCacheManager(cacheManager); return manager; } //配置自定義權限登錄器 @Bean(name="authRealm") public AuthorizingRealm authRealm(IInnerUserService userService) { MyRealm myrealm = new MyRealm(IInnerUserService); logger.info("authRealm myRealm initiated!"); return myrealm; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(Ordered.LOWEST_PRECEDENCE); }
其中MyRealm是自定義的shiro AuthorizingRealm,用于執行認證與授權,其實現依賴innerUserService從庫中查找用戶信息,示例代碼如下:
public class MyRealm extends AuthorizingRealm { IInnerUserService userService; public MyRealm(){ super(); } public MyRealm(IInnerUserService userService){ this.userService = userService; } public IInnerUserService getUserService() { return userService; } public void setUserService(IInnerUserService userService) { this.userService = userService; } @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //null usernames are invalid if (principals == null) { throw new AuthorizationException("PrincipalCollection method argument cannot be null."); } Set<String> roleNames = new HashSet<String>(); Set<String> permissions = new HashSet<String>(); User user = (User)getAvailablePrincipal(principals); roleNames.add("role1"); roleNames.add("role2"); permissions.add("user:create"); permissions.add("user:update"); permissions.add("user:delete"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames); info.setStringPermissions(permissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); //得到用戶名 String password = new String((char[])token.getCredentials()); //得到密碼 User user = userService.findByUsernameInner(username); if(user==null){ throw new UnknownAccountException(); }else if(!password.equals(user.getPassword())) { throw new IncorrectCredentialsException(); } else{ return new SimpleAuthenticationInfo(user, password, getName()); } } }
而在innerUserService中配置了spring cache的標注,示例代碼如下:
@Service public class IInnerUserServiceImpl implements IInnerUserService { Logger logger = LoggerFactory.getLogger(IInnerUserServiceImpl.class); @Autowired IUserDao userDao; @Override @Cacheable(value = "mycache", key = "#username") public User findByUsernameInner(String username) { User user = userDao.findByUsername(username); logger.info("Real execute find from database, username:{}", username); return user; } }
并在配置文件上標注了@EnableCaching(mode=AdviceMode.PROXY)以啟動spring cache。這里不過多解釋具體shiro和spring cache的使用,有興趣的同學請自行搜索相關資料。
按理說這樣的配置在認證的時候應該可以直接使用到innerUserService中配置的spring cache緩存。
但,問題出現了,當authRealm中依賴了innerUserService以后,定義在innerUserService上的spring cache就神奇的失效了。而authRealm不依賴innerUserService的時候,cache卻運行的好好的。
接下來是問題查找的路徑。
2 解決問題之旅
2.1 spring cache失效的表象原因
首先要找到spring cache失效的表象/直接原因,我們知道spring cache使用Spring AOP和攔截器的方式攔截定義了特定標注的方法,然后執行特定邏輯。因此其實現依賴于動態代理機制auto-proxy,而經過初步調試發現,當被authRealm依賴以后,innerUserService就不會被代理了,因此無從進入AOP的pointcut,也就是說AOP切面失效了!
2.2 從spring cache的集成機制分析深層次原因
為何沒有被代理呢,我們先來確認一下正常情況下什么時候進行代理封裝,這時關于BeanPostProcessor的定義浮現腦海,據文檔記載BeanPostProcessor允許在Bean實例化的前后對其做一些猥瑣的事情,比如代理。我們在BeanPostProcessor的實現類中發現了InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、AbstractAutoProxyCreator、InfrastructureAdvisorAutoProxyCreator這一脈。而反觀@enableCache標注在啟動的時候會@import CachingConfigurationSelector,其selectImports方法會返回AutoProxyRegistrar和ProxyCachingConfiguration的全類名(我們定義了mode=AdviceMode.PROXY),也就是加載這兩個類。第一個的作用就是注冊InfrastructureAdvisorAutoProxyCreator到BeanDefinitionRegistry中。第二個的作用就是注冊了BeanFactoryCacheOperationSourceAdvisor和CacheInterceptor。
因此,當正常情況下,一個添加了spring cache相關標注的bean會在創建后被InfrastructureAdvisorAutoProxyCreator基于advisor進行代理增強,代理后便可在攔截器CacheInterceptor中對其方法進行攔截,然后執行cache相關邏輯。此處省略具體處理邏輯,有興趣請參考相關文檔。
所以第一懷疑就是innerUserService沒有經過InfrastructureAdvisorAutoProxyCreator的代理增強。果然調試發現,被authRealm依賴的情況下在InnerUserService的Bean實例化時,用于處理該Bean的PostBeanProcessor明顯比沒被authRealm依賴時少,并且不含有InfrastructureAdvisorAutoProxyCreator。
而且,被依賴時會多打出來一行信息:
...................
Bean 'IInnerUserServiceImpl' of type [shiro.web.inner.service.impl.IInnerUserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
...................
據此推斷,可能是innerUserService啟動時機過早,導致的后面那些BeanPostProcessor們來沒來得及實例化及注冊呢。
2.3 BeanPostProcessor啟動階段對其依賴的Bean造成的影響
首先確認了authRealm也是受害者,因為shiroFilter->SecurityManager->authRealm的依賴關系導致其不得不提前實例化。表面上的罪魁禍首是shiroFilter,但是到底是誰導致的shiroFilter預料之外的提前啟動呢。shiroFilter與InfrastructureAdvisorAutoProxyCreator的具體啟動時機到底是什么時候呢。
又經過一番混天暗地的調試,終于了解了BeanPostProcessor的啟動時機。在AbstractBeanFactory中維護了BeanPostProcessor的列表:
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();
并實現了ConfigurableBeanFactory定義的方法:
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
因此我們首先監控AbstractBeanFactory.addBeanPostProcessor(),看看啟動過程中誰調用了該方法來注冊BeanPostProcessor。發現實例化及注冊PostBeanFactory的階段分為四個:
第一階段是在啟動時調用過程會調用AbstractApplicationContext.refresh(),其中的prepareBeanFactory方法中注冊了
ApplicationContextAwareProcessor、ApplicationListenerDetector:
........
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
........
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
........
然后在postProcessBeanFactory方法中注冊了WebApplicationContextServletContextAwareProcessor:
beanFactory.addBeanPostProcessor( new WebApplicationContextServletContextAwareProcessor(this));
然后在invokeBeanFactoryPostProcessors方法中調用
復制代碼 代碼如下:
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
其中對已經注冊的BeanFactoryPostProcessors挨個調用其postProcessBeanFactory方法,其中有一個ConfigurationClassPostProcessor,其postProcessBeanFactory方法中注冊了一個ImportAwareBeanPostProcessor:
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
最后在registerBeanPostProcessors方法中調用
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
在該方法中,首先注冊BeanPostProcessorChecker:
復制代碼 代碼如下:
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
該BeanPostProcessorChecker就是輸出上面那行信息的真兇,它會在Bean創建完后檢查可在當前Bean上起作用的BeanPostProcessor個數與總的BeanPostProcessor個數,如果起作用的個數少于總數,則報出上面那句信息。
然后分成三個階段依次實例化并注冊實現了PriorityOrdered的BeanPostProcessor、實現了Ordered的BeanPostProcessor、沒實現Ordered的BeanPostProcessor,代碼如下:
// Separate between BeanPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>(); List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>(); List<String> orderedPostProcessorNames = new ArrayList<String>(); List<String> nonOrderedPostProcessorNames = new ArrayList<String>(); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); priorityOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // First, register the BeanPostProcessors that implement PriorityOrdered. sortPostProcessors(priorityOrderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // Next, register the BeanPostProcessors that implement Ordered. List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>(); for (String ppName : orderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); orderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } sortPostProcessors(orderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, orderedPostProcessors); // Now, register all regular BeanPostProcessors. List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>(); for (String ppName : nonOrderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); nonOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // Finally, re-register all internal BeanPostProcessors. sortPostProcessors(internalPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, internalPostProcessors); // Re-register post-processor for detecting inner beans as ApplicationListeners, // moving it to the end of the processor chain (for picking up proxies etc). beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
需要注意的是,除了第一個階段,其他階段同一個階段的BeanPostProcessor是在全部實例化完成以后才會統一注冊到beanFactory的,因此,同一個階段的BeanPostProcessor及其依賴的Bean在實例化的時候是無法享受到相同階段但是先實例化的BeanPostProcessor的“服務”的,因為它們還沒有注冊。
從上面調試與源代碼分析,BeanPostProcessor的實例化與注冊分為四個階段,第一階段applicationContext內置階段、第二階段priorityOrdered階段、第三階段Ordered階段、第四階段nonOrdered階段。而BeanPostProcessor同時也是Bean,其注冊之前一定先實例化。而且是分批實例化和注冊,也就是屬于同一批的BeanPostProcesser全部實例化完成后,再全部注冊,不存在先實例化先注冊的問題。而在實例化的時候其依賴的Bean同樣要先實例化。
因此導致一個結果就是,被PriorityOrderedBeanPostProcessor所依賴的Bean其初始化時無法享受到PriorityOrdered、Ordered、和nonOrdered的BeanPostProcessor的服務。而被OrderedBeanPostProcessor所依賴的Bean無法享受Ordered、和nonOrdered的BeanPostProcessor的服務。最后被nonOrderedBeanPostProcessor所依賴的Bean無法享受到nonOrderedBeanPostProcessor的服務。
由于InfrastructureAdvisorAutoProxyCreator的啟動階段是Ordered,因此我們需要確保沒有任何priorityOrdered和Ordered的BeanPostProcessor直接或間接的依賴到shiroFilter,也就是依賴到我們的innerUserService。
同時,在PriorityOrdered接口的注解中也提到了該情況:
Note: {@code PriorityOrdered} post-processor beans are initialized in
* a special phase, ahead of other post-processor beans. This subtly
* affects their autowiring behavior: they will only be autowired against
* beans which do not require eager initialization for type matching.
2.4 BeanPostProcessor在進行依賴的Bean注入時,根據Bean名稱進行類型檢查時導致的“誤傷”
OK,問題貌似已查明,修改Configuration中所有PriorityOrdered和Ordered類型的PostBeanProcessor的Bean配置,使其不再依賴shiroFilter。再次啟動,卻發現仍然提前啟動了shiroFilter->SecurityManager->authRealm->innerUserService。
百思不得其解,又是一輪昏天暗地的調試,查找shiroFilter具體的啟動時機。發現在一個叫做dataSourceInitializerPostProcessor的BeanPostProcessor實例化的時候,在根據類型獲得其依賴的參數時,對shiroFilter執行了初始化。導致后續SecurityManager->authRealm->innerUserService統統提前初始化。但是在dataSourceInitializerPostProcessor之前的BeanPostProcessor卻沒有。經調試它們是否會導致shiroFilter初始化的區別在調用AbstractBeanFactory.isTypeMatch方法時出現:
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException{ ..................... // Check bean class whether we're dealing with a FactoryBean. if (FactoryBean.class.isAssignableFrom(beanType)) { //(1)判斷名稱對應的Bean是否是一個FactoryBean,若是FactoryBean才執行本句 if (!BeanFactoryUtils.isFactoryDereference(name)) { // If it's a FactoryBean, we want to look at what it creates, not the factory class. beanType = getTypeForFactoryBean(beanName, mbd); if (beanType == null) { return false; } } } ..................... }
然后進入AbstractAutowireCapableBeanFactory.getTypeForFactoryBean方法:
@Override protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { String factoryBeanName = mbd.getFactoryBeanName(); String factoryMethodName = mbd.getFactoryMethodName(); if (factoryBeanName != null) { if (factoryMethodName != null) { // Try to obtain the FactoryBean's object type from its factory method declaration // without instantiating the containing bean at all. BeanDefinition fbDef = getBeanDefinition(factoryBeanName); if (fbDef instanceof AbstractBeanDefinition) { AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef; if (afbDef.hasBeanClass()) { Class<?> result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName); if (result != null) { return result; } } } } // If not resolvable above and the referenced factory bean doesn't exist yet, // exit here - we don't want to force the creation of another bean just to // obtain a FactoryBean's object type... if (!isBeanEligibleForMetadataCaching(factoryBeanName)) { //(2)判斷該bean對應的factoryBeanName是否已經初始化了,如果沒有,就返回。如果有,則繼續 return null; } } // Let's obtain a shortcut instance for an early getObjectType() call... FactoryBean<?> fb = (mbd.isSingleton() ? getSingletonFactoryBeanForTypeCheck(beanName, mbd) : getNonSingletonFactoryBeanForTypeCheck(beanName, mbd)); ...................... }
其中,有一個重要的判斷:
// If not resolvable above and the referenced factory bean doesn't exist yet, // exit here - we don't want to force the creation of another bean just to // obtain a FactoryBean's object type... if (!isBeanEligibleForMetadataCaching(factoryBeanName)) { return null; }
注解說的很明確,如果名字對應的factoryBean所在的factoryBean工廠尚未解析并實例化,那就直接退出,不會強制創建該facotryBean工廠,也就是Configuration對應的Bean。再次調試,果然發現,在先前的BeanPostProcessor和dataSourceInitializerPostProcessor之間,存在一個lifecycleBeanPostProcessor,而lifecycleBeanPostProcessor是在我們的Configuration中顯示定義的,因此,當lifecycleBeanPostProcessor啟動時會導致Configuration實例化。
dataSourceInitializerPostProcessor和在它之前的BeanPostProcessor對shiroFilter行為的不同在這里得到了完美的解釋。本質上說dataSourceInitializerPostProcessor并不重要,重要的是lifecycleBeanPostProcessor將Configuration初始化了。就算不是dataSourceInitializerPostProcessor,那另一個BeanPostProcessor實例化時同樣會將shiroFilter初始化。
最終隱藏大BOSS查明,解決方案就簡單了,將lifecycleBeanPostProcessor移出到一個單獨的Configuration就好了。
3. 總結
3.1 BeanPostProcessor啟動順序,以及其對于依賴的Bean的影響
BeanPostProcessor的啟動時機。分為四個階段,第一階段context內置階段、第二階段priorityOrdered階段、第三階段Ordered階段、第四階段nonOrdered階段。
而BeanPostProcessor同時也是Bean,其注冊之前一定先實例化。而且是分批實例化和注冊,也就是屬于同一批的BeanPostProcesser全部實例化完成后,再全部注冊,不存在先實例化先注冊的問題。而在實例化的時候其依賴的Bean同樣要先實例化。
因此導致一個結果就是,被PriorityOrderedBeanPostProcessor所依賴的Bean其初始化以后無法享受到PriorityOrdered、Ordered、和nonOrdered的BeanPostProcessor的服務。而被OrderedBeanPostProcessor所依賴的Bean無法享受Ordered、和nonOrdered的BeanPostProcessor的服務。最后被nonOrderedBeanPostProcessor所依賴的Bean無法享受到nonOrderedBeanPostProcessor的服務。
3.2 注意避免BeanPostProcessor啟動時的“誤傷”陷阱
BeanPostProcessor實例化時,自動依賴注入根據類型獲得需要注入的Bean時,會將某些符合條件的Bean(FactoryBean并且其FactoryBeanFactory已經實例化的)先實例化,如果此FacotryBean又依賴其他普通Bean,會導致該Bean提前啟動,造成誤傷(無法享受部分BeanPostProcessor的后處理,例如典型的auto-proxy)。
感謝各位的閱讀!關于“BeanPostProcessor加載次序及其對Bean造成影響的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。