web-dev-qa-db-ja.com

NoSuchBeanDefinitionExceptionとは何ですか?どうすれば修正できますか?

SpringのNoSuchBeanDefinitionException例外について、以下を説明してください:

  • どういう意味ですか?
  • どのような条件下でスローされますか?
  • どうすれば防ぐことができますか?

この投稿は、Springを使用するアプリケーションでのNoSuchBeanDefinitionExceptionの発生に関する包括的なQ&Aになるように設計されています。

50

NoSuchBeanDefinitionExceptionのjavadoc の説明

定義が見つからないBeanインスタンスをBeanFactoryに要求するとスローされる例外。これは、存在しないBean、一意でないBean、またはBean定義が関連付けられていない手動で登録されたシングルトンインスタンスを指している場合があります。

BeanFactory は、基本的に SpringのInversion of Controlコンテナ を表す抽象概念です。 Beanを内部および外部でアプリケーションに公開します。これらのBeanを検出または取得できない場合、NoSuchBeanDefinitionExceptionをスローします。

以下に、BeanFactory(または関連するクラス)がBeanを見つけられない簡単な理由と、それを確実に行う方法を示します。


Beanが存在せず、登録されていません

以下の例では

@Configuration
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        ctx.getBean(Foo.class);
    }
}

class Foo {}   

@Beanメソッド、@Componentスキャン、XML定義、またはその他の方法で、Foo型のBean定義を登録していません。したがって、BeanFactoryによって管理されるAnnotationConfigApplicationContextには、getBean(Foo.class)によって要求されたBeanを取得する場所が示されていません。上記のスニペットはスローします

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
    No qualifying bean of type [com.example.Foo] is defined

同様に、@Autowired依存関係を満たそうとして例外がスローされた可能性があります。例えば、

@Configuration
@ComponentScan
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
    }
}

@Component
class Foo { @Autowired Bar bar; }
class Bar { }

ここでは、Fooから@ComponentScanまでのBean定義が登録されています。しかし、SpringはBarを何も知りません。したがって、bar BeanインスタンスのFooフィールドを自動配線しようとして、対応するBeanを見つけることができません。スロー( UnsatisfiedDependencyException 内にネスト)

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

Bean定義を登録するには、複数の方法があります。

  • @Beanクラスの@ConfigurationメソッドまたはXML構成の<bean>メソッド
  • @Component(およびそのメタ注釈、たとえば@Repository)からXMLの@ComponentScanまたは<context:component-scan ... />まで
  • GenericApplicationContext#registerBeanDefinition を使用して手動で
  • BeanDefinitionRegistryPostProcessorを使用して手動で

...もっと。

期待するBeanが適切に登録されていることを確認してください。

一般的なエラーは、Beanを複数回登録することです。同じタイプの上記のオプションを組み合わせます。たとえば、私は持っているかもしれません

@Component
public class Foo {}

およびXML設定

<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />

このような構成では、タイプFooの2つのBeanを登録します。1つはfooという名前で、もう1つはeg-different-nameという名前です。必要以上にBeanを誤って登録しないようにしてください。それが私たちを導く...

XMLと注釈ベースの構成の両方を使用している場合は、必ず一方を他方からインポートしてください。 XMLが提供する

<import resource=""/>

Javaは @ImportResource アノテーションを提供します。

単一の一致するBeanが必要ですが、2つ(またはそれ以上)が見つかりました

同じタイプ(またはインターフェース)に対して複数のBeanが必要な場合があります。たとえば、アプリケーションでは、MySQLインスタンスとOracleデータベースの2つのデータベースを使用できます。このような場合、それぞれへの接続を管理する2つのDataSource Beanがあります。 (簡略化された)例では、次の

@Configuration
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(ctx.getBean(DataSource.class));
    }
    @Bean(name = "mysql")
    public DataSource mysql() { return new MySQL(); }
    @Bean(name = "Oracle")
    public DataSource Oracle() { return new Oracle(); }
}
interface DataSource{}
class MySQL implements DataSource {}
class Oracle implements DataSource {}

投げる

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
    No qualifying bean of type [com.example.DataSource] is defined:
        expected single matching bean but found 2: Oracle,mysql

@Beanメソッドを介して登録された両方のBeanが BeanFactory#getBean(Class) の要件を満たしているためです。両方ともDataSourceを実装します。この例では、Springには2つを区別または優先順位付けするメカニズムがありません。しかし、そのようなメカニズムは存在します。

documentation および this post で説明されているように、 @Primary (およびXMLでの同等のもの)を使用できます。この変更により

@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); } 

前のスニペットは例外をスローせず、代わりにmysql Beanを返します。

documentation で説明されているように、@Qualifier(およびそれに相当するXML)を使用して、Bean選択プロセスをより詳細に制御することもできます。 @Autowiredは主にタイプごとの自動配線に使用されますが、@Qualifierを使用すると名前で自動配線できます。例えば、

@Bean(name = "mysql")
@Qualifier(value = "main")
public DataSource mysql() { return new MySQL(); }

今として注入することができます

@Qualifier("main") // or @Qualifier("mysql"), to use the bean name
private DataSource dataSource;

問題なく。 @Resource もオプションです。

間違ったBean名を使用する

Beanを登録する方法が複数あるように、Beanに名前を付ける方法も複数あります。

@Bean has name

このBeanの名前、または複数の場合、このBeanのエイリアス。指定しない場合、Beanの名前は注釈付きメソッドの名前になります。指定した場合、メソッド名は無視されます。

<bean>には、 Beanの一意の識別子を表すid属性がありますおよびnameは、(XML)idで不正な1つ以上のエイリアスを作成するために使用できます。

@Component およびそのメタ注釈には value

この値は、自動検出されたコンポーネントの場合にSpring Beanに変換される論理コンポーネント名の提案を示している場合があります。

それが指定されていない場合、注釈付きの型(通常は小文字のラクダ型の型名)のBean名が自動的に生成されます。

@Qualifierは、前述のように、Beanにさらにエイリアスを追加できます。

名前で自動配線する場合は、正しい名前を使用してください。


より高度なケース

プロフィール

Bean定義プロファイル Beanを条件付きで登録できます。 @Profile 、具体的には、

1つ以上の指定されたプロファイルがアクティブな場合、コンポーネントは登録に適格であることを示します。

プロファイルは、 ConfigurableEnvironment.setActiveProfiles(Java.lang.String...) を介してプログラムでアクティブにできる、またはspring.profiles.activeプロパティをJVMシステムプロパティ、環境変数、またはweb.xmlのサーブレットコンテキストパラメータとして設定することで宣言的にアクティブにできる名前付き論理グループですWebアプリケーション。プロファイルは、統合テストで @ActiveProfiles アノテーションを使用して宣言的にアクティブ化することもできます。

spring.profiles.activeプロパティが設定されていないこの例を検討してください。

@Configuration
@ComponentScan
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(Arrays.toString(ctx.getEnvironment().getActiveProfiles()));
        System.out.println(ctx.getBean(Foo.class));
    }
}

@Profile(value = "StackOverflow")
@Component
class Foo {
}

これにより、アクティブなプロファイルは表示されず、NoSuchBeanDefinitionException Beanに対してFooがスローされます。 StackOverflowプロファイルがアクティブではなかったため、Beanは登録されませんでした。

代わりに、適切なプロファイルを登録しながらApplicationContextを初期化すると

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();

beanは登録されており、返されるか、注入されます。

AOPプロキシ

Springは AOPプロキシ を使用して高度な動作を実装します。以下に例を示します。

これを実現するために、Springには2つのオプションがあります。

  1. JDKの Proxy クラスを使用して、実行時にBeanのインターフェースのみを実装する動的クラスのインスタンスを作成し、すべてのメソッド呼び出しを実際のBeanインスタンスに委任します。
  2. CGLIB プロキシを使用して、ターゲットBeanのインターフェースと具象型の両方を実装し、すべてのメソッド呼び出しを実際のBeanインスタンスに委任する実行時に動的クラスのインスタンスを作成します。

このJDKプロキシの例を使用します(proxyTargetClass@EnableAsyncのデフォルトのfalseによって達成されます)

@Configuration
@EnableAsync
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(ctx.getBean(HttpClientImpl.class).getClass());
    }
}

interface HttpClient {
    void doGetAsync();
}

@Component
class HttpClientImpl implements HttpClient {
    @Async
    public void doGetAsync() {
        System.out.println(Thread.currentThread());
    }
}

ここで、SpringはHttpClientImpl型のBeanを見つけようとします。この型には、@Componentという注釈が明確に付けられているため、検出されるはずです。ただし、代わりに、例外が発生します

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No qualifying bean of type [com.example.HttpClientImpl] is defined

SpringはHttpClientImpl Beanをラップし、Proxyのみを実装するHttpClientオブジェクトを介して公開しました。だからあなたはそれを取得することができます

ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;

program to interfaces が常に推奨されます。できない場合は、CGLIBプロキシを使用するようSpringに指示できます。たとえば、 @EnableAsync を使用すると、 proxyTargetClasstrueに設定できます。同様の注釈(EnableTransactionManagementなど)には同様の属性があります。 XMLにも同等の構成オプションがあります。

ApplicationContext階層-Spring MVC

Springでは、 ConfigurableApplicationContext#setParent(ApplicationContext) を使用して、他のApplicationContextインスタンスを親としてApplicationContextインスタンスを構築できます。子コンテキストは親コンテキストのBeanにアクセスできますが、その逆は当てはまりません。 この投稿 は、特にSpring MVCでこれがいつ役立つかについて詳しく説明します。

典型的なSpring MVCアプリケーションでは、2つのコンテキストを定義します。1つはアプリケーション全体(ルート)用、もう1つは DispatcherServlet (ルーティング、ハンドラーメソッド、コントローラー)用です。詳細はこちらから入手できます。

また、公式ドキュメント here でも非常によく説明されています。

Spring MVC設定の一般的なエラーは、XMLの@EnableWebMvc注釈付き@Configurationクラスまたは<mvc:annotation-driven />クラスを使用して、ルートコンテキストでWebMVC設定を宣言することですが、 @Controller サーブレットコンテキスト。 ルートコンテキストはサーブレットコンテキストに到達してBeanを見つけることができないため、ハンドラーは登録されず、すべてのリクエストは404で失敗します。NoSuchBeanDefinitionExceptionは表示されませんが、効果は同じです。

Beanが適切なコンテキストで登録されていることを確認してください。 WebMVCに登録されたBeanがそれらを見つけることができる(HandlerMappingHandlerAdapterViewResolverExceptionResolverなど)。最善の解決策は、Beanを適切に分離することです。 DispatcherServletはリクエストのルーティングと処理を担当するため、関連するすべてのBeanはそのコンテキストに入る必要があります。ルートコンテキストをロードするContextLoaderListenerは、アプリケーション、サービス、リポジトリなどの残りのアプリケーションが必要とするBeanを初期化する必要があります。

配列、コレクション、およびマップ

一部の既知のタイプのBeanは、Springによって特別な方法で処理されます。たとえば、MovieCatalogの配列をフィールドに挿入しようとした場合

@Autowired
private MovieCatalog[] movieCatalogs;

Springは、MovieCatalog型のすべてのBeanを検出し、それらを配列にラップして、その配列を挿入します。これは @Autowiredを議論する春のドキュメント で説明されています。同様の動作がSetList、およびCollectionインジェクションターゲットに適用されます。

Mapインジェクションターゲットの場合、キータイプがStringの場合、Springもこのように動作します。たとえば、あなたが持っている場合

@Autowired
private Map<String, MovieCatalog> movies;

Springは、MovieCatalog型のすべてのBeanを検出し、それらを値としてMapに追加します。対応するキーはBean名になります。

前に説明したように、要求されたタイプのBeanが利用できない場合、SpringはNoSuchBeanDefinitionExceptionをスローします。ただし、次のようなコレクションタイプのBeanを宣言したい場合もあります。

@Bean
public List<Foo> fooList() {
    return Arrays.asList(new Foo());
}

そしてそれらを注入する

@Autowired
private List<Foo> foos;

この例では、コンテキストにNoSuchBeanDefinitionException Beanがないため、SpringはFooで失敗します。ただし、Foo Beanは必要ありませんでした。List<Foo> Beanは必要でした。 Spring 4.3以前では、@Resourceを使用する必要がありました

それ自体がコレクション/マップまたは配列タイプとして定義されているBeanの場合、@Resourceは、特定のコレクションまたは配列Beanを一意の名前で参照する優れたソリューションです。そうは言っても、4.3の時点で、コレクション/マップおよび配列型は、要素型情報が@Autowired戻り型シグネチャで保持されている限り、Springの@Bean型一致アルゴリズムによっても一致できます。コレクションの継承階層。この場合、前の段落で概説したように、修飾子の値を使用して同じ型のコレクションから選択できます。

これは、コンストラクター、セッター、およびフィールド注入で機能します。

@Resource
private List<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}

ただし、@Beanメソッドの場合は失敗します。

@Bean
public Bar other(List<Foo> foos) {
    new Bar(foos);
}

ここでは、Springは@Resourceメソッドであるため、メソッドに注釈を付ける@Autowiredまたは@Beanを無視します。したがって、ドキュメントで説明されている動作を適用できません。ただし、Spring Expression Language(SpEL)を使用して、名前でBeanを参照できます。上記の例では、使用できます

@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
    new Bar(foos);
}

fooListという名前のBeanを参照して挿入します。

84