浅谈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和springcache整合时就碰到了,导致的结果就是springcache不起作用。现将问题场景、查找历程及解决方法展现一下。
1问题场景
打算在项目中将shiro与springcache整合,使用springcache统一管理缓存,也包括shiro认证时的用户信息查询。项目中将service分层,outter层负责权限和session,inner层主打事务和缓存并与DAO交互,两层之间也可以较容易的扩展为RPC或微服务模式。因此在shiro的authRealm中依赖了innerUserService,并在innerUserService中配置了springcache的标注,使用cache进行缓存。配置如下(摘录重要部分):
@Bean(name="shiroFilter") publicShiroFilterFactoryBeanshiroFilter( @Qualifier("securityManager")SecurityManagermanager ){ ShiroFilterFactoryBeanbean=newShiroFilterFactoryBean(); bean.setSecurityManager(manager); .............. returnbean; } //配置核心安全事务管理器 @Bean(name="securityManager") publicSecurityManagersecurityManager(@Qualifier("authRealm")AuthorizingRealmauthRealm, @Qualifier("sessionManager")SessionManagersessionManager, @Qualifier("cookieRememberMeManager")RememberMeManagerrememberMeManager, @Qualifier("cacheManager")CacheManagercacheManager){ System.err.println("--------------shiro已经加载----------------"); DefaultWebSecurityManagermanager=newDefaultWebSecurityManager(); manager.setRealm(authRealm); manager.setSessionManager(sessionManager); manager.setRememberMeManager(rememberMeManager); manager.setCacheManager(cacheManager); returnmanager; } //配置自定义权限登录器 @Bean(name="authRealm") publicAuthorizingRealmauthRealm(IInnerUserServiceuserService){ MyRealmmyrealm=newMyRealm(IInnerUserService); logger.info("authRealmmyRealminitiated!"); returnmyrealm; } @Bean publicLifecycleBeanPostProcessorlifecycleBeanPostProcessor(){ returnnewLifecycleBeanPostProcessor(Ordered.LOWEST_PRECEDENCE); }
其中MyRealm是自定义的shiroAuthorizingRealm,用于执行认证与授权,其实现依赖innerUserService从库中查找用户信息,示例代码如下:
publicclassMyRealmextendsAuthorizingRealm{ IInnerUserServiceuserService; publicMyRealm(){ super(); } publicMyRealm(IInnerUserServiceuserService){ this.userService=userService; } publicIInnerUserServicegetUserService(){ returnuserService; } publicvoidsetUserService(IInnerUserServiceuserService){ this.userService=userService; } @Override protectedAuthorizationInfodoGetAuthorizationInfo( PrincipalCollectionprincipals){ //nullusernamesareinvalid if(principals==null){ thrownewAuthorizationException("PrincipalCollectionmethodargumentcannotbenull."); } SetroleNames=newHashSet (); Set permissions=newHashSet (); Useruser=(User)getAvailablePrincipal(principals); roleNames.add("role1"); roleNames.add("role2"); permissions.add("user:create"); permissions.add("user:update"); permissions.add("user:delete"); SimpleAuthorizationInfoinfo=newSimpleAuthorizationInfo(roleNames); info.setStringPermissions(permissions); returninfo; } @Override protectedAuthenticationInfodoGetAuthenticationInfo( AuthenticationTokentoken)throwsAuthenticationException{ Stringusername=(String)token.getPrincipal();//得到用户名 Stringpassword=newString((char[])token.getCredentials());//得到密码 Useruser=userService.findByUsernameInner(username); if(user==null){ thrownewUnknownAccountException(); }elseif(!password.equals(user.getPassword())) { thrownewIncorrectCredentialsException(); } else{ returnnewSimpleAuthenticationInfo(user,password,getName()); } } }
而在innerUserService中配置了springcache的标注,示例代码如下:
@Service publicclassIInnerUserServiceImplimplementsIInnerUserService{ Loggerlogger=LoggerFactory.getLogger(IInnerUserServiceImpl.class); @Autowired IUserDaouserDao; @Override @Cacheable(value="mycache",key="#username") publicUserfindByUsernameInner(Stringusername){ Useruser=userDao.findByUsername(username); logger.info("Realexecutefindfromdatabase,username:{}",username); returnuser; } }
并在配置文件上标注了@EnableCaching(mode=AdviceMode.PROXY)以启动springcache。这里不过多解释具体shiro和springcache的使用,有兴趣的同学请自行搜索相关资料。
按理说这样的配置在认证的时候应该可以直接使用到innerUserService中配置的springcache缓存。
但,问题出现了,当authRealm中依赖了innerUserService以后,定义在innerUserService上的springcache就神奇的失效了。而authRealm不依赖innerUserService的时候,cache却运行的好好的。
接下来是问题查找的路径。
2解决问题之旅
2.1springcache失效的表象原因
首先要找到springcache失效的表象/直接原因,我们知道springcache使用SpringAOP和拦截器的方式拦截定义了特定标注的方法,然后执行特定逻辑。因此其实现依赖于动态代理机制auto-proxy,而经过初步调试发现,当被authRealm依赖以后,innerUserService就不会被代理了,因此无从进入AOP的pointcut,也就是说AOP切面失效了!
2.2从springcache的集成机制分析深层次原因
为何没有被代理呢,我们先来确认一下正常情况下什么时候进行代理封装,这时关于BeanPostProcessor的定义浮现脑海,据文档记载BeanPostProcessor允许在Bean实例化的前后对其做一些猥琐的事情,比如代理。我们在BeanPostProcessor的实现类中发现了InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、AbstractAutoProxyCreator、InfrastructureAdvisorAutoProxyCreator这一脉。而反观@enableCache标注在启动的时候会@importCachingConfigurationSelector,其selectImports方法会返回AutoProxyRegistrar和ProxyCachingConfiguration的全类名(我们定义了mode=AdviceMode.PROXY),也就是加载这两个类。第一个的作用就是注册InfrastructureAdvisorAutoProxyCreator到BeanDefinitionRegistry中。第二个的作用就是注册了BeanFactoryCacheOperationSourceAdvisor和CacheInterceptor。
因此,当正常情况下,一个添加了springcache相关标注的bean会在创建后被InfrastructureAdvisorAutoProxyCreator基于advisor进行代理增强,代理后便可在拦截器CacheInterceptor中对其方法进行拦截,然后执行cache相关逻辑。此处省略具体处理逻辑,有兴趣请参考相关文档。
所以第一怀疑就是innerUserService没有经过InfrastructureAdvisorAutoProxyCreator的代理增强。果然调试发现,被authRealm依赖的情况下在InnerUserService的Bean实例化时,用于处理该Bean的PostBeanProcessor明显比没被authRealm依赖时少,并且不含有InfrastructureAdvisorAutoProxyCreator。
而且,被依赖时会多打出来一行信息:
...................
Bean'IInnerUserServiceImpl'oftype[shiro.web.inner.service.impl.IInnerUserServiceImpl]isnoteligibleforgettingprocessedbyallBeanPostProcessors(forexample:noteligibleforauto-proxying)
...................
据此推断,可能是innerUserService启动时机过早,导致的后面那些BeanPostProcessor们来没来得及实例化及注册呢。
2.3BeanPostProcessor启动阶段对其依赖的Bean造成的影响
首先确认了authRealm也是受害者,因为shiroFilter->SecurityManager->authRealm的依赖关系导致其不得不提前实例化。表面上的罪魁祸首是shiroFilter,但是到底是谁导致的shiroFilter预料之外的提前启动呢。shiroFilter与InfrastructureAdvisorAutoProxyCreator的具体启动时机到底是什么时候呢。
又经过一番混天暗地的调试,终于了解了BeanPostProcessor的启动时机。在AbstractBeanFactory中维护了BeanPostProcessor的列表:
privatefinalListbeanPostProcessors=newArrayList ();
并实现了ConfigurableBeanFactory定义的方法:
voidaddBeanPostProcessor(BeanPostProcessorbeanPostProcessor);
因此我们首先监控AbstractBeanFactory.addBeanPostProcessor(),看看启动过程中谁调用了该方法来注册BeanPostProcessor。发现实例化及注册PostBeanFactory的阶段分为四个:
第一阶段是在启动时调用过程会调用AbstractApplicationContext.refresh(),其中的prepareBeanFactory方法中注册了
ApplicationContextAwareProcessor、ApplicationListenerDetector:
........
beanFactory.addBeanPostProcessor(newApplicationContextAwareProcessor(this));
........
beanFactory.addBeanPostProcessor(newApplicationListenerDetector(this));
........
然后在postProcessBeanFactory方法中注册了WebApplicationContextServletContextAwareProcessor:
beanFactory.addBeanPostProcessor( newWebApplicationContextServletContextAwareProcessor(this));
然后在invokeBeanFactoryPostProcessors方法中调用
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory,getBeanFactoryPostProcessors());
其中对已经注册的BeanFactoryPostProcessors挨个调用其postProcessBeanFactory方法,其中有一个ConfigurationClassPostProcessor,其postProcessBeanFactory方法中注册了一个ImportAwareBeanPostProcessor:
beanFactory.addBeanPostProcessor(newImportAwareBeanPostProcessor(beanFactory));
最后在registerBeanPostProcessors方法中调用
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory,this);
在该方法中,首先注册BeanPostProcessorChecker:
beanFactory.addBeanPostProcessor(newBeanPostProcessorChecker(beanFactory,beanProcessorTargetCount));
该BeanPostProcessorChecker就是输出上面那行信息的真凶,它会在Bean创建完后检查可在当前Bean上起作用的BeanPostProcessor个数与总的BeanPostProcessor个数,如果起作用的个数少于总数,则报出上面那句信息。
然后分成三个阶段依次实例化并注册实现了PriorityOrdered的BeanPostProcessor、实现了Ordered的BeanPostProcessor、没实现Ordered的BeanPostProcessor,代码如下:
//SeparatebetweenBeanPostProcessorsthatimplementPriorityOrdered, //Ordered,andtherest. ListpriorityOrderedPostProcessors=newArrayList (); List internalPostProcessors=newArrayList (); List orderedPostProcessorNames=newArrayList (); List nonOrderedPostProcessorNames=newArrayList (); for(StringppName:postProcessorNames){ if(beanFactory.isTypeMatch(ppName,PriorityOrdered.class)){ BeanPostProcessorpp=beanFactory.getBean(ppName,BeanPostProcessor.class); priorityOrderedPostProcessors.add(pp); if(ppinstanceofMergedBeanDefinitionPostProcessor){ internalPostProcessors.add(pp); } } elseif(beanFactory.isTypeMatch(ppName,Ordered.class)){ orderedPostProcessorNames.add(ppName); } else{ nonOrderedPostProcessorNames.add(ppName); } } //First,registertheBeanPostProcessorsthatimplementPriorityOrdered. sortPostProcessors(priorityOrderedPostProcessors,beanFactory); registerBeanPostProcessors(beanFactory,priorityOrderedPostProcessors); //Next,registertheBeanPostProcessorsthatimplementOrdered. List orderedPostProcessors=newArrayList (); for(StringppName:orderedPostProcessorNames){ BeanPostProcessorpp=beanFactory.getBean(ppName,BeanPostProcessor.class); orderedPostProcessors.add(pp); if(ppinstanceofMergedBeanDefinitionPostProcessor){ internalPostProcessors.add(pp); } } sortPostProcessors(orderedPostProcessors,beanFactory); registerBeanPostProcessors(beanFactory,orderedPostProcessors); //Now,registerallregularBeanPostProcessors. List nonOrderedPostProcessors=newArrayList (); for(StringppName:nonOrderedPostProcessorNames){ BeanPostProcessorpp=beanFactory.getBean(ppName,BeanPostProcessor.class); nonOrderedPostProcessors.add(pp); if(ppinstanceofMergedBeanDefinitionPostProcessor){ internalPostProcessors.add(pp); } } registerBeanPostProcessors(beanFactory,nonOrderedPostProcessors); //Finally,re-registerallinternalBeanPostProcessors. sortPostProcessors(internalPostProcessors,beanFactory); registerBeanPostProcessors(beanFactory,internalPostProcessors); //Re-registerpost-processorfordetectinginnerbeansasApplicationListeners, //movingittotheendoftheprocessorchain(forpickingupproxiesetc). beanFactory.addBeanPostProcessor(newApplicationListenerDetector(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:{@codePriorityOrdered}post-processorbeansareinitializedin
*aspecialphase,aheadofotherpost-processorbeans.Thissubtly
*affectstheirautowiringbehavior:theywillonlybeautowiredagainst
*beanswhichdonotrequireeagerinitializationfortypematching.
2.4BeanPostProcessor在进行依赖的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方法时出现:
publicbooleanisTypeMatch(Stringname,ResolvableTypetypeToMatch)throwsNoSuchBeanDefinitionException{ ..................... //Checkbeanclasswhetherwe'redealingwithaFactoryBean. if(FactoryBean.class.isAssignableFrom(beanType)){//(1)判断名称对应的Bean是否是一个FactoryBean,若是FactoryBean才执行本句 if(!BeanFactoryUtils.isFactoryDereference(name)){ //Ifit'saFactoryBean,wewanttolookatwhatitcreates,notthefactoryclass. beanType=getTypeForFactoryBean(beanName,mbd); if(beanType==null){ returnfalse; } } } ..................... }
然后进入AbstractAutowireCapableBeanFactory.getTypeForFactoryBean方法:
@Override protectedClass>getTypeForFactoryBean(StringbeanName,RootBeanDefinitionmbd){ StringfactoryBeanName=mbd.getFactoryBeanName(); StringfactoryMethodName=mbd.getFactoryMethodName(); if(factoryBeanName!=null){ if(factoryMethodName!=null){ //TrytoobtaintheFactoryBean'sobjecttypefromitsfactorymethoddeclaration //withoutinstantiatingthecontainingbeanatall. BeanDefinitionfbDef=getBeanDefinition(factoryBeanName); if(fbDefinstanceofAbstractBeanDefinition){ AbstractBeanDefinitionafbDef=(AbstractBeanDefinition)fbDef; if(afbDef.hasBeanClass()){ Class>result=getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(),factoryMethodName); if(result!=null){ returnresult; } } } } //Ifnotresolvableaboveandthereferencedfactorybeandoesn'texistyet, //exithere-wedon'twanttoforcethecreationofanotherbeanjustto //obtainaFactoryBean'sobjecttype... if(!isBeanEligibleForMetadataCaching(factoryBeanName)){//(2)判断该bean对应的factoryBeanName是否已经初始化了,如果没有,就返回。如果有,则继续 returnnull; } } //Let'sobtainashortcutinstanceforanearlygetObjectType()call... FactoryBean>fb=(mbd.isSingleton()? getSingletonFactoryBeanForTypeCheck(beanName,mbd): getNonSingletonFactoryBeanForTypeCheck(beanName,mbd)); ...................... }
其中,有一个重要的判断:
//Ifnotresolvableaboveandthereferencedfactorybeandoesn'texistyet, //exithere-wedon'twanttoforcethecreationofanotherbeanjustto //obtainaFactoryBean'sobjecttype... if(!isBeanEligibleForMetadataCaching(factoryBeanName)){ returnnull; }
注解说的很明确,如果名字对应的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.1BeanPostProcessor启动顺序,以及其对于依赖的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)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。