스프링 도큐먼트 읽고 정리해보기 / Version 5.1.9.RELEASE


1. The IoC Container

1.1 Introduction to the Spring IoC Container and Beans

IoC와 DI는 유사한 개념으로 객체(빈) 사이의 의존성을 별도의 IoC 컨테이너에서 관리한다.

IoC 컨테이너에 의해서 객체가 생성되고, 초기화되고, 조립되며 이렇게 IoC 컨테이너에 의해서 관리되는 객체를 Bean이라 부른다.

IoC컨테이너는 Bean 관리를 위해서 설정정보(configuration metadata)를 사용한다. 설정정보는 XML파일이나 자바 애노테이션 또는 자바 코드가 될 수 있다.

 

1.2 Container Overview

스프링에서 IoC컨테이너는 BeanFactory 인터페이스를 말하며, 실제 어플리케이션에서는 이를 확장한 ApplicationContext를 의미하는 것이 일반적이다.

비즈니스가 포함된 자바 소스(POJOs)와 빈 설정관련 정보가 있으면 스프링컨테이너에 이를 읽어들여 실행가능한 어플리케이션을 구성해준다.

ApplicationContext는 인터페이스이므로 ApplicationContext를 생성(초기화)하고자 한다면 설정파일(빈 설정에 사용되는 메타데이터)의 타입에 따라 클래스를 선택한다. 스프링에는 이미 구현된 다수의 클래스들을 제공하고 있다.

 

제공되는 구현 클래스 예시 : ClassPathXmlApplicationContext(XML), AnnotationConfigApplicationContext(JAVA)

 

ApplicationContext가 제공하는 T getBean(String name, Class<T> requiredType) 메서드를 통해 IoC컨테이너에서 관리되는 빈을 가져올 수 있지만 어플리케이션 소스에서 직접 빈을 가져오는 Spring API를 사용하는 것은 안된다.

@Autowired 애노테이션처럼 스프링에서 DI를 위해 제공하는 방법에 따라 빈을 DI받아야 한다.

 

1.3 Bean Overview

빈 설정파일에는 빈 정의에 대한 내용이 담겨있으며 상황에 따라 클래스파일이나 XML파일을 선택하여 작성한다.

빈 정의시 Class, Name, Scope 등 여러 Property들을 정의할 수 있다.

 

1.4 Dependencies

어플리케이션은 한 개의 객체로만 구성되어 있지 않고 여러 객체가 서로 상호작용하며 동작한다. 한 객체가 다른 객체를 사용할 때 의존성(Dependency)을 가진다고 한다.

 

스프링에서는 객체 간의 의존성을 스프링 컨테이너가 관리하며, 객체가 생성되고 초기화 될 때 객체간의 의존성이 구성된다. 즉, 소스코드 자체에서 의존성을 관리하는 것이 아니라 컨테이너가 메타데이터를 이용해 객체간의 의존성을 구성해주므로 의존성 주입(Dependency Injection)이라 한다.

 

컨테이너에 의한 의존성 주입은 객체의 생성자나 세터(setter) 메서드를 이용하여 실행된다.

 

1.5 Bean Scopes

스프링 컨테이너가 생성하는 빈들의 디폴트 스코프는 싱글톤이며, 이를 포함하여 지원하는 스코프는 총 6개가 있다. 빈설정 메타데이터에 스코프를 지정 가능하며 대부분의 경우에는 디폴트인 싱글톤을 사용한다.

 

Singleton, Prototype, Request, Session, Application, Websocket

 

빈이 stateless 할 때는 싱글톤을 사용해도 괜찮지만 빈이 stateful 하다면 프로토타입을 고려한다. 단 싱글톤 빈이 프로토타입 빈을 참조할 때는 항상 같은 빈이 참조되는 상황을 생각해야 한다.

 

(참고) 싱글톤 빈이 프로토타입 빈을 참조하면서 업데이트가 되게 하는 3가지 방법

  1. scoped-proxy 2. Object-Provider 3. Provider(표준)

1.6 Customizing the Nature of a Bean

스프링 컨테이너에서 빈이 생성되고 초기화되는 과정 외에도 빈을 커스터마이징 할 수 있다.

  • Lifecycle Callbacks
    라이프사이클에 따라서 호출될 콜백메서드를 지정한다.
    InitializingBean 인터페이스의 afterPropertiesSet()과 DisposableBean 인터페이스의 destroy() 를 구현함으로써 빈 생성 이후 동작을 지정할 수 있지만 스프링의 라이브러리를 코드에서 직접적으로 사용한다는 점에서 추천되지 않는다.
    대신 JSR-250의 @PostConstruct 와 @PreDestroy 를 활용하면, 스프링 소스에 의존하지 않고 커스터마이징을 할 수 있다.(XML에서는 init-method 와 destroy-method 속성을 활용한다.)

@Component
public class TestPerson implements InitializingBean, DisposableBean {
    private String name;
    private int age;

    @PostConstruct
    public void init(){
        // 빈 생성 후의 동작을 지정한다.
    }

    @PreDestroy
    public void end(){
        // 빈 종료 후의 동작을 지정한다.
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 빈 생성 후의 동작을 지정한다.
    }

    @Override
    public void destroy() throws Exception {
        // 빈 종료 후의 동작을 지정한다.
    }
}
  • ApplicationContextAware / BeanNameAware 위 인터페이스의 메서드를 구현하여 ApplicationContext와 BeanName을 확인할 수 있다. ApplicationContext를 이용하여 소스코드에서 빈을 커스터마이징할 수 있다. 하지만 이 방법도 스프링의 라이브러리를 코드에서 직접 사용하는 것이므로 추천되지 않는다.

public interface ApplicationContextAware { 
	void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 
}
  • 그 외 Aware 인터페이스 ApplicationContextAware 와 BeanNameAware 을 포함하여 스프링에서 제공하는 Aware 인터페이스들이 있다. 자세한 내용은 링크 참조. 하지만 이런 Aware들을 사용하는 것도 스프링 라이브러리에 의존하며, IoC 스타일에 맞지 않으므로 지양하자.

1.7 Bean Definition Inheritance

XML을 이용한 빈 설정시 parent 속성을 이용하여 다른 빈의 설정을 그대로 적용할 수 있다. 소스코드를 이용한 메타데이터 설정시에는 그다지 필요한 부분은 아닌듯 하다.

 

1.8 Container Extension Points

개발자가 ApplicationContext 의 구현체를 직접 사용하지 않고 제공되는 통합 인터페이스(integration interface)를 이용하여 컨테이너를 확장 할 수 있다.

  • BeanPostProcessor 인터페이스로 빈을 커스터마이징
    BeanPostProcessor는 ApplicationContext가 빈을 초기화하는 과정에 동작한다. 그러므로 해당 인터페이스를 구현한 BeanPostProcessor 객체(빈)를 컨테이너에 등록하면 빈 초기화 과정에서 추가적인 동작(초기화 로직, 의존성 변경 등)을 지정할 수 있다. 다수의 BeanPostProcessor 가 동작하는 경우 Ordered 인터페이스를 구현하여 동작 순서를 지정한다.
    빈 개별마다 초기화 동작을 지정하고 싶다면 @PostConstruct 를 사용하고 공통적으로 지정할 로직은 BeanPostProcessor 의 사용을 고려한다.

  • BeanFactoryPostProcessor 인터페이스로 메타데이터를 커스터마이징
    BeanPostProcessor 와 갖는 의미(Semantic)은 유사하지만 큰 차이점은 빈 설정 메타데이터에 대한 조작이 가능하다는 점이다. 스프링 컨테이너는 빈 초기화 전에 등록된 BeanFactoryPostProcessor 구현체에게 메타데이터에 대한 변경을 가능하게 해준다.
    여러개의 BeanFactoryPostProcessor 가 존재할 경우 Ordered 인터페이스를 구현하여 동작 순서를 지정한다.
    예시로는 PropertyPlaceholderConfigurer 가 있는데, 빈 설정 메타데이터에서 ${property.name} 와 같이 placeholder 안에 있는 값을 지정된 외부위치로부터 가져와 치환해준다.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

PropertyPlaceholderConfigurer(BeanFactoryPostProcessor의 구현체)를 빈으로 등록하면, 빈 초기화시 동작하여 placeholder로 표현한 프로퍼티 값이 주입된다.

  • FactoryBean 인터페이스로 빈 초기화를 커스터마이징
    빈 설정 메타데이터를 XML방식으로 사용하면서, 복잡한 빈 초기화 로직을 작성하고자 한다면 자바소스로 작성하는 것이 더 낫다. 이럴 때 FactoryBean 을 작성하여 <bean />의 class 프로퍼티에 지정해주면 FactoryBean.getObject() 가 리턴하는 객체가 빈으로 등록된다.
    ApplicationContext 객체로부터 FactoryBean 의 실제 구현체를 가져오고자 하는경우 getBean("&name") 으로 '&' 을 prefix로 하여 호출하면 된다.(getBean("name")은 FactoryBean의 구현체가 리턴된다.)

+ Recent posts