[Spring-기본] 빈 스코프

DANI·2023년 11월 28일

Spring[김영한T]

목록 보기
29/31
post-thumbnail

📕 빈 스코프(Bean Scope)란?

스프링 빈은 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지된다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다

스코프(Scope)란? : 빈이 존재할 수 있는 범위


📑 스프링이 지원하는 스코프

✅ 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
✅ 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 고나여하고 더는 관리하지 않음
✅ 웹 관련 스코프

  • request : 웹 요청에서 들어오고 나갈 때까지 유지

  • session : 웹 세션이 생성되고 종료될 때까지 유지

  • appliccation : 서블릿 컨텍스트와 같은 범위로 유지

  • websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프



✨ 컴포넌트 스캔 자동 등록

@Scope("prototype")
@Component
static class Prototype{}

✨ 컴포넌트 스캔 수동 등록

@Scope("prototype")
@Bean
Prototype HelloBean(){
    return new HelloBean();
}



🔑 프로토타입 스코프



새로운 빈을 생성해서 반환하고, 관리하지 않는다.
싱글톤 스코프 처럼 같은 인스턴스를 반환하는 것이 아니라 요청마다 새로운 빈을 생성해서 반환하게 된다.

코드로 알아보자



💾 PrototypeTest

package com.scope;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class PrototypeTest {
	
	@Test
	void prototypeTest() {
		// 스프링 컨테이너로 등록
		AnnotationConfigApplicationContext ac =   new AnnotationConfigApplicationContext(Prototype.class);
		
		// bean1 인스턴스 생성 
		Prototype bean1 = ac.getBean(Prototype.class);
		System.out.println(bean1);
		
		// bean2 인스턴스 생성 
		Prototype bean2 = ac.getBean(Prototype.class);
		System.out.println(bean2);
		
		assertThat(bean1).isNotSameAs(bean2);
		
		// 소멸 (호출되지 않음)
		ac.close();
		
	}
	
	// 프로토타입 스코프로 지정
	@Scope("prototype")
	static class Prototype{
		
		// 초기화 
		@PostConstruct
		public void init() {
			System.out.println("PrototypeInit");
		}
		
		// 소멸
		@PreDestroy
		public void destory() {
			System.out.println("PrototypeDestory");
		}
	}

}

🔵 실행결과

PrototypeInit
com.scope.PrototypeTest$Prototype@460ebd80
PrototypeInit
com.scope.PrototypeTest$Prototype@6f3c660a

다른 인스턴스로 호출됨. 소멸 메서드가 실행되지 않음 why? 프로토타입의 스코프는 생성과 초기화까지만 관리하고 소멸까지 관리하지 않는다.



🔑 싱글톤 스코프


💾 SingleTonTest

package com.scope;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class SingleTonTest {
	
	@Test
	void singleTonTest() {
		
		AnnotationConfigApplicationContext ac =   new AnnotationConfigApplicationContext(SingleTon.class);
		
		// bean1 인스턴스 생성
		SingleTon bean1 = ac.getBean(SingleTon.class);
		System.out.println(bean1);

		// bean2 인스턴스 생성
		SingleTon bean2 = ac.getBean(SingleTon.class);
		System.out.println(bean2);
		
		// 같은 인스턴스인가
		assertThat(bean1).isSameAs(bean2);
		
		// 소멸
		ac.close();
		
		
	}
	
	@Scope("singleton")
	static class SingleTon{
		
		@PostConstruct
		public void init() {
			System.out.println("SingletonInit");
		}
		
		@PreDestroy
		public void destory() {
			System.out.println("SingletonDestory");
		}
	}

}

🔵 실행결과

SingletonInit
com.scope.SingleTonTest$SingleTon@460ebd80
com.scope.SingleTonTest$SingleTon@460ebd80
SingletonDestory

종료메서드까지 정상 호출 되었다. 싱글톤 스코프의 경우 생성->초기화->소멸 까지 관리한다.





❓ 프로토 스코프 타입 빈 직접 요청한 경우

💾 SingleTonWithPrototypeTest

package com.scope;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class SingleTonWithPrototypeTest {
	
	@Test
	void PrototypeTest() {
		
		AnnotationConfigApplicationContext ac =   new AnnotationConfigApplicationContext(Prototype1.class);
		
		// 프로토스코프 타입의 인스턴스 bean1 생성
		Prototype1 bean1 = ac.getBean(Prototype1.class);
		// 카운트추가
		bean1.addCount();
		
		// 프로토스코프 타입의 인스턴스 bean2 생성
		Prototype1 bean2 = ac.getBean(Prototype1.class);
		// 카운트 추가
		bean2.addCount();
		
		// bean1에 저장된 카운트
		System.out.println(bean1.getCount()); // 1
		// bean2에 저장된 카운트
		System.out.println(bean2.getCount()); // 1
		
		assertThat(bean1.getCount()).isEqualTo(1);
		assertThat(bean2.getCount()).isEqualTo(1);
		
		ac.close(); // 출력되지 않음
	}
  
  
@Scope("prototype")
static class Prototype1{
		
	private int count=0;
		
	// 초기화
	@PostConstruct
	public void init() {
		System.out.println("PrototypeInit : " + this);
	}
		
	public void addCount() {
		count++;
	}
		
	public int getCount() {
		return count;
	}
		
	// 소멸
	@PreDestroy
	public void destory() {
		System.out.println("PrototypeDestory");
	}
}
}

🔵 실행결과

PrototypeInit : com.scope.SingleTonWithPrototypeTest$Prototype1@21a21c64
1
PrototypeInit : com.scope.SingleTonWithPrototypeTest$Prototype1@6f3c660a
1

프로토스코프 타입의 빈은 생성 시마다 새로운 인스턴스를 반환해주기 때문에 카운트가 2가 아닌 1로 반환된다. 반환 후 소멸가지 관리하지 않기 때문에 소멸메소드는 출력되지 않는다.



✨ 싱글톤 빈에서 프로토타입 스코프를 사용하고 싶을땐?

DI(의존관계 주입)이 아닌 의존관계를 직접 찾는 DL(Dependency Lookup) 의존관계 조회(탐색)이라고 한다.

ObjectFactory, ObjectProvider
Provider 사용



🔑 ObjectFactory, ObjectProvider 사용


💾 SingleTonWithPrototypeTest2

package com.scope;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Provider;

public class SingleTonWithPrototypeTest2 {
	
	@Test
	@DisplayName("싱글톤에서 프로토타입 빈 사용하기")
	void ClientBeanTest() {
		
		AnnotationConfigApplicationContext ac =   new AnnotationConfigApplicationContext(ClientBean.class, Prototype1.class);
		
		ClientBean bean1 = ac.getBean(ClientBean.class);
		System.out.println(bean1.logic());
		
		ClientBean bean2 = ac.getBean(ClientBean.class);
		System.out.println(bean2.logic());
		
		ac.close(); // 출력되지 않음
	}
  
  static class ClientBean{
		
		private final ObjectProvider<Prototype1> prototypeProvider;
		
		public ClientBean(ObjectProvider<Prototype1> prototypeProvider) {
			this.prototypeProvider = prototypeProvider;
		}

		public int logic() {
            // 항상 새로운 프로토타입 빈을 생성하고 반환한다.
			Prototype1 prototypebean = prototypeProvider.getObject();
			prototypebean.addCount();
			return prototypebean.getCount();
		}
	}
  
  @Scope("prototype")
	static class Prototype1{
		
		private int count=0;
		
		@PostConstruct
		public void init() {
			System.out.println("PrototypeInit" + this);
		}
		
		public void addCount() {
			count++;
		}
		
		public int getCount() {
			return count;
		}
		
		@PreDestroy
		public void destory() {
			System.out.println("PrototypeDestory");
		}
	}

}

🔵 실행결과

PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@f316aeb
1
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@6aa3a905
1

ObjectProviderObjectFactory를 상속한다. ObjectFactory에서 편의기능이 추가된 것이 ObjectProvider이다



🔑 Provider 사용


💾 SingleTonWithPrototypeTest2

package com.scope;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Provider;

public class SingleTonWithPrototypeTest2 {
  
  @Test
	@DisplayName("싱글톤에서 프로토타입 빈 사용하기")
	void ClientBeanTest1() {
		AnnotationConfigApplicationContext ac =   new AnnotationConfigApplicationContext(ClientBean1.class, Prototype1.class);
		ClientBean1 bean1 = ac.getBean(ClientBean1.class);
		System.out.println(bean1.logic());
		ClientBean1 bean2 = ac.getBean(ClientBean1.class);
		System.out.println(bean2.logic());
		
		
		ac.close(); // 출력되지 않음
	}
	
  static class ClientBean1{
		
		private final Provider<Prototype1> providers;
		
		public ClientBean1(Provider<Prototype1> providers) {
			super();
			this.providers = providers;
		}

		public int logic() {
			Prototype1 prototypebean = providers.get();
			prototypebean.addCount();
			return prototypebean.getCount();
		}
	}
	
	
	
	@Scope("prototype")
	static class Prototype1{
		
		private int count=0;
		
		@PostConstruct
		public void init() {
			System.out.println("PrototypeInit" + this);
		}
		
		public void addCount() {
			count++;
		}
		
		public int getCount() {
			return count;
		}
		
		@PreDestroy
		public void destory() {
			System.out.println("PrototypeDestory");
		}
	}

}

	

🔵 실행결과

PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@324a0017
1
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@6187d1f5
1

testImplementation 'jakarta.inject:jakarta.inject-api:2.0.1' build.gradle에 추가 후 사용할 수 있다.
Provider는 자바 표준이므로, 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

0개의 댓글