스프링 컨테이너에서 관리하는 자바 객체를 스프링 빈이라고 한다.
XML 설정 파일이나 자바 설정 파일에 빈을 정의해주면 해당 설정 파일을 이용해 스프링 컨테이너에서 빈 정의 내용대로 빈을 관리하도록 한다.
public class MyBean {
...
...
}
<beans ...>
...
...
<bean id="myBean" class="com.study.beans.MyBean" />
<bean id="yourBean" class="com.study.beans.YourBean" />
...
...
<beans/>
xml설정 파일 사용 시 <component-scan/>
을 명시, 자바 설정 파일 사용 시 @ComponentScan
어노테이션을 통해 컨테이너 생성 시점에 component-scan
을 통해 쫘악 스캔하여 빈으로 등록해준다. 두가지 빈 정의 방법이 존재한다.
코드 수정이 가능한 클래스를 객체로 생성하여 초기화해 빈으로 등록하고자 할 때 @Component를 사용한다. 직관적으로 파악 가능하다.
@Component
public class MyBean {
...
...
}
@Bean은 @Configuration을 통해 자바 설정 파일임을 명시하는 클래스 안에 작성한다.
보통 내부적으로 코드 수정이 불가능한 라이브러리를 초기화해 빈으로 등록하고자 할 때 @Bean을 사용한다.
@Configuration
public class MyConf {
@Bean
public MyBeanByConf name() {
return new MyBeanByConf();
}
}
.
스프링빈은 스프링 컨테이너 생성 -> 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백의 생명주기를 가진다.
스프링을 비롯해 많은 IoC 컨터이너는 자신이 관리하는 Bean의 라이프 사이클을 관리하여 주고 특정 시점에 Bean에게 이를 알려줄 수 있는 메커니즘을 제공한다. 이 시점에서 이벤트를 캐치하는 방법은 세 가지가 제공된다. 1. 어노테이션 지정
, 2. 인터페이스 구현
, 3. Bean설정 시 init, destroy 메서드 지정
세가지 방법이다. 상황에 따라 알맞는거 사용하면 된다.
1. @PostConstruct
Bean 생성 시점에 호출 호출하는 메서드 지정
2. @PreDestroy
Bean 소멸 시점에 호출하는 메서드 지정
@Component
public class TestBean {
@PostConstruct
public void postConstruct() {
...
...
}
@PreDestroy
public void preDestroy() {
...
...
}
}
InitializingBean
, DisposableBean
인터페이스를 구현해 afterPropertiesSet
와 destroy
를 재정의한다.
public class SimpleClass implements InitializingBean, DisposableBean{
@Override
public void afterPropertiesSet() throws Exception {
// bean 생성 및 초기화
}
@Override
public void destroy() throws Exception {
// bean 소멸
}
...
...
}
init-method
, destroy-method
에 처리할 메서드를 지정한다.
<bean id="MyBean" class="com.spring.bean.MyBeanClass"
init-method="init" destroy-method="destroy"/>
default로 스프링 컨테이너는 빈을 싱글톤
으로 관리한다.
bean scope
옵션을 지정해주어 스프링 컨테이너에서 해당 객체를 어떻게 관리할 것인지 지정할 수 있다.
scope | 내용 |
---|---|
singleton | 하나의 빈 정의에 대해 스프링 컨테이너에서는 하나의 객체만 존재 |
prototype | 하나의 빈 정의에 대해 스프링 컨테이너에서는 다수의 객체 존재 가능 |
request(web scope) | HTTP Request 요청하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 인스턴스가 생성되고 관리 |
session(web scope) | HTTP Session과 동일한 생명주기를 가지는 스코프 |
application(web scope) | 웹 어플리케이션이 시작되고 종료될 때까지 변수가 유지 스코프 |
websocket(web scope) | 웹 소켓과 동일한 생명주기를 가지는 스코프 |
global session | 하나의 빈 정의에 대해 global session 생명주기 안에 하나의 객체가 존재, 포틀릿 기반의 웹 애플리케이션 용도로 전역 세션 스코프가 빈과 같은 스프링 MVC를 사용한 포탈 애플리케이션 내의 모든 포틀릿 사이에 공유 |
thread | 새 스레드에서 요청하면 새로운 bean 인스턴스를 생성. 같은 스레드의 요청에는 항상 같은 인스턴스가 반환 |
custom | org.pringframework.beans.factory.config.Scope를 구현하고 커스텀 스코프를 스프링의 설정에 등록하여 사용 |
스프링 컨테이너에서 한번 생성되며 컨테이너가 사라지면 해당 빈 객체도 사라진다. 생성된 하나의 인스턴스는 ApplicationContext(스프링 컨테이너)
에 저장되고, 해당 bean에 대한 요청과 참조가 있으면 싱글톤 객체를 반환한다. 그래서 스프링 컨테이너에 빈을 요청할 때마다 동일한 객체를 반환받는다.
스프링은 멀티쓰레드
로 동작한다. 싱글톤 객체는 thread-safe
하지 않다. 쓰레드 A에서 프로퍼티 값을 x로 바꾸고 출력하는 과정에서 쓰레드 B가 프로퍼티 값을 y로 바꾸면 쓰레드 A 입장에서는 예상치 못한 결과가 나올수도 있다. 그래서 싱글톤 객체는 상태를 가지면 안된다. stateless 해야한다.
스프링 컨테이너에 해당 빈을 요청할 때마다 컨테이너는 새로운 객체를 생성해 반환한다. 그래서 하나의 빈 정의에 대해 스프링 컨테이너에 객체가 다수 존재 가능하다.
scope가 prototype의 경우 스프링 컨테이너는 프로토타입 빈의 생명주기를 알지 못하므로 빈의 생성만 관여한다. 그래서 스프링 컨테이너는 빈이 생성되며 init 이벤트는 처리하지만 destroy 이벤트는 처리가 불가능하다.
[Spring bean destroy-method , singleton and prototype scopes]
프로토타입 빈은 더이상 참조되지 않을 경우 가비지 컬렉터에 의해 반환이 이루어진다.
싱글톤은 스프링 컨테이너의 시작와 끝까지 모두 함께하는 스코프이고 프로토타입의 경우 컨테이너에서는 의존관계주입 그리고 초기화까지만 관리하는 스프링 컨테이너에 여러 객체가 존재할 수 있는 스코프이다.
웹 스코프는 웹 환경에서만 사용 가능하다. 따라서 아래와 같은 의존성이 필요하다.
spring-boot-starter-web
해당 패키지는 스프링부트 내장 톰캣 웹서버를 띄워주는 역할을 수행한다.
프로토타입 빈과 싱글톤 빈을 섞어서 사용하는 것은 개발자가 예상치 못한 문제가 발생할 수 있다.
프로토타입 빈의 인스턴스는 계속 생성되고 주입받는 싱글톤 빈은 계속 동일한 하나의 인스턴스일 것이다. 이것은 의도대로 작동하므로 문제가 없을 것이다.
@Component @Scope("prototype")
public class MyBean {
@Autowired
OhMyBean ohMySingletonBean;
}
이때 실수 할 수 있다. 싱글톤 빈의 인스턴스는 단 한번만 생성되고 그 때 프로토타입 빈의 주입도 이미 완료된다.
그렇기 때문에 싱글톤 빈을 사용할때 주입받은 프로토타입 빈이 변경(업데이트) 되지 않는다.
싱글톤객체.getProto()
해서 프로토타입 빈을 요청하면 매번 새로운 객체를 생성해 반환해줄 것 같지만 그렇지 않다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Prototype by Singleton:");
System.out.println(ctx.getBean(Singleton.class).getProto()); //a객체 반환
System.out.println(ctx.getBean(Singleton.class).getProto()); //a객체 반환(동일한 객체)
System.out.println(ctx.getBean(Singleton.class).getProto()); //a객체 반환(동일한 객체)
}
}
이 문제를 해결하기 위해서는 proxyMode
설정을 통해 해결할 수 있다. 프로토타입 빈의 @Scope
어노테이션에 proxyMode
속성을 설정해준다.
@Component @Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
}
proxyMode 속성은 설정하지 않으면 기본값은 scopedProxyMode.DEFAULT
이다.
DEFAULT는 proxy를 사용하지 않는다는 옵션이고 proxy를 사용하려면 클래스인 경우 TARGET_CLASS
, 인터페이스일 경우 INTERFACES
로 설정한다.
프로토타입 빈을 직접 참조하면 인스턴스를 새로 생성해줄 여지가 없기 때문에 매번 인스턴스를 생성해줄 수 있도록 프록시로 감싼다는 개념이다.
오늘은 스프링 빈에 대해서 알아보았다. 다음에는 스프링 빈을 관리해주는 스프링 컨테이너에 대해 알아보자!