java

面試官:談談Spring中的NoSuchBeanDefinitionException原始碼,沒想到我剛好對spring原始碼很熟悉!

概述

org.springframework.beans.factory.NoSuchBeanDefinitionException 是很常見的異常,可以說絕大多數使用過 的人都曾遇到過它。本文旨在總結下NoSuchBeanDefinitionException(以下簡稱 NSBDE)的含義,哪些情況下可能丟擲 NSBDE,和如何解決(文中配置均用 JavaConfig)。

我在這裡分享一個,有很多幹貨,包含jvm,netty,spring,執行緒,spring cloud等詳細講解,也有詳細的學習規劃圖,面試題整理等,我感覺在面試這塊講的非常清楚:獲取面試資料只需:點選這裡領取!!! 暗號:CSDN在這裡插入圖片描述

什麼是 NoSuchBeanDefinitionException

從字面其實就很好理解,NoSuchBeanDefinitionException 就是沒有找到指定 Bean 的 Definition。NoSuchBeanDefinitionException 的 JavaDoc是這樣定義的:

Exception thrown when a BeanFactory is asked for a bean instance for which it cannot find a definition. This may point to a non-existing bean, a non-unique bean, or a manually registered singleton instance without an associated bean definition.

下面看看可能丟擲 NSBDE 的一些情況。

情況1: No qualifying bean of type […] found for dependency

最常見的丟擲 NSBDE 的情況就是在一個 BeanA 中注入 BeanB 時找不到 BeanB 的定義。例子如下:

@Component
public class BeanA {
    @Autowired
    private BeanB dependency;
    //...
}

當在 BeanA 中注入 BeanB 時,如果在 Spring 上下文中找不到 BeanB 的定義,就會丟擲 NSBDE。異常資訊如下:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type [org.baeldung.packageB.BeanB]
  found for dependency: 
    expected at least 1 bean which qualifies as
  autowire candidate for this dependency. 
    Dependency annotations: 
  {@org.springframework.beans.factory.annotation.Autowired(required=true)}

拋異常的原因在異常資訊中說的很清楚:expected at least 1 bean which qualifies as autowire candidate for this dependency。所以要麼是 BeanB 不存在在 Spring 上下文中(比如沒有標註 @ Component,@Repository,@Service, @Controller等註解) ,要麼就是 BeanB 所在的包沒有被 Spring 掃描到。

解決辦法就是先確認 BeanB 有沒有被某些註解宣告為 Bean:

package org.baeldung.packageB;
@Component
public class BeanB { ...}

如果 BeanB 已經被宣告為一個 Bean,就再確認 BeanB 所在的包有沒有被掃描。

@Configuration
@ComponentScan("org.baeldung.packageB")
public class ContextWithJavaConfig {
}

情況2: No qualifying bean of type […] is defined

還有一種可能丟擲 NSBDE 的情況是在上下文中存在著兩個 Bean,比如有一個介面 IBeanB,它有兩個實現類 BeanB1 和 BeanB2。

@Component
public class BeanB1 implements IBeanB {
    //
}
@Component
public class BeanB2 implements IBeanB {
    //
}

現在,如果 BeanA 按照下面的方式注入,那麼 Spring 將不知道要注入兩個實現中的哪一個,就會丟擲 NSBDE。

@Component
public class BeanA {
    @Autowired
    private IBeanB dependency;
}

異常資訊如下:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type
  [org.baeldung.packageB.IBeanB] is defined: 
    expected single matching bean but found 2: beanB1,beanB2

仔細看異常資訊會發現,並不是直接丟擲 NSBDE,而是它的子類 NoUniqueBeanDefinitionException,這是 Spring 3.2.1 之後引入的新異常,目的就是為了和第一種找不到 Bean Definition 的情況作區分。

解決辦法1就是利用 @Qualifier 註解,明確指定要注入的 Bean 的名字(BeanB2 預設的名字就是 beanB2)。

@Component
public class BeanA {
    @Autowired
    @Qualifier("beanB2")
    private IBeanB dependency;
}

除了指定名字,我們還可以將其中一個 Bean 加上 @Primary的註解,這樣會選擇加了 Primary 註解的 Bean 來注入,而不會拋異常:

@Component
@Primary
public class BeanB1 implements IBeanB {
    //
}

這樣 Spring 就能夠知道到底應該注入哪個 Bean 了。

情況3: No Bean Named […] is defined

NSBDE 還可能在從 Spring 上下文中透過名字獲取一個 Bean 時丟擲。

@Component
public class BeanA implements InitializingBean {
    @Autowired
    private ApplicationContext context;
    @Override
    public void afterPropertiesSet() {
        context.getBean("someBeanName");
    }
}

在這種情況中,如果找不到指定名字 Bean 的 Definition,就會丟擲如下異常:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'someBeanName' is defined

情況4: 代理 Beans

Spring 透過 AOP 代理 實現了許多高階功能,比如:

透過 @Transactional完成 事務管理
透過 @Cacheable實現快取
透過 @Async和 @Scheduled實現任務排程和非同步執行
Spring 有兩種方式實現代理:

利用 動態代理機制 ,在執行時為實現了某些介面的類動態建立一個實現了同樣介面的代理物件。
使用 CGLIB,CGLIB 可以在執行期擴充套件Java類與實現Java介面,也就是說當一個類沒有實現介面時,必須用 CGLIB 生成代理物件。
所以,當 Spring 上下文中的一個實現了某個介面的 Bean 透過JDK 動態代理機制被代理時,代理類並不是繼承了目標類,而是實現同樣的介面。

也正因為如此,如果一個 Bean 透過介面注入時,可以成功被注入。但如果是透過真正的類注入,那麼 Spring 將無法找到匹配這個類的 Definition——因為代理類並沒有繼承這個類。

以 Spring 中比較常見的事務管理為例,假設 ServiceA 中要注入 ServiceB,兩個 Service 均標註了 @Transactional註解來進行事務管理,那麼下面的注入方式是不會正常 work 的。

@Service
@Transactional
public class ServiceA implements IServiceA{
    @Autowired
    private ServiceB serviceB;
    ...
    }
 
@Service
@Transactional
public class ServiceB implements IServiceB{
}

解決辦法就是透過介面來進行注入:

@Service
@Transactional
public class ServiceA implements IServiceA{
    @Autowired
    private IServiceB serviceB;
    }
 
@Service
@Transactional
public class ServiceB implements IServiceB{
}

文末福利

我在這裡分享一個,有很多幹貨,包含jvm,netty,spring,執行緒,spring cloud等詳細講解,也有詳細的學習規劃圖,面試題整理等,我感覺在面試這塊講的非常清楚:獲取面試資料只需:點選這裡領取!!! 暗號:CSDN在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

本文章已修改原文用詞符合繁體字使用者習慣使其容易閱讀

版權宣告:此處為CSDN博主「雪碧沒有氣了」的原創文章,依據CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。

原文連結:https://blog.csdn.net/weixin_50917449/article/details/109607438