Spring管理外のクラスでSpringのBeanを使う
Spring管理外のクラスでSpringのBeanを使いたい場面がある。
そもそも自分でnewすればいいだけでは?という場面もあるけど、@ProfileでどんなBeanが来るのか実行時までわからない、とか複雑な初期化あるとかいう前提。
例えば、
- ORMが生成するクラス内でSpringのBean参照したい
- throw時に毎回インスタンスを生成する例外クラスで、Springの管理してるメッセージを取り出したい
どうすればよいのか。方法は2つある。
- Springの@Configurableを使う方法(aspectjで実現する方法)
- 使いたいフィールドをstaticにして強引にSpringのBeanをつっこむ方法
Springの@Configurable(aspectjで実現する方法)
外部ライブラリで勝手にnewされるクラスでSpringのBeanを使いたかったらこの方法。今回はJPAで取得してきたドメインクラス内でSpringの管理Beanを使う例。
1. DBにサイトのリンクをブックマークとして登録する
2. JPAでDBからブックマークを取得する
3. JPAで取得したクラス内でSpring管理BeanのRestTemplateをつかってリンクのサイトがまだ存在するか確認する
Springの管理Beanを使うPOJOのクラス
- SpringのBeanを使う側に@Configurableをつける。そうするとaspectj(というかspring-aspects.jar?)でコンパイル時にSpring管理Beanをインジェクトする処理が追加される。(黒魔術感がはんぱじゃない)
- インジェクトしたいフィールドに@Autowired追加する
@Entity
@Configurable
public class BookMark {
@Id
@GeneratedValue
private Long id;
private String link;
BookMark() {
}
public BookMark(String link) {
this.link = link;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
@Transient
@Autowired
private RestTemplate restTemplate;
// アクセスして200が返ってこなければfalse
public boolean isExist() {
ResponseEntity<String> result;
try {
result = restTemplate.getForEntity(link, String.class);
} catch (ResourceAccessException e) {
return false;
}
if (result.getStatusCode() == HttpStatus.OK) {
return true;
} else {
return false;
}
}
}
Configurationクラス
- @EnableSpringConfiguredを追加する
Springの管理BeanにRestTemplateを追加するのも忘れずに。
@EnableSpringConfigured
@SpringBootApplication
public class AspectApplication {
public static void main(String[] args) {
SpringApplication.run(AspectApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Autowired
BookMarkRepository bookMarkRepository;
//Spring Data JPAを使って確認
@Bean
public CommandLineRunner save() {
return args -> {
System.out.println("-----------save-----------");
bookMarkRepository.save(new BookMark("http://google.com"));
bookMarkRepository.save(new BookMark("http://google.come"));
System.out.println("--------execute--------");
bookMarkRepository.findAll().forEach(bookMark -> {
System.out.println(bookMark.getLink() + " isExist: " + bookMark.isExist());
});
};
}
}
pom.xml
aspectJ使うのでコンパイル時に指定が必要
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
サンプル
サンプル書いた。
kimullaa/configurablegithub.com
使いたいフィールドをstaticにして強引にSpringのBeanをつっこむ方法
自分でnewしたコードでSpringのBean使いたい、って例外クラスくらいしか思いつかないので例外を例にして説明。
SpringのBeanを使う側のクラスでstaticフィールド定義しとく。
public class BusinessException extends RuntimeException {
private static MessageSource ms;
private ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(ms.getMessage(errorCode.code(), null, Locale.getDefault()));
this.errorCode = errorCode;
}
public static void setMessageSource(MessageSource ms){
BusinessException.ms = ms;
}
}
強引にSpringのBeanをつっこむ。
Bean解決が終わったあと(@PostConstruct)にインジェクトする。
@Component
public class MessageSourceInjector {
@Autowired
private MessageSource ms;
@PostConstruct
public void inject(){
BusinessException.setMessageSource(ms);
}
}
注意点
- 同一インスタンスを共有するので、スレッドセーフなクラス以外入れないこと
- Springの管理Beanのライフサイクルから外れるので、Singletonより短いスコープを入れないこと(リクエストスコープのBeanを生成の度に全インスタンスで共有するstaticフィールドなんかにつっこむのは危ない)
サンプル
サンプル書いた。