Beanのライフサイクルや@PostConstructは理解していても、ライブラリのソースでBeanNameAwareやApplicationContextAwareのような「Aware」という名前のインターフェースを見かけて、何のためにあるのか気になったことはありませんか。今回は4つのAwareが何を注入するのか、使っていい場面とコンストラクタインジェクションに置き換えるべき場面を整理します。

Awareは「Springの内部情報を受け取るsetter」

Awareインターフェースはどれも setXxx(Xxx xxx) という1メソッドだけを持ち、実装するとSpringコンテナがそのsetterを自動的に呼び出して情報を渡してくれます。呼ばれるのは依存性注入の後、@PostConstructより前です。

BeanNameAware - 自分のBean名を教えてもらう

setBeanName(String name) で、コンテナ内での自分のBean名を受け取れます。Bean名の決まり方自体は別記事の範囲ですが、実行時にBean自身がその名前を知るための仕組みです。

public class LoggingAwareBean implements BeanNameAware {
    private String beanName;

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

業務ロジックで必要になることはほぼなく、ライブラリ側の実装くらいでしか使われません。

BeanFactoryAware - BeanFactory自体を受け取る

setBeanFactory(BeanFactory beanFactory) で、自分を生成したBeanFactoryそのものを受け取ります。

public class RegistryBean implements BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
}

スコープの切り替えやプロキシ生成といった低レベルな処理向けで、普段のビジネスロジックで出番はありません。

ApplicationContextAware - コンテナ全体を受け取る

setApplicationContext(ApplicationContext context) で、コンテナ全体を受け取ります。4つの中でもっとも見かける頻度が高いインターフェースです。

public class ReportService implements ApplicationContextAware {
    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }

    public void generate() {
        context.getBean(MailSender.class).send("done");
    }
}

getBean() で好きなBeanを後から取り出せるので一見便利ですが、この「何でもできる」便利さこそが後述する問題の原因になります。

EnvironmentAware - プロパティとプロファイルを受け取る

setEnvironment(Environment environment) で Environment を受け取ります。

public class FeatureToggle implements EnvironmentAware {
    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public boolean isBetaEnabled() {
        return environment.getProperty("feature.beta.enabled", Boolean.class, false);
    }
}

多くの場合これは @Value の直接DIで十分で、EnvironmentAwareが必要になるのはBean定義が解決される前に設定値を見たいという特殊なケースに限られます。

Awareを使っていい場面

Aware系はどれも業務ロジックのためではなく、Springコンテナと協調するための配線コードです。BeanPostProcessorやFactoryBeanといった拡張ポイントを自作していてコンテナ内部の情報が必要な場合などがそれに当たります。逆に「業務ロジックを書いている」だけならAwareを使う理由はほぼありません。

業務ロジックで使うとつらくなる理由

ApplicationContextAwareを業務ロジックのクラスに実装すると、単体テストでモックのApplicationContextが必要になりテストが重くなります。必要な依存がコンストラクタ引数ではなくsetterの中に隠れ、クラスを見ただけでは依存関係が分かりません。getBean()を多用すると、DIコンテナに解決させるべき依存を自分で手動解決するサービスロケーターパターンに陥りがちで、これはDIの基本的な考え方とは逆方向の設計です。

コンストラクタインジェクションに置き換える

必要な依存がはっきりしているなら、普通にコンストラクタインジェクションしましょう。

@Service
public class ReportService {
    private final MailSender mailSender;

    public ReportService(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void generate() {
        mailSender.send("done");
    }
}

イベント発行だけが目的ならApplicationEventPublisherを、設定値の参照だけなら@Valueや@ConfigurationPropertiesをコンストラクタインジェクションすれば十分です。依存が明示されるのでテストでは普通にnewすればよく、Springの起動も不要です。

もっと狭いAwareを選ぶという手も

どうしてもAwareが必要でも、ApplicationContextAwareより機能を絞ったAwareがあります。イベント発行だけならApplicationEventPublisherAware、リソース読み込みだけならResourceLoaderAwareです。最初に検討すべきは「コンストラクタインジェクションで事足りないか」です。

まとめ

Aware系インターフェースは、Springコンテナが持つ情報をBeanに渡すための仕組みで、@Componentの基本的な仕組みの上に乗る、コンテナとの配線のためのAPIです。ライブラリのコードで見かけたら大抵正当な用途ですが、自分の業務ロジックで書きたくなったら、コンストラクタインジェクションや狭いAwareへの置き換えをまず検討しましょう。