SpringDIで生成されるオブジェクトを扱う際に注意すること

https://qiita.com/gengen0719/items/65cab20943bf4cce61ca

こちらの記事の続きです。
SpringDIを使って生成されるBeanの取り扱いで注意するべきことを書きました。

# Beanは通常シングルトンである

例えば以下のような実装をします。

```Java
@Controller
public class DemoController {

    private final CaluculationService incrementalService;

    public DemoController(IncrementalService incrementalService) {
        this.incrementalService = incrementalService;
    }

    @GetMapping("incremental")
    public String calc(Model model) {
        model.addAttribute("result", incrementalService.inc());        
        return "calculation";
    }
}
```

```Java
@Service
public class IncrementalService {   
    
    private int result = 0;

    public String inc(){
        result = result + 1;
        return "結果:"+ result;
    }
}

```

http://localhost:8080/incremental にアクセスすると以下のような結果になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1126770/23024692-a032-0830-472e-a882a6e8321f.png)

この画面をリロードすると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1126770/1a49936b-f029-2bb1-6200-016e2c31b30c.png)

IncrementalServiceのインスタンスは同一のものであり、フィールド変数の値が加算されていることが見てとれます。

# Servletはマルチスレッドでリクエストを処理する

ここで注意しなければいけないのは、Servletはリクエストをマルチスレッドで処理するということです。

例えばIncrementalServiceの実装が以下のようなものだった場合を考えます。
```Java
@Service
public class IncrementalService {
    
    private int result = 0;

    public String inc() throws InterruptedException{
        result = result + 1;
        // データベースアクセス等、時間のかかる処理
        Thread.sleep(10000);//代わりにThread.sleep(10000)で10秒処理を遅延させる
        return "結果:"+ result;
    }
}
```
こういった実装をしてサーバーを再起動し複数のブラウザから同時にアクセスをしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1126770/ba069ea9-895c-20d4-646d-daa818089f89.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1126770/99341f79-85f6-21e7-c921-92944c275501.png)

結果は両方のブラウザに結果:2と表示されました。
最初のアクセスを処理するスレッドがThread.sleepで処理を待っている間に、次のアクセスを処理するスレッドがresultの値を2に書き換えため、両方の結果が2になったのです。

例えばこの機能がページを何人の人が開いたのかを表示するためのものであれば、別にこれでも構わないかもしれません。しかし例えば店舗や病院のシステムで受付番号を表示するための機能の場合は番号に重複が発生してしまうと困ることになるでしょう。

例えばこういう実装にしたり、
```Java
public class IncrementalService {
    
    private int result = 0;

    public synchronized String inc() throws InterruptedException{
        result = result + 1;
        // データベースアクセス等、時間のかかる処理
        Thread.sleep(10000);//代わりにThread.sleep(10000)で10秒処理を遅延させる
        return "結果:"+ result;
    }
}
```
でもこれだと最初のスレッドの処理が全て終わるまで次のスレッドが待つことになって遅いのでこうしたりする必要があるでしょう。

```Java
public class IncrementalService {
    
    private int count = 0;

    public String inc() throws InterruptedException{
        int result = getResult();
        // データベースアクセス等、時間のかかる処理
        Thread.sleep(10000);//代わりにThread.sleep(10000)で10秒処理を遅延させる
        return "結果:"+ result;
    }

    private synchronized int getResult(){
        return ++count;
    }
}
```

それぞれの仕組みがどのように動いているのかを理解し、作りたい機能の要件に合わせて適切に実装することが大切です。

# Beanとして管理しない方が良いオブジェクト
## スレッドごとに異なる状態を保持するためのオブジェクト
スレッドごとに異なる状態を保持するためのオブジェクトは当然ながらシングルトンとなるBeanとして管理するのではなく、それぞれのスレッドの中でインスタンスを生成した方が良いです。
具体例としてはデータベースからロードした値を保持するEntityオブジェクトや、画面のユーザーの入力内容を保持するParameterオブジェクト等が該当します。

## 外部リソースのオブジェクト
データベースへのConnectionやFileに書き込みを行うWriterなどもそれぞれのスレッドでインスタンスを持つ方が良いでしょう。
ConnectionやWriterをBeanとして管理してしまうと、トランザクションの管理ができなくなったり、複数のスレッドからの書き込みが混ざってしまったりするでしょう。
直接そういったオブジェクトをBeanとして管理するのではなく、ConnectionであればDataSourceなど外部リソースのオブジェクトを管理するオブジェクトをBeanとして利用する必要があります。

おわり。