스프링 빈 스코프

봄 빈 범위를 사용하면 빈 인스턴스 생성을 더 세밀하게 제어할 수 있습니다. 때로는 빈 인스턴스를 싱글톤으로 생성하고 싶을 수 있지만 다른 경우에는 각 요청마다 만들거나 세션 중에 한 번만 만들기를 원할 수 있습니다.

봄 빈 범위

다섯 가지 종류의 봄 빈 범위가 있습니다:

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

스프링 빈 싱글톤과 프로토타입 스코프

스프링 빈의 싱글톤과 프로토타입 스코프는 독립적인 스프링 앱에서 사용할 수 있습니다. @Scope 주석을 사용하여 이러한 스코프를 쉽게 구성하는 방법을 살펴보겠습니다. 예를 들어 Java 빈 클래스가 있다고 가정해 봅시다.

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이 기본 스코프임을 유의하십시오. 따라서 위의 빈 정의에서 @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();
}

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

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, session, 및 global-session 스코프가 독립형 애플리케이션에는 사용할 수 없기 때문입니다.

스프링 빈 요청 및 세션 스코프

스프링 빈 요청 및 세션 스코프 예제를 위해 스프링 부트 웹 애플리케이션을 만들겠습니다. 스프링 부트 스타터 프로젝트를 만들고 “web”을 선택하여 웹 애플리케이션으로 실행할 수 있도록합니다. 최종 프로젝트는 아래 이미지와 같이 보일 것입니다. ServletInitializerSpringBootMvcApplication은 자동 생성된 스프링 부트 클래스입니다. 거기에는 어떤 변경도 필요하지 않습니다. 여기에 제 pom.xml 파일이 있습니다. 애플리케이션의 종속성을 살펴보세요. 사용 중인 Eclipse 버전에 따라 pom.xml 파일이 약간 다를 수 있습니다.

<?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>

몇 가지 스프링 컴포넌트를 만들고 스프링 컨테이너에서 스코프를 requestsession으로 구성합니다.

스프링 빈 요청 스코프

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;
	}
}

스프링 빈 세션 스코프

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;
	}


}

스프링 레스트 컨트롤러

마지막으로, 테스트 목적으로 몇 가지 API 엔드 포인트를 구성하기 위해 RestController 클래스를 생성합시다.

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();
	}
}

스프링 부트 세션 타임아웃 구성

마지막으로, 스프링 부트 세션 타임아웃 변수를 구성해야 합니다. src/main/resources/application.properties에 아래 속성을 추가합니다.

server.session.cookie.max-age= 1
server.session.timeout= 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 Request Scope Test

어떤 브라우저든 열고 URL https://localhost:8080/nameRS로 이동하여 콘솔 출력을 확인하십시오. 각 요청마다 DataRequestScope Constructor Called이(가) 인쇄된 것을 확인해야 합니다.

Spring Bean Session Scope Test

https://localhost:8080/nameSS로 이동하고 다음 출력을 받게 됩니다. 이제 https://localhost:8080/nameSSUpdated로 이동하여 DataSessionScope 이름 값이 Session Scope Updated로 업데이트되었는지 확인하십시오. 이제 다시 https://localhost:8080/nameSS로 이동하면 업데이트된 값을 볼 수 있어야 합니다. 이 시점에 콘솔 출력에서 DataSessionScope Constructor Called at XXX가 한 번만 나타나야 합니다. 이제 세션 스코프 빈이 무효화되기까지 1분을 기다리십시오. 그런 다음 다시 https://localhost:8080/nameSS로 이동하면 원래 출력이 표시되어야 합니다. 또한 컨테이너에 의해 DataSessionScope가 다시 생성되는 콘솔 메시지를 확인해야 합니다. 이것이 스프링 빈 스코프 튜토리얼입니다.

Spring Beans Scope Spring Boot 프로젝트는 GitHub 저장소에서 다운로드할 수 있습니다.

Source:
https://www.digitalocean.com/community/tutorials/spring-bean-scopes