IoC는 Inversion of Control의 약자로 제어의 역전이라는 의미이다. 제어의 역전이란 프로그램의 제어 흐름 구조가 뒤바뀌는 것이라 설명할 수 있다. IoC에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하거나 생성하지 않는다. 또한, 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
서블릿도 제어의 역전 개념이 적용되어있다고 볼 수 있다.
일반적으로는 main()
메소드에서 시작해 개발자가 미리 정한 순서를 따라 오브젝트가 생성되고 실행된다. 하지만 서블릿은 실행을 개발자가 직접 제어할 수 있는 방법은 없다. 대신 서블릿에 대한 제어 권한을 가진 컨테이너가 적절한 시점에 서블릿 오브젝트를 만들고 그 안의 메소드를 호출한다.
프레임워크도 제어의 역전 개념이 적용된 대표 기술이다. 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다. 프레임워크가 흐름을 주도하는 중에 개발자가 애플리케이션 코드를 사용하도록 만든 구조이다.
- bean : 스프링이 IoC 방식으로 관리하는 오브젝트 or 관리되는 오브젝트이다. ( 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다. )
- bean factory : 스프링의 IoC를 담당하는 핵심 컨테이너로 빈을 등록, 생성, 조회, 반환과 같은 기능을 담당한다. 보통 빈 팩토리를 확장한 Application Context를 사용한다.
- application context : bean factory를 확장한 IoC 컨테이너다. bean factory에 추가로 스프링이 제공하는 각종 부가 서비스를 추가한다. 즉, 애플리케이션 지원 기능을 모두 포함해서 이야기하는 것이라고 보면된다.
- Configuration metadata(설정/설정 메타 정보) : application context 또는 bean factory가 IoC를 적용하기 위해 사용하는 메타정보를 말한다. 보통 IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용
@Configuration
어노테이션으로 설정
- IoC container : IoC방식으로 빈을 관리한다는 뜻에서 application context나 bean factory를 그렇게 부른다. IoC container는 빈 팩토리 관점에서 이야기 하는 것이며, 그냥 컨테이너 혹은 스프링 컨테이너라고 할때는 applicatoin context를 가리키는 것이라고 보면된다.
- spring framework : IoC container, application context를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할때 주로 사용
Factory는 객체의 생성 방법을 결정하고 그렇게 만들어진 Object를 돌려주는 역할을 하는 클래스이다. Factory는 단지 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용하는 것이다.
package springbook.user.dao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
UserDao dao = new UserDao(connectionMaker());
return dao;
}
@Bean
public ConnectionMaker connectionMaker() {
ConnectionMaker connectionMaker = new DConnectionMaker();
return connectionMaker;
}
}
@Configuration
: applicatoin context 또는 bean factory가 사용할 설정정보라는 어노테이션@Bean
: 오브젝트 생성을 담당하는 IoC용 메소드라는 표시
import java.sql.SQLException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import springbook.user.domain.User;
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
User user = new User();
user.setId("testId");
user.setName("testName");
user.setPassword("testtest");
dao.add(user);
}
}
AnnotationConfigApplicationContext
는 @Configuration
이 붙은 자바 코드를 설정정보로 사용할때 사용하면된다.
ApplicationContext는 DaoFactory 클래스를 설정정보로 등록해두고 @Bean
이 붙은 메소드 이름을 가져와 빈 목록을 만들어 둔다. 클라이언트가 ApplicationContext를 getBean()메소드를 호출하면 자신의 빈 목록에서 요청한 이름이 있다면 빈을 생성하는 메소드를 호출해 오브젝트를 생성한 후 반환한다.
ApplicationContext는 또한 싱들톤을 저장하고 관리하는 Singleton Registry이기도 하다. 스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.
스프링은 서버 환경에서 싱글톤이 만들어져 서비스 오브젝트 방식으로 사용되는 것은 적극 지지한다. 하지만 자바의 기본적인 싱글톤 패턴의 구현 방식은 여러가지 단점이 있기때문에 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.
Singleton Registry의 장점은 평범한 자바 클래스이더라도 IoC방식의 컨테이너를 사용해 생성과 관계설정, 사용 등에 대한 제어권을 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다. 그렇기 때문에 테스트 환경에서도 자유롭게 오브젝트를 만들 수 있고, 테스트를 위한 목적으로 오브젝트를 대체하는 것도 간단하다.
싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 stateless 방식으로 만들어져야한다. 이때는 읽기전용 값이라면 초기화 시점에 인스턴스 변수에 저장해두고 공유하는 것은 문제 없다. 만약 각 요청에 대한 정보나, DB 서버의 리소스로 부터 생성한 정보는 파라미터와 로컬 변수, 리턴 값을 이용하면 된다. 메소드 파라미터나, 메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 문제없다.
스프링이 관리하는 오브젝트(=빈)의 Scope는 기본적으로 싱글톤이다.
-
Singleton Scope : 컨테이너 내에 한개의 오브젝트만 만들어져 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지된다.
- 상태가 없는 공유 객체 : 매번 새로운 객체를 생성할 이유가 없다.
- 읽기용으로만 상태를 가진 공유 객체 : 매번 새로운 객체를 생성할 이유가 없다.
- 공유가 필요한 상태를 지닌 공유 객체 : 객체 간 반드시 공유해야할 상태를 지닌 객체가 있다면 이 경우에는 Singleton적합
- 쓰기가 가능하면서도 사용빈도가 매우 높은 객체
- 장시간에 걸쳐 매우 많은 객체가 생성되는 경우
- 해당 객체가 매우 작은 양의 쓰기 상태를 가진 경우
- 객체 생성 비용이 매우 큰 경우
-
Prototype Scope : 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다.(gc에 의해서 bean이 제거된다)
-
Request Scope : HTTP 요청이 생길때마다 생성되는 요청 스코프
-
Session Scope : 웹의 Session과 유사한 세션 스코프
DI는 의존관계 주입은 말 그대로 의존적인 객체를 직접 생성하거나 제어하는 것이 아니라, 특정 객체에 필요한 객체를 외부에서 결정해서 연결시키는 것을 의미한다.
인터페이스에 대해서만 의존관계를 만들어두면 인터페이스 구현 클래스와의 관계는 느슨해지면서 변화에 영향을 덜 받는 상태(= 결합도가 낮은 상태)가 된다. 의존관계란 한쪽의 변화가 다른 쪽에 영향을 주는 것인데, 인터페이스를 통해 의존 관계를 제안해주면 그만큼 변화로부터 자유로워질 수 있다.
런타임 시에 오브젝트 사이에서 만들어지는 의존관계도 있다. 런타임 의존관계 혹은 오브젝트 의존 관계인데, 설계 시점의 의존관계가 실체화된 것으로 볼 수 있다.
인터페이스를 통해 설계 시점에 느슨한 의존관계를 갖는 경우에는 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든 것인지 미리 알 수 없다. 프로그램이 시작되고 오브젝트가 만들어지고 나서 런타임 시에 의존 관계를 맺는 대상을 의존 오브젝트(dependent object)라고 한다.
즉, 의존 관계 주입은 의존 오브젝트와 그것을 사용할 주체 오브젝트를 런타임 시에 연결해주는 작업을 말한다.
- 클래스 모델이나 코드에서 런타임 시점의 의존 관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야한다.
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
- 의존 관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
의존관계 주입(DI)의 핵심은 설계 시점에 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제 3의 존재가 있다는 것이다. 여기서 제 3의 존재는 바로 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너라고 볼 수 있다.
XML으로 의존관계 설정정보를 만들면 다음과 같은 장점이 있다.
- 단순한 텍스트 파일이기때문에 다루기 쉬움
- 쉽게 이해할 수 있으며, 컴파일과 같은 별도의 빌드 작없이 없음
- 빠르게 변경사항을 반영할 수 있음
- 스키마나 DTD를 이용해 정해진 포맷을 따라 작성했는지 쉽게 확인 가능
스프링의 application context는 XML에 담긴 DI정보를 활용할 수 있다. 앞서서 다뤘던 @Configuration
을 <beans>
, @bean
을 <bean>
으로 대응해서 생각하면 이해하기 쉽다.
자바 코드 설정정보 | XML | |
---|---|---|
Bean 설정파일 | @Configuration | <beans> |
Bean 이름 | @Bean methodName() | <bean id="methodName"> |
Bean Class | return new BeanClass() | class="a.b.c...BeanClass" (package까지 포함해야한다.) |
Bean Scope | @Scope() | scope = "singleton" |
@Bean
public ConnectionMaker connectionMaker(){
return new DConnectionMaker();
}
<bean id="connectionMaker" class="springbook.user.dao.DconnectionMaker" />
XML에서는 <property>
태그를 사용해 의존 오브젝트와 관계를 설정한다.
userDao.setConnectionMaker(connectionMaker());
<bean id="userDao" class="springbook.user.dao.UserDao">
<property name="connectionMaker" ref="connectionMaker" />
<!-- 여기서 name은 DI에서 사용할 수정자 메소드의 프로퍼티 명이며, ref는 주입할 오브젝트를 정의한 bean의 ID이다. -->
</bean>