読者です 読者をやめる 読者になる 読者になる

SIerだけど技術やりたいブログ

5年目のSIerのブログです

Spring管理外のクラスでSpringのBeanを使う

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>

サンプル

サンプル書いた。

github.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フィールドなんかにつっこむのは危ない)

サンプル

サンプル書いた。

github.com