中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

BeanPostProcessor加載次序及其對Bean造成影響的示例分析

發布時間:2021-09-09 14:17:47 來源:億速云 閱讀:162 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關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造成影響的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鹿泉市| 清涧县| 惠安县| 石狮市| 柳州市| 扶绥县| 定西市| 廊坊市| 吐鲁番市| 汶川县| 霍山县| 东乡族自治县| 铜山县| 南宫市| 兴业县| 阿拉善左旗| 乐都县| 新泰市| 平罗县| 徐州市| 建德市| 泰顺县| 平南县| 蓬安县| 于田县| 乌恰县| 新巴尔虎左旗| 孝感市| 雅安市| 大姚县| 冕宁县| 潼南县| 恩施市| 石柱| 绥德县| 翁源县| 大庆市| 晋宁县| 洪泽县| 曲松县| 江阴市|