【Java・Spring Boot】別パッケージにて、同じクラス名でDIしたいとき
Spring Bootで新しくControllerクラスを作ろうと思ったら、
サーバが起動しないエラーが起きたので、その解決方法を載せます。
少し長いので、見出しを頼りにしていただける大変嬉しいです。
別パッケージで、同じクラス名を作成し、起動したらエラーが起きた
見出しの通りで、新しくクラスを作るとき、既存のクラス名と同じクラス名を
別パッケージに作ろうとしたらエラーが起きました。
クラス構成、環境しては以下の通りになります。
Spring Bootの環境について
環境は以下
- Java 1.8
- STS 3.9.2
- Spring Bootバージョン 1.5.10
※WindowsでもMacでも同じ解決方法なので、使用OSは省略
既存のクラス
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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 org.springframework.web.bind.annotation.RestController;
import com.example.service.UserInfoService;
@Controller
@RequestMapping("/")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@RequestMapping(method=RequestMethod.GET)
public String index(Model model) {
userInfoService.getUserInfo();
return "main/index";
}
}
新しく作ったクラス
package com.example.controller.userAdmin;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/userAdmin")
public class UserInfoController {
@RequestMapping(method=RequestMethod.GET)
public String index(Model model) {
return "admin/index";
}
}
プロジェクトのパッケージ構成
起動しようとしたら以下のエラー
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.example.demo.SpringTest2Application]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userInfoController' for bean class [com.example.controller.userAdmin.UserInfoController] conflicts with existing, non-compatible bean definition of same name and class [com.example.controller.UserInfoController]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:181) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:308) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:272) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:92) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.10.RELEASE.jar:1.5.10.RELEASE]
at com.example.demo.SpringTest2Application.main(SpringTest2Application.java:15) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-1.5.10.RELEASE.jar:1.5.10.RELEASE]
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userInfoController' for bean class [com.example.controller.userAdmin.UserInfoController] conflicts with existing, non-compatible bean definition of same name and class [com.example.controller.UserInfoController]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:345) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:283) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:135) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:287) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:198) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167) ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
... 18 common frames omitted
上記のエラーで調べたら以下のサイトがヒットしました。
どうやら、別パッケージでも、同じクラス名を使った場合、
DIするときに、クラス名のみでBean名をつけようとして、
同じBean名だから重複してしまい、結局紐づけられずにエラーが起きるみたいです。
原則同じBean名は使えないのがSpring Bootの仕様です。
そして上記サイトで解決方法が2種類あることを知りました。
1.ControllerのアノテーションにBean名をつける
今回はControllerでしたが、他にもcomponent、Serviceもできる模様
Bean名を固定でつけてあげることにより、Springがその指定した名前でBeanを作ってくれる。
@Controller("userAdminController")
public class UserInfoController
@Controller("userInfoController")
public class UserInfoController
それぞれのクラスにBean名を指定して、起動してみました。
起動してくれました。
ですが、これだと、一つ一つBean名を手作業で書かないといけないので
だいぶ手間です。
なので、そういう時はもう一つの方法を使うとすごく楽です。
2.ComponentScanのNameGenaratorを使い、作るBean名をFQCNにする
何を言っているかわかりやすくするなら・・・
「パッケージも含めたクラス名なら、確実にかぶる事ないから、その文字列をBean名にしようぜ!」
という事です。
つまりはFQCN(完全修飾クラス名)を使おうぜという事です。
NameGenaratorとは
Springが用意してくれている、Bean名を作成してくれる仕組み。というか設定
使うクラスは「AnnotationBeanNameGenerator」を継承したクラスです。
NameGenaratorを設定することにより、Componentがスキャンされるたびに
オーバーライドしたメソッドを呼び出し、処理が走るみたいです。
戻り値は文字列で、それがそのままそのクラスのBean名になります。
引数は、Componentが設定されているクラス自身です。
つまりはこの機構を使って、引数で受け取ったクラスを使い、
FQCNをBean名にするという仕組みができるわけです。
考えた方、本当に天才かと思いました。
FQCNはかぶることもないので、必ず一意のBean名をつけることができます。
作ったクラスは以下です。
FQCNを返してくれるメソッドを持ったクラス
package com.example.demo;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
public class FQCNGenerator extends AnnotationBeanNameGenerator {
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
return definition.getBeanClassName();
}
}
その後、Applicationクラスに、ComponentScanを設定
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication(scanBasePackages={"com.example"})
@EntityScan("com.example.entity")
@EnableJpaRepositories("com.example.repository")
@ComponentScan(nameGenerator = FQCNGenerator.class)
public class SpringTest2Application {
public static void main(String[] args) {
SpringApplication.run(SpringTest2Application.class, args);
}
}
そして起動
無事起動することができました。
まとめ
やっぱり大体はアノテーションで解決することができるんですね。
別パッケージなのにおかしいなぁ・・・って色々悩んでましたが、
Qiitaのサイトによって助けられました!
本当にありがとうございます!!
そして、FQCNはどういう状況でも役に立つことが理解できました。
個人的には手で名前を決める方が好きだったりしますが、
手間などを考えるとやはりFQCN一択になってしまうのかなと。
「意識しなくても良いところは意識しなくても良いように作る。」
というのも、製造する上で大事な考えだと思いました。
ちなみにセキュリティ面も含めて、何か一つWebアプリケーション作ってみようかなと思いました。
今回は以上!
ディスカッション
コメント一覧
まだ、コメントがありません