@Bean
을 사용하거나 ² xml 설정을 통해 일반 객체를 Bean으로 등록이 가능하다.IoC란 영어 그대로 제어의 역전이라 부른다.
제어의 역전이란 메서드나 객체의 호출 작업을 개발자가 아닌 스프링에게 제어권을 넘기는 것을 말한다.
이전까지는 개발자가 객체의 생성을 관리하며 제어했지만, 스프링을 사용하게 되면 스프링 컨테이너에게 제어권을 넘겨 스프링 컨테이너가 흐름을 제어하게 된다.
DL
DI
DL 사용시 컨테이너 종속이 증가하기 때문에 주로 DI를 사용한다.
프레임워크를 적용하지 않은, 우리가 그동안 작성해왔던 일반적인 프로그램을 생각해보자. 객체의 생명주기(객체의 생성, 초기화, 소멸, 메서드 호출 등)를 클라이언트 구현 객체가 직접 관리한다. 또한 다른 사람이 작성한 외부 코드(라이브러리)를 호출하더라도 해당 코드의 호출 시점 역시 직접 관리한다. 하지만 이러한 생명주기를 직접 관리하지 않는 경우라면?🤔
스프링과 같은 프레임워크를 사용할 때를 생각해보자. Controller, Service 같은 객체들의 동작을 우리가 직접 구현하기는 하지만, 해당 객체들이 어느 시점에 호출될 지는 신경쓰지 않는다🤷♀️. 단지 프레임워크가 요구하는대로 객체를 생성하면, 프레임워크가 해당 객체들을 가져다가 생성하고, 메서드를 호출하고, 소멸시킨다. 프로그램의 제어권이 역전된 것이다.
때문에 프레임워크와 라이브러리는 어떤 차이가 있는지에 대해 IoC를 통해 설명이 가능하다. 라이브러리를 사용하는 어플리케이션은 어플리케이션의 제어 흐름을 라이브러리에 내주지 않는다. 단지 필요한 시점에 라이브러리에 작성된 객체를 적재적소에 가져다 쓸 뿐이다. 하지만 프레임워크를 사용한 어플리케이션의 경우, 어플리케이션 코드에 작성한 객체들을 프레임워크가 필요한 시점에 가져다가 프로그램을 구동하기 때문에 프로그램의 제어권이 프레임워크로 역전된다.
spring 컨테이너가 ioc 컨테이너의 역할을 하는거지 둘이 완전히 같은게 아님
-> di컨테이너라고도 한다.
=> 그 반대는 적용되지 않는다. 포함하지 않는다.
ioc 컨테이너의 역할을 하려고 하는게 스프링에서는 '스프링컨테이너'가 그 역할을 하는 것
1) DI(Dependency Injection, 의존성 주입)
개발자가 객체를 new해서 생성하지않고, Ioc컨테이너에 존재하는 Bean 객체를 주입해준다. 해당 기능을 사용하면 싱글톤, 개발자의 편의, 성능 이슈 등등을 해결해준다.
2) 객체의 Scope
Ioc 컨테이너에 객체의 제어권을 넘겨주면, Ioc 컨테이너가 해당 객체의 Scope를 관리해준다.
3) 라이프 사이클 인터페이스 지원
완벽히 구현되지 않은 클래스를 단위 테스트할 때, 테스팅을 도와준다
Container가 로드되면, Bean에 해당하는 객체들을 scan 하여, 해당 Bean들을 생성하려고 한다.
-> 이 과정에서 의존성 주입이 이루어진다.
=> 만약 순환 참조가 있다면 예외가 발생하여 Application은 종료된다.
이제 Bean들이 생성되려고 하는데, 사용자가 지정한 init method가 존재한다면, 객체가 생성되고 init이 실행되게 된다.
그 뒤에 사용자가 지정한 utility method(afterPropertiesSet)과 같은 메서드가 존재한다면, 해당 메서드가 실행되게 된다.(콜백 함수)
프로그램이 종료되기 전에 이제 Container도 같이 종료되려고 하는데, 이 과정에서 destory 메서드가 존재한다면, 실행하고 정상적으로 종료
- 절대 IoC == DI가 아니다! (DI ⊂ IoC 라면 몰라도) DI 없이도 IoC를 만족하는 프로그램을 만들 수 있다.
- IoC는 프로그램 제어권을 역전시키는 개념이고, DI는 해당 개념을 구현하기 위해 사용하는 디자인 패턴 중 하나로, 이름 그대로 객체의 의존관계를 외부에서 주입시키는 패턴을 말한다.
웹 프로그래밍은 멀티유저에 대한 동시성 제어가 중요하며 이를 위해 스프링 컨테이너에서는 싱글톤 패턴으로 관리한다.
크게 두 가지 방법이 있는데,
스프링에서 제공하는 @Component 어노테이션으로 클래스를 명시하여 컨테이너가 생성될 때 컴포넌트 스캔을 통해 자동으로 빈에 등록할 수 있다.
@Component
@Controller
@Service
@Repository
위 3가지 어노테이션들은 @Component로 구성되어 있어서 컴포넌트 스캔에 이용할 수 있다.
- (결론): 연관된 어노테이션들을 이용하여 클래스를 명시해주면 스프링 컨테이너는 컴포넌트 스캔을 통해 자동으로 해당 클래스의 객체를 빈(Bean)으로 관리한다.
Configuration을 위한 클래스를 하나 만든 후(ex SpringConfig,,,), @Configuration
으로 명시를 하면 된다. 그리고 빈으로 등록할 클래스를 @Bean 메소드
로 생성하여 객체를 리턴하면 된다.
이렇게 쓰게되면 만일 인터페이스로 구현된 Repository 클래스가 다른 클래스로 변경된다고 했을 경우, 간단히 해당 Config파일에서 찾아 리턴 클래스 객체만을 수정해주면 된다.
Bean Scope는 기본적으로 빈이 존재하는 범위를 뜻한다.
Scope | 설명 |
---|---|
singleton | 하나의 Bean 정의에 대해 Spring IoC Container에서 단 하나의 객체만 존재한다. |
prototype | 하나의 Bean 정의에 대해 다수의 객체가 존재할 수 있다. |
request | <웹스코프..> 하나의 Bean 정의에 대해 하나의 HTTP request의 생명주기 안에 단 하나의 객체만 존재한다. 각각의 HTTP request는 자신만의 객체를 가지며 Web-aware Spring ApplicationContext 안에서만 유효한 특징이 있다. |
session | 하나의 Bean 정의에 대해 하나의 HTTP Session의 생명주기 안에서 단 하나의 객체만 존재한다. Web-aware Spring ApplicationContext 안에서만 유효한 특징이 있다. |
global session | 하나의 Bean 정의에 대해 하나의 global HTTP Session의 생명주기 안에서 단 하나의 객체만 존재한다. 일반적으로는 portlet context안에서만 유효하며, Web-aware Spring ApplicationContext 안에서만 유효한 특징이 있다. |
@Scope 어노테이션
을 @Bean 어노테이션
과 함께 사용해야 한다.그 중에 몇 개의 예시를 들면...
singleton bean은 Spring 컨테이너 안에서 딱 한 번 생성되며, 컨테이너가 사라질 때는 bean고 같이 사라진다.
// xml 설정
<bean id="..." class="..." scope="singleton"></bean>
// annotaion 설정 (대상 클래스에 적용)
@Scope("singleton")
// xml 설정
<bean id="..." class="..." scope="prototype"></bean>
// annotaion 설정 (대상 클래스에 적용)
@Scope("prototype")
1. 스프링 IoC 컨테이너 생성 → 2. 스프링 빈 생성 → 3. 의존관계 주입 → 4. 초기화 콜백 메소드 호출 → 5. 사용 → 6. 소멸 전 콜백 메소드 호출 → 7. 스프링 종료 과정의 생명 주기를 가지고 있다.
- Bean은 스프링 컨테이너에 의해 생명주기를 관리한다.
스프링은 "의존관계 주입이 완료되면"... 스프링 빈에게 콜백 메소드를 통해 초기화 시점을 알려주며,
스프링 컨테이너가 종료되기 직전에도 소멸 콜백 메소드를 통해 소멸 시점을 알려준다.
public class ExampleBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 초기화 콜백 (의존관계 주입이 끝나면 호출)
}
@Override
public void destroy() throws Exception {
// 소멸 전 콜백 (Bean 종료 전 메모리 반납, 연결 종료와 같은 과정)
}
}
InitializingBean Interface
또는 DisposableBean
을 구현하고 있으며 해당 인터페이스에서 정의된 메소드를 호출해 빈 객체의 초기화 또는 종료를 수행한다.
현재는 거의 쓰지 않음( 초창기 방법임)
public class ExampleBean {
public void initialize() throws Exception {
// 초기화 콜백 (의존관계 주입이 끝나면 호출)
}
public void close() throws Exception {
// 소멸 전 콜백 (메모리 반납, 연결 종료와 같은 과정)
}
}
@Configuration
class LifeCycleConfig {
@Bean(initMethod = "initialize", destroyMethod = "close")
public ExampleBean exampleBean() {
// 생략
}
}
initMethod
와 destoryMethod
를 직접 지정해야 하기에 번거롭다.
@Bean
의destoryMethod
는 기본값이 inferred(추론)으로 등록
- 즉,
close
,shutdown
이라는 이름의 메소드가 '종료 메소드이군~!'이라고 추론하고 🤔 자동으로 호출해준다.👍
=> 즉, 종료 메소드를 따로 부여하지 않더라도 잘 작동한다.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class ExampleBean {
@PostConstruct
public void initialize() throws Exception {
// 초기화 콜백 (의존관계 주입이 끝나면 호출)
}
@PreDestroy
public void close() throws Exception {
// 소멸 전 콜백 (메모리 반납, 연결 종료와 같은 과정)
}
}
javax.annotation.xxx
이다.외부 라이브러리에서 초기화, 종료를 해야 할 경우 두 번째 방법 즉,
@Bean
의initMethod
와destoryMethod
속성을 사용하자.
"둘다 빈으로 등록되게 하고 관리되게 해주는 것이라던데,,, 빈을 생성하는데 @Bean과 @Component 어노테이션 둘의 차이 점은 무엇일까?" 🤔
A. 특정 객체를 의존하는 다른 객체들에게 의존성을 주입하기 위해서입니다.
이를 위해서 Spring IoC 컨테이너에서 관리할 수 있도록, 즉 ApplicationContext가 관리하는 bean으로 등록하기 위해서 이런 어노테이션을 붙이는 것이고, 해당 어노테이션이 붙은 객체들을 의존하는 다른 객체들이 컨테이너가 빈으로 등록하여 관리하는 의존성을 @Autowired를 선언함으로써 주입받을 수 있습니다.
@Bean
은 @Configuration
이나 @Component
어노테이션이 붙은 클래스 내에 메서드 레벨에서 사용@Component
는@Bean
은 개발자가 직접 작성하지 않은 서드파티 라이브러리의 설정과 같은 의존성을 주입받기 위해서 사용하기에 적절하고,(@Configuration이 붙은 설정 파일에 주로 선언함)
@Component
는 개발자가 직.접. 작성한 코드에 있는 의존성을 주입받기 위해서 사용하기에 적절
컴포넌트 스캔은 스프링 스캔을 어디서부터 시작하는지
developer-ellen님의 글을 바탕으로 작성하였습니다.