ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

よくあるDIの勘違い② - InjectしたBeanのフィールドに直接アクセスしてはいけない

Spring FrameworkでDIするBeanクラスや、Java EECDI Beanクラスにフィールドを定義して状態を持たせることがあります。

@RequestScope
public class ReqScope {

    public String request = "Request-1";

    public String getRequest() {
        return this.request;
    }

    public String setRequest(String request) {
        this.request = request;
    }

このコードでは、requestという変数名で文字列を保持できるBeanということです。

InjectしたBeanのフィールドへのアクセス方法

先程のクラスをインジェクトして、フィールドにアクセスしてみます。

@RestController
public class FieldTest {
    @Autowired
    private ReqScope reqScope;

    @GetMapping("/request")
    public String request(@RequestParam(value="request")String request) {
        reqScope.request= "Request-2";    ➊
        return reqScope.getRequest();     ➋
    }
}

➊ フィールドに直接アクセスして値を変更します。
➋ Getterメソッド経由でフィールドの値を取得します。

通常のJavaプログラムであれば、このプログラムは期待通りの動作をします。しかし、InjectしたBeanに関しては、このコードは期待通りの動作をしません。
➊でフィールドの値を変更しているにもかかわらず、➋のGetterメソッドで取得できる値は変更前の初期値になります。

フィールドに直接アクセスして値を変更しても、Beanのフィールドに変更結果は反映されません。
以下のようにフィールドの変更にSetterメソッドを使用すると、期待通りの動作になります。

        reqScope.setRequest("Request-2");
        return reqScope.getRequest();

InjectしたBeanのフィールドへの直接アクセスがダメな理由

一部の例外を除き、インジェクトしたBeanのインスタンスはProxyクラスになります。
フィールドに直接アクセスした場合、このProxyクラスのフィールドにアクセスしていて、実装したクラスのフィールドにアクセスしているわけではありません。
実装したクラスのメソッド経由でフィールドにアクセスした場合は、実装したクラスのフィールドへのアクセスになります。

このことから、InjectしたBeanのフィールドに直接アクセスしてはいけないことがわかります。
そもそも、クラスのフィールドは外部から直接アクセスできないスコープ(privateとかprotected)にして、メソッド経由でアクセスしましょう。

今回のケースに該当しないBeanのスコープ

Spring Frameworkprototypeスコープ、Java EEdependentスコープはProxyクラスが生成されず、実装クラスが直接Injectされるため、今回のようなことは起きません。 ただ、特定のBeanスコープに依存するフィールドのアクセス方法を実装することはよくないのです。やはり、Beanスコープにかかわらず、フィールドはメソッド経由でアクセスしましょう。