웹사이트 검색

스프링 빈 스코프


Spring Bean 범위를 사용하면 Bean 인스턴스 생성을 보다 세부적으로 제어할 수 있습니다. 때때로 우리는 bean 인스턴스를 싱글톤으로 생성하기를 원하지만 다른 경우에는 요청마다 또는 세션에서 한 번 생성되기를 원할 수 있습니다.

스프링 빈 스코프

Spring Bean 범위에는 5가지 유형이 있습니다.

  1. singleton - 스프링 컨테이너에 대해 스프링 빈 인스턴스 하나만 생성됩니다. 이것은 기본 스프링 빈 범위입니다. 이 범위를 사용하는 동안 Bean에 공유 인스턴스 변수가 없는지 확인하십시오. 그렇지 않으면 데이터 불일치 문제가 발생할 수 있습니다.
  2. prototype – 스프링 컨테이너에서 빈이 요청될 때마다 새 인스턴스가 생성됩니다.
  3. 요청 – 프로토타입 범위와 동일하지만 웹 애플리케이션에 사용하기 위한 것입니다. 각 HTTP 요청에 대해 빈의 새 인스턴스가 생성됩니다.
  4. 세션 – 컨테이너에 의해 각 HTTP 세션에 대해 새 빈이 생성됩니다.
  5. global-session – 포틀릿 애플리케이션을 위한 전역 세션 빈을 생성하는 데 사용됩니다.

Spring Bean 싱글톤 및 프로토타입 범위

Spring bean 싱글톤 및 프로토타입 범위는 독립형 스프링 앱에서 사용할 수 있습니다. @Scope 주석을 사용하여 이러한 범위를 쉽게 구성하는 방법을 살펴보겠습니다. 자바 빈 클래스가 있다고 가정해 보겠습니다.

package com.journaldev.spring;

public class MyBean {

	public MyBean() {
		System.out.println("MyBean instance created");
	}

}

스프링 컨테이너에서 MyBean 인스턴스를 가져오는 메서드를 정의할 스프링 구성 클래스를 정의해 보겠습니다.

package com.journaldev.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MyConfiguration {
	
	@Bean
	@Scope(value="singleton")
    public MyBean myBean() {
		return new MyBean();
	}
	
}

singleton이 기본 범위이므로 위의 bean 정의에서 @Scope(value=\singleton\)를 제거할 수 있습니다. 이제 메인 메서드를 생성하고 싱글톤 범위를 테스트해 봅시다.

package com.journaldev.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MySpringApp {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(MyConfiguration.class);
		ctx.refresh();

		 MyBean mb1 = ctx.getBean(MyBean.class);
		 System.out.println(mb1.hashCode());

		 MyBean mb2 = ctx.getBean(MyBean.class);
		 System.out.println(mb2.hashCode());

		ctx.close();
	}

}

위의 프로그램이 실행되면 아래와 같은 출력을 얻게 됩니다.

MyBean instance created
867988177
867988177

두 MyBean 인스턴스는 동일한 해시코드를 가지며 생성자는 한 번만 호출됩니다. 이는 스프링 컨테이너가 항상 동일한 MyBean 인스턴스를 반환함을 의미합니다. 이제 범위를 prototype으로 변경하겠습니다.

@Bean
@Scope(value="prototype")
public MyBean myBean() {
	return new MyBean();
}

이번에는 main 메소드가 실행될 때 다음과 같은 출력을 얻을 것입니다.

MyBean instance created
867988177
MyBean instance created
443934570

스프링 컨테이너에서 요청할 때마다 MyBean 인스턴스가 생성된다는 것은 분명합니다. 이제 범위를 request로 변경하겠습니다.

@Bean
@Scope(value="request")
public MyBean myBean() {
	return new MyBean();
}

이 경우 다음과 같은 예외가 발생합니다.

Exception in thread "main" java.lang.IllegalStateException: No Scope registered for scope name 'request'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:347)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1015)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:339)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
	at com.journaldev.spring.MySpringApp.main(MySpringApp.java:12)

request, sessionglobal-session 범위는 독립 실행형 애플리케이션에서 사용할 수 없기 때문입니다.

Spring Bean 요청 및 세션 범위

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.journaldev.spring</groupId>
	<artifactId>Spring-Boot-MVC</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>Spring-Boot-MVC</name>
	<description>Spring Beans Scope MVC</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>10</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

스프링 컴포넌트를 생성하고 범위를 request세션인 스프링 컨테이너에서 스프링 빈으로 구성해 봅시다.

Spring Bean 요청 범위

package com.journaldev.spring;

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

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataRequestScope {

	private String name = "Request Scope";
	
	public DataRequestScope() {
		System.out.println("DataRequestScope Constructor Called");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Spring Bean 세션 범위

package com.journaldev.spring;

import org.springframework.context.annotation.Scope;
import java.time.LocalDateTime;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataSessionScope {

	private String name = "Session Scope";
	
	public DataSessionScope() {
		System.out.println("DataSessionScope Constructor Called at "+LocalDateTime.now());
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

스프링 컴포넌트

이제 스프링 구성 요소를 만들고 스프링을 사용하여 위의 빈을 자동 구성해 보겠습니다.

package com.journaldev.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Customer {

	@Autowired
	private DataRequestScope dataRequestScope;
	
	@Autowired
	private DataSessionScope dataSessionScope;

	public DataRequestScope getDataRequestScope() {
		return dataRequestScope;
	}

	public void setDataRequestScope(DataRequestScope dataRequestScope) {
		this.dataRequestScope = dataRequestScope;
	}

	public DataSessionScope getDataSessionScope() {
		return dataSessionScope;
	}

	public void setDataSessionScope(DataSessionScope dataSessionScope) {
		this.dataSessionScope = dataSessionScope;
	}


}

스프링 레스트 컨트롤러

마지막으로 RestController 클래스를 만들고 테스트 목적으로 일부 API 끝점을 구성해 보겠습니다.

package com.journaldev.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloData {

	@Autowired
	private Customer customer;
	
	@RequestMapping("/nameRS")
	public String helloRS() {
		return customer.getDataRequestScope().getName();
	}
	
	@RequestMapping("/nameSSUpdated")
	public String helloSSUpdated() {
		customer.getDataSessionScope().setName("Session Scope Updated");
		return customer.getDataSessionScope().getName();
	}
	
	@RequestMapping("/nameSS")
	public String helloSS() {
		return customer.getDataSessionScope().getName();
	}
}

Spring Boot 세션 시간 초과 구성

마지막으로 스프링 부트 세션 시간 초과 변수를 구성하고 src/main/resources/application.properties에 아래 속성을 추가해야 합니다.

server.session.cookie.max-age= 1
server.session.timeout= 1

이제 세션 범위가 있는 스프링 빈이 1분 안에 무효화됩니다. SpringBootMvcApplication 클래스를 스프링 부트 애플리케이션으로 실행하기만 하면 됩니다. 구성 중인 엔드포인트에 대한 출력이 아래에 표시되어야 합니다.

2018-05-23 17:02:25.830  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameRS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloRS()
2018-05-23 17:02:25.831  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSSUpdated]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSSUpdated()
2018-05-23 17:02:25.832  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSS()

Spring Bean 요청 범위 테스트

브라우저를 열고 URL https://localhost:8080/nameRS로 이동하여 콘솔 출력을 확인합니다. 각 요청에 대해 DataRequestScope Constructor Called가 인쇄되는 것을 볼 수 있습니다.

Spring Bean 세션 범위 테스트

GitHub 리포지토리에서 Spring Beans Scope Spring Boot 프로젝트를 다운로드할 수 있습니다.