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