스프링 IoC 컨테이너에 의해 관리되는 자바 객체
IoC (Inversion of Control)
프로그램의 실행 흐름이나 객체의 생명 주기를 개발자가 제어하는 것이 아닌 외부에서 관리해주는 것
스프링 IoC 컨테이너
Spring Bean 객체를 인스턴스화하고 생명 주기를 관리
싱글톤 : 애플리케이션이 시작될 때 static을 사용해서 인스턴스를 메모리에 딱 하나만 할당하고, 다음부터는 만들어진 해당 인스턴스만을 사용하는 디자인 패턴
생성자를 private로 만들기 때문에 외부에서 생성자를 통해 인스턴스를 만들 수 없다.
장점 : 매번 새로운 인스턴스를 만들지 않기 때문에 대규모 트래픽을 처리하기 좋다.
new 생성자를 이용한 경우 인스턴스간의 결합도가 높아서 하나의 인스턴스를 변경할 경우 해당 인스턴스에 의존적인 모든 인스턴스를 변경해줘야 한다. 또한 인스턴스간의 의존성 관계를 개발자가 모두 설정해줘야 한다.
Bean을 사용할 경우 스프링 컨테이너에서 의존성 관리를 해주고 외부에서 의존성을 주입하기 때문에 낮은 결합도를 가지고 있어 하나의 인스턴스를 변경해주어도 다른 인스턴스를 변경하지 않아도 된다.
스프링 컨테이너가 스프링 Bean의 생명 주기를 관리하기 때문에 제어의 흐름을 외부에서 관리해준다(프레임워크의 역할).
Bean을 관리하는 스프링 컨테이너는 Bean Factory를 가지고 있는데, Bean Factory는 스프링 Bean을 관리하고 조회한다.
ApplicationContext는 Bean Factory의 하위 인터페이스로 Bean Factory의 기능 + 부가적인 기능(국제화 기능, 환경 변수 관련 처리, 애플리케이션 이벤트, 리소스 조회)을 가지고 있다. 그리고 ApplicationContext의 기본 생성자는 private로 되어 있기 때문에 수정할 수 없으며 조회만 가능하다.
그 중에서 가장 눈여겨 볼 기능은 ApplicationContext가 스프링 컨테이너 내부의 Bean 저장소에 Bean을 등록하고 싱글톤으로 관리한다는 것이다.
@Configuration이 붙은 클래스들을 설정 정보로 등록하고 @Bean이 붙은 메서드의 이름을 key로 그리고 메서드 객체를 value로 Bean 저장소에 저장한다.
클라이언트가 Bean을 요청한다.
Bean을 반환하거나 생성한다.
만약 ApplicationContext가 없다면 이 모든 기능을 할 클래스를 자바로 만들어 줘야 한다.
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class ApplicationContext {
// 싱글톤을 위해 자기 자신을 참조하는 static 필드 선언. 바로 초기화
// 외부에서는 사용이 안되지만, 내부에서는 사용이 가능하기 때문에 딱 하나의 인스턴스 생성 가능
private static ApplicationContext instance = new ApplicationContext();
// 위에서 만든 인스턴스를 반환하는 static 메서드
// 외부에서 인스턴스를 받을 수 있도록 만들어줌
public static ApplicationContext getInstance(){
return instance;
}
private Properties props;
private Map objectMap;
// private 생성자 사용
// 왜냐하면 외부에서 new 생성자를 막아서 인스턴스를 생성하지 못하게 한다.
private ApplicationContext(){
props = new Properties();
objectMap = new HashMap<String, Object>();
try{
props.load(new FileInputStream("src/main/resources/Beans.properties"));
} catch (IOException e){
e.printStackTrace();
}
}
public Object getBean(String id) throws Exception{
Object beanObject = objectMap.get(id); // HashMap 구조
if(beanObject == null){
String className = props.getProperty(id);
// className에 해당하는 클래스 찾기
Class clazz = Class.forName(className);
// 찾은 클래스를 가지고 인스턴스 만들기
Object o = clazz.newInstance();
objectMap.put(id, o);
beanObject = objectMap.get(id);
}
return beanObject;
}
}
이렇게 되면 싱글톤 패턴으로 만들어진 인스턴스를 사용 가능하다.
ApplicationContext context1 = ApplicationContext.getInstance();
ApplicationContext context2 = ApplicationContext.getInstance();
따라서 위의 context1과 context2는 같은 객체가 된다.
하지만 위와 같이 직접 코드를 짜면 private 생성자를 갖고 있어 상속이 불가능하다. 또한 테스트하기 어렵다.
Bean Factory는 lazy-loading으로 getBean() 메서드가 호출되면 그 때서야 Bean을 인스턴스화한다. (On-Demand)
ApplicationContext는 pre-loading으로 런타임 실행 시 모든 Bean들을 로딩된다.
사용해야 할 Bean이 많으면 ApplicationContext가 좋고 사용해야 할 Bean이 많지 않다면 Bean Factory가 좋지만 스프링에서는 부가 기능이 많은 ApplicationContext 사용을 권장하고 있다.
@Component를 붙여서 자동으로 스프링 컨테이너에 Bean을 등록하는 방법
@Component는 ElementType.TYPE 설정이 있으므로 Class 혹은 Interface에만 붙일 수 있다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// Bean 정의
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public AnotherBean anotherBean() {
return new AnotherBean(myBean()); // 다른 Bean을 주입
}
}
@Configuration을 class 위에 붙여서 싱글톤 패턴을 가지도록 하고, @Bean을 사용한다.
@Bean은 ElementType 설정이 METHOD 혹은 ANNOTATION_TYPE이므로 메서드나 어노테이션에만 붙일 수 있다. 클래스에 붙일 수는 없다.
싱글톤(Singleton) : 기본 스코프로 스프링 컨테이너에 하나의 Bean 정의에 하나의 객체만 생성
프로토타입(Prototype) : 스프링 컨테이너에 하나의 Bean에 여러 개의 객체가 있을 수 있다.
웹 관련 스코프
HTTP Request : 하나의 Bean 정의에 하나의 HTTP Request 생명주기 안에 단 하나의 객체가 존재한다. 즉, 각각의 HTTP Request는 자신만의 Bean 객체를 가진다.
HTTP Session : 하나의 Bean 정의에 하나의 HTTP Session 생명주기 안에 단 하나의 객체가 존재한다.
application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
프로토타입 빈 스코프는 컨테이너가 생성, 의존성 주입, 초기화까지만 처리해준다. 종료 메서드가 호출되지 않기 때문에 프로토타입 빈을 사용한 클라이언트가 관리해줘야 하며, 사용이 끝난 프로토타입 빈은 GC가 처리한다.
반면에 싱글톤을 생성부터 종료까지 컨테이너에서 관리해준다.