1장. 오브젝트와 의존관계
- 초난감 DAO
- DAO의 분리
- DAO의 확장
- 제어의 역전(IoC)
- 스프링의 IoC
- 싱글톤 레지스트리와 오브젝트 스코프
- 의존관계 주입(DI)
- 정리
오브젝트와 의존관계(2)에 이어서 스프링의 IoC와 싱글톤 레지스트리와 오브젝트 스코프에 대해 살펴봅시다.
스프링의 IoC
이전 글에서는 초난감 DAO를 개선하고 객체지향 프로그램을 지향하는 방향을 살펴봤다면 이번에는 스프링을 살펴볼 차례입니다. 스프링은 매우 많은 기능을 제공하지만, 핵심은 바로 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것입니다. 애플리케이션 컨텍스트는 앞서 구현했던 DaoFactory를 일반화한 것입니다. DaoFactory가 설계도의 역할로 오브젝트 생성과 관계 주입의 책임을 가졌던 것처럼 애플리케이션 컨텍스트도 스프링 환경에서 동일한 책임을 가집니다.
스프링에서는 제어의 역전이 적용된 오브젝트를 빈이라 부릅니다.
스프링 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리(Bean Factory)라고 부릅니다. 하지만 보통은 빈 팩토리를 확장한 애플리케이션 컨텍스트를 주로 사용합니다.
애플리케이션 컨텍스트는 별도의 정보(설정정보)를 참고해서 빈을 생성하고 관계설정 등의 작업을 합니다. 애플리케이션 컨텍스트는 직접 설정정보를 담고 있지 않고, 별도의 설정정보 클래스를 가져와 이를 활용하게 됩니다.
이때 설정정보는 자바 클래스 또는 XML 파일로 만들어 애플리케이션 컨텍스트에게 주입해 줄 수 있습니다.
스프링에서 애플리케이션 컨텍스트와 그 설정정보는 설계도로서 컴포넌트의 생성과 관계설정의 책임을 가집니다.
우리가 이전에 애플리케이션의 로직을 담고 있는 컴포넌트와 설계도 역할을 하는 팩토리를 구분했습니다. 이때 설계도 역할을 하던 DaoFactory가 애플리케이션 컨텍스트가 주입받는 설정정보입니다. 물론 스프링이 DaoFactory가 설정정보 클래스임을 인식하게 위해 애노테이션을 추가해야 합니다.
기존 DaoFactory를 스프링 애플리케이션 컨텍스트의 설정정보로 만들어봅시다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
...
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new ConnectionMaker();
}
}
스프링의 빈 팩토리가 사용할 수 있는 설정정보를 위해서는
- 클래스에 @Configuration 애노테이션을 붙여야 합니다.
- 그리고 오브젝트를 만들어주는 메소드에는 @Bean 애노테이션을 붙여야 합니다.
이제 DaoFactory를 설정정보로 사용하는 애플리케이션 컨텍스트를 만들어봅시다. DaoFactory는 단지 설정정보 클래스일 뿐이고 이를 사용하는 IoC 오브젝트인 애플리케이션 컨텍스트를 만들어야 합니다.
애플리케이션 컨텍스트는 ApplicationContext 타입의 오브젝트이고 ApplicationContext를 구현한 클래스는 여러 가지가 있습니다. 이 중에서 @Configuration이 붙은 자바 클래스를 설정정보로 사용하려면 AnnotationConfigApplicationContext를 이용하면 됩니다. 아래 코드로 살펴봅시다.
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ApplicationContext context = new AnnntationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
...
}
}
위 코드처럼 AnnotationConfigApplicationContext을 생성할 때 생성자로 설정정보 클래스를 주입하면 애플리케이션 컨텍스트를 생성할 수 있습니다. 이제 ApplicationContext의 getBean()이라는 메소드를 이용해 UserDao의 오브젝트를 가져올 수 있습니다.
오히려 기존 오브젝트 팩토리보다 추가해야 할 애노테이션도 더 많은데... 굳이 스프링을 사용하지 않고 그냥 DaoFactory 같은 오브젝트 팩토리를 만들어 사용하면 되지 않을까?라고 생각할 수 있습니다.
하지만 스프링은 일반적인 오브젝트 팩토리에서 얻을 수 없는 방대한 기능과 활용 방법을 제공합니다.
기존에 오브젝트 팩토리를 이용했던 방식과 스프링의 애플리케이션 컨텍스트를 사용한 방식의 차이를 살펴봅시다.
앞에서 이야기했듯이 오브젝트 팩토리에 대응되는 것이 스프링의 애플리케이션 컨텍스트입니다.
애플리케이션 컨텍스트가 동작하는 방식은 아래 그림과 같습니다.
애플리케이션 컨텍스트는 DaoFactory 클래스를 설정정보로 등록해 두고 @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만들어둡니다.
DaoFactory를 오브젝트 팩토리로 직접 사용했을 때와 비교해서 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점은 크게 세 가지가 있습니다.
먼저 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없습니다. 애플리케이션이 발전하면 DaoFactory와 같은 IoC를 적용한 오브젝트도 계속 추가될 것입니다. 때문에 클라이언트가 필요한 오브젝트를 가져오려면 어떤 팩토리 클래스를 사용해야 하는지 알아야 합니다. 하지만 애플리케이션 컨텍스트를 사용하면 오브젝트 팩토리가 아무리 많아져도 이를 클라이언트에서 알아야 하거나 직접 사용할 필요가 없습니다. 단지 애플리케이션 컨텍스트를 이용하면 일관된 방식으로 원하는 오브젝트를 가져올 수 있습니다. 실제로 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 여러 개가 만들어져 사용됩니다. 애플리케이션 컨텍스트를 스프링 컨테이너 또는 스프링이라고 부를 때도 있습니다.
두 번째 장점은 애플리케이션 컨텍스트가 종합 IoC 서비스를 제공해 준다는 점입니다.
애플리케이션 컨텍스트는 오브젝트를 만드는 방식, 시점과 전략을 관리하고 자동생성, 오브젝트에 대한 후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 활용할 수 있는 다양한 기능을 제공합니다.
마지막으로 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공합니다.
애플리케이션 컨텍스트가 제공하는 getBean() 메소드는 빈의 이름이나 타입으로 빈을 찾아줍니다.
싱글톤 레지스트리와 오브젝트 스코프
스프링 빈은 애플리케이션 컨텍스트에서 싱글톤으로 만들어집니다.
그래서 애플리케이션 컨텍스트는 싱글톤 레지스트리이기도 합니다. 스프링은 대부분 서버환경에서 사용되기 때문에 매번 클라이언트의 요청에 오브젝트를 새로 생성해서 반환하면 큰 부하가 따릅니다. 그래서 별다른 설정을 하지 않으면 스프링 빈은 싱글톤으로 생성됩니다.
싱글톤 패턴은 피해야 할 패턴이라는 말을 자주 들었을 것입니다. 맞습니다. 싱글톤 패턴은 여러 한계를 가집니다.
싱글톤 패턴의 한계를 일부 나열해 보겠습니다.
- private 생성자를 갖고 있기 때문에 상속할 수 없습니다.
- 싱글톤은 테스트하기가 어렵거나 아예 불가능합니다.
- 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못합니다. 클래스 로더의 구성 여부에 따라 달라집니다.
- 싱글톤의 사용은 전역(static) 상태를 만들 수 있기 때문에 바람직하지 못합니다.
하지만 만약 싱글톤 패턴이 가진 한계는 피할 수 있고 싱글톤 패턴이 가진 장점만 얻을 수 있다면 굉장히 좋을 것입니다.
스프링은 IoC 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해 주는 싱글톤 레지스트입니다. 스프링 컨테이너에서 생성된 빈은 싱글톤이지만, 우리가 작성한 코드에는 싱글톤의 흔적이 전혀 없습니다. 그래서 싱글톤의 한계점도 우리 코드에 나타나지 않게 됩니다.
싱글톤 레지스트리인 스프링 컨테이너를 통해 고전적인 싱글톤 패턴에 드러나는 스태틱 메소드와 private 생성자를 사용하지 않고 평범한 자바 클래스를 싱글톤으로 활용할 수 있습니다.
물론 스프링 빈이 싱글톤이기 때문에 주의해야 할 부분이 있습니다.
싱글톤인 스프링 빈은 멀티스레드 환경에서 여러 스레드가 동시에 접근해서 사용할 수 있습니다. 따라서 스프링 빈의 상태 관리에 주의해야 합니다. 인스턴스 변수를 가진 상태가 있는 스프링 빈을 싱글톤으로 활용할 경우 멀티스레드 환경에서 저장된 값의 동기화 문제가 발생할 수 있습니다. 그래서 싱글톤 스프링 빈의 경우 상태정보를 내부에 갖고 있지 않는 무상태(stateless) 방식으로 만들어야 합니다.
그렇다면 상태가 없는 방식으로 클래스를 만드는 경우에 각 요청에 대한 정보나, DB나 서버의 리소스로부터 생성한 정보는 어떻게 전달해야 할까요?
바로 파라미터와 로컬변수, 리턴 값을 이용하면 됩니다.
이들은 매번 새로운 값을 저장할 독립적인 공간이기 때문에 싱글톤이더라도 여러 스레드가 변수의 값을 덮어쓸 일이 없습니다.
// 무상태 UserDao
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
// 상태를 가진 UserDao
public class UserDao {
// 초기 생성자에서 설정하면 사용 중에는 바뀌지 않는 읽기전용 인스턴스 변수
private ConnectionMaker connectionMaker;
// 매번 새로운 값으로 바뀌는 정보를 담은 인스턴스 변수로 심각한 문제가 발생한다.
private Connection c;
private User user;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public User get(String id) throws ClassNotFoundException, SQLException {
this.c = connectionMaker.makeConnection();
...
this.user = new User();
this.user.setId(re.getString("id"));
...
return this.user;
}
}
위 코드를 보면 사용 중에 바뀌지 않는 읽기 전용 인스턴스 변수는 상태를 갖지 않습니다. 하지만 매번 새로운 값으로 바뀌는 상태를 갖는 인스턴스 변수는 싱글톤 환경에서 큰 문제를 일으킵니다. 당연히 상태를 갖는 인스턴스 변수는 스프링 빈에 사용해서는 안됩니다.
또한 UserDao의 connectionMaker 인스턴스 변수도 단순한 읽기 전용 값임을 나타내는 게 좋습니다.
아래 코드와 같이 static final이나 final로 읽기 전용 인스턴스를 선언합시다.
private final ConnectionMaker connectionMaker;
private static final ConnectionMaker connectionMaker;
'기술 서적 > 토비의 스프링 3.1' 카테고리의 다른 글
1장 오브젝트와 의존관계(2) DAO의 확장, 제어의 역전(IoC) (0) | 2024.10.20 |
---|---|
1장 오브젝트와 의존관계(1) 초난감 DAO, DAO의 분리 (0) | 2024.10.20 |