本記事は、Spring Framework(非boot)のDIの動作確認を行った。
以下の4パターンの動作確認を行いました。
No. | context:component-scan にパッケージが含まれているか |
実装クラス1 | `実装クラス2 | 実行結果 |
---|---|---|---|---|
1 | ○ | ○ | x | DI可能 |
2 | ○ | x | ○ | DI可能 |
3 | ○ | ○ | ○ | 実行時エラーが発生 |
4 | ○ | x | x | 実行時エラーが発生 |
本記事タイトルの「インターフェースを実装した2つのクラスが2つとも@Component
を付与されていた場合どうなるのか」というのはパターン3になります。
各パターンで使用するJSP、インターフェース、context:component-scan
要素の内容は同じものを使用します。
home.jsp
各パターンの結果を表示する用の画面として以下を用意しています。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Hello world!</h1>
<P> ${str} </P>
</body>
</html>
servlet-context.xml(C:\workspace\sample\src\main\webapp\WEB-INF\spring\appServlet.servlet-context.xml
)を開き、context:component-scan
要素の内容を確認する。
下記を確認すると「com.my.app」プロジェクトがコンポーネントスキャンの対象になっていることが確認します。
<context:component-scan base-package="com.my.app" />
SampleServiceInterface.java
実装クラスの元ネタとなるインターフェースは以下のように定義しておきます。
package com.my.app.service;
public interface SampleServiceInterface {
public String execute();
}
HomeController.java
package com.my.app.ctrl;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.my.app.service.SampleServiceInterface;
@Controller
public class HomeController {
public SampleServiceInterface sampleServiceInterface;
@Autowired
public HomeController(SampleServiceInterface sampleServiceInterface){
this.sampleServiceInterface = sampleServiceInterface;
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
String result = sampleServiceInterface.execute();//DIして実装クラスを実行
model.addAttribute("str", result );
return "home";
}
}
パターン1では、SampleServiceA.java
に@Component
アノテーションを付与し、SampleServiceB.java
では付与しません。
SampleServiceA.java
このクラスに@Component
アノテーションを付与します。
package com.my.app.service.impl;
import org.springframework.stereotype.Component;
import com.my.app.service.SampleServiceInterface;
@Component
public class SampleServiceA implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceA";
}
}
SampleServiceB.java
このクラスには@Component
アノテーションを付与しません。
package com.my.app.service.impl;
import com.my.app.service.SampleServiceInterface;
public class SampleServiceB implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceB";
}
}
実行すると、画面に以下のように表示されました。SampleServiceA.java
がreturnした値を画面に表示できていることを確認できました。
パターン2では、SampleServiceA.java
には@Component
アノテーションを付与せず、SampleServiceB.java
に付与します。
SampleServiceA.java
このクラスには@Component
アノテーションを付与しません。
package com.my.app.service.impl;
import com.my.app.service.SampleServiceInterface;
public class SampleServiceA implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceA";
}
}
SampleServiceB.java
このクラスに@Component
アノテーションを付与します。
package com.my.app.service.impl;
import org.springframework.stereotype.Component;
import com.my.app.service.SampleServiceInterface;
@Component
public class SampleServiceB implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceB";
}
}
実行すると、画面に以下のように表示されました。SampleServiceB.java
がreturnした値を画面に表示できていることを確認できました。
パターン3では、SampleServiceA.java
とSampleServiceB.java
の両方に@Component
アノテーションを付与します。
SampleServiceA.java
@Component
アノテーションを付与します。
package com.my.app.service.impl;
import org.springframework.stereotype.Component;
import com.my.app.service.SampleServiceInterface;
@Component
public class SampleServiceA implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceA";
}
}
SampleServiceB.java
@Component
アノテーションを付与します。
package com.my.app.service.impl;
import org.springframework.stereotype.Component;
import com.my.app.service.SampleServiceInterface;
@Component
public class SampleServiceB implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceB";
}
}
実行時、以下のエラーがコンソールに表示され、起動することができませんでした。
【例外】
javax.servlet.ServletException: サーブレット appServlet のServlet.init()が例外を投げました
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
【原因1】UnsatisfiedDependencyException
が発生
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'homeController' defined in file [C:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\sample\WEB-INF\classes\com\my\app\ctrl\HomeController.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.my.app.service.SampleServiceInterface]: : No unique bean of type [com.my.app.service.SampleServiceInterface] is defined: expected single matching bean but found 2: [sampleServiceA, sampleServiceB]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.my.app.service.SampleServiceInterface] is defined: expected single matching bean but found 2: [sampleServiceA, sampleServiceB]
org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:730)
【原因2】NoSuchBeanDefinitionException
が発生
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.my.app.service.SampleServiceInterface] is defined: expected single matching bean but found 2: [sampleServiceA, sampleServiceB]
org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:800)
ログを見ると以下のように出力されています。
No unique bean of type [com.my.app.service.SampleServiceInterface] is defined: expected single matching bean but found 2: [sampleServiceA, sampleServiceB]
DeepL日本語に訳すると
com.my.app.service.SampleServiceInterface] タイプのユニークなビーンが定義されていません: 1つの一致するビーンを期待しましたが、2つ見つかりました: [sampleServiceA, sampleServiceB].
となりました。つまり、「インターフェースの中に実装クラスが2つ見つかったよ。実装クラスはユニークにしてね(1つにしてね。)」と解釈できます。
要するに、@Componentアノテーションが付与された実装クラスを1つだけにしてください。ということです。
パターン4では、SampleServiceA.java
とSampleServiceB.java
の両方に@Component
アノテーションを付与しません。
SampleServiceA.java
@Component
アノテーションを付与しません。
package com.my.app.service.impl;
import com.my.app.service.SampleServiceInterface;
public class SampleServiceA implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceA";
}
}
SampleServiceB.java
package com.my.app.service.impl;
import com.my.app.service.SampleServiceInterface;
public class SampleServiceB implements SampleServiceInterface{
@Override
public String execute() {
return "is a SampleServiceB";
}
}
実行時、以下のエラーがコンソールに表示され、起動することができませんでした。
【例外】
javax.servlet.ServletException: サーブレット appServlet のServlet.init()が例外を投げました
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
【原因1】UnsatisfiedDependencyException
が発生
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'homeController' defined in file [C:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\sample\WEB-INF\classes\com\my\app\ctrl\HomeController.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.my.app.service.SampleServiceInterface]: : No matching bean of type [com.my.app.service.SampleServiceInterface] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.my.app.service.SampleServiceInterface] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:730)
【原因2】NoSuchBeanDefinitionException
が発生
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.my.app.service.SampleServiceInterface] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:924)
ログを見ると以下のように出力されています。
No matching bean of type [com.my.app.service.SampleServiceInterface] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
DeepL日本語に訳すると
依存関係に [com.my.app.service.SampleServiceInterface] タイプの一致する bean が見つかりません: この依存関係の autowire 候補として修飾される bean が少なくとも 1 つ期待されます。依存関係のアノテーション。{}
となりました。つまり「@Componentアノテーションが付与された実装クラスが一つもなかったよ。」ということになります。