山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

Spring Framework で管理されるオブジェクトはデフォルトでシングルトン(singleton)

タイトルに書いたとおりだが、Spring を使用する上でこれを常に頭に入れておかないと、とんでもないバグを仕込んでしまう。

実験してみる

Spring のバージョンは 4.3.7 を使用しているが、他のバージョンでも、これに関しての仕様は同じ。

フォルダ構成。

f:id:yyama1556:20170409130009p:plain

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>ProjectX</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>
		<!-- Generic properties -->
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<maven.compiler.target>${java.version}</maven.compiler.target>
		<maven.compiler.source>${java.version}</maven.compiler.source>

		<!-- Spring -->
		<spring-framework.version>4.3.7.RELEASE</spring-framework.version>
	</properties>
	
	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
	</dependencies>	
</project>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd">
	<context:component-scan base-package="org.yyama.bean" />
</beans>

ClassA は空っぽ。 @Component だけ付けている。

package org.yyama.bean;

import org.springframework.stereotype.Component;

@Component
public class ClassA {

}


Main クラス。

package org.yyama.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.yyama.bean.ClassA;

public class Main {
	public static void main(String... args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		ClassA class1 = context.getBean(ClassA.class);
		ClassA class2 = context.getBean(ClassA.class);

		if (class1 == class2) {
			System.out.println("2つのインスタンスは同じものです。");
		} else {
			System.out.println("2つのインスタンスは異なるものです。");
		}
		context.close();
	}
}

ClassA のインスタンスを 2 回取得し、それが同じものなのか別のものなのか判定している。

実行するとこうなる。

2つのインスタンスは同じものです。

このように、Spring のコンテナは同じインスタンスを複数回渡してくる。

例えば、インスタンス変数にユーザーごとの状態を保持しておいたりすると、あるユーザへの変更が全ユーザに反映されてしまう。単体テストでは発見しにくいバグを作り込んでしまうので注意が必要だ。

呼び出すたびに別のインスタンスが欲しい場合

ClassA に Scope アノテーションを付与し、パラメータに "prototype" を指定する。

package org.yyama.bean;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class ClassA {

}

これで main メソッドを実行すると、結果はこうなった。

2つのインスタンスは異なるものです。

"prototype"のほかに"singleton"があり、これがデフォルトで適用される。Scope アノテーションを省略するとシングルトンになる。

Spring の web アプリケーションでは、"request"や"session"も使用できる。"global-session"というのもあるらしいが、今のところ興味ない。

まとめ

基本的なことなので、これを知らずに開発している人がいたら教えてあげましょう。これ大事です。

↓↓↓ Spring Framework 関連の記事をまとめました。
yyama1556.hateblo.jp

「プロになるためのWeb技術入門」 ――なぜ、あなたはWebシステムを開発できないのか

「プロになるためのWeb技術入門」 ――なぜ、あなたはWebシステムを開発できないのか