Post

스프링3 - 스프링 컨테이너

스프링 컨테이너

스프링 컨테이너란 의존성을 부여하고 제어하기 위해서 외부에서 사용하는 스프링 인터페이스이다. 스프링 컨테이너를 통해서 IOC(제어의 역전), DI(의존성 주입)이 가능해지며 객체지향에 유용한 코드를 작성할 수 있게 해준다.

1
2
3
4
5
6
7
8
9
10
@Component
public class MemberServiceImpl implements MemberService{

    private MemberRepository memberRepository;	//인터페이스
    
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

위와 같이 MemberRepository 인터페이스를 생성자로 받아오는 클래스가 있다.

생성자 파라미터로 왜 구현체가 아닌 인터페이스를 넣는 것일까? 첫번째 이유는 바로 다형성이다.
외부에서 MemberServiceImpl을 호출할 때, 생성자 파라미터로 원하는 인터페이스 구현체를 넣어 호출하는 것이 가능해지는 것이다. 이렇게 사용하면 자바의 다형성이 가져다주는 장점들을 유용하게 사용할 수 있다.
두번째는 구현체에 의존하지 않고 추상화에 의존하도록 하면서 의존성을 줄이는 것이다. 클래스에서 직접적으로 구현체에 의존하게 되면 구현체의 기능이 변경됐을 때 오류가 발생하게 될 확률이 높아진다. 또한 구현체에 직접 의존하고 있으면 향후에 유지보수 및 기능을 변경하는데 제약이 있을 수 있다.
따라서 추상화에 의존하도록 DIP(의존역전원칙)를 따르며 코드를 작성하는 것이 올바른 객체지향설계 방법이다.

그렇다면 MemberServiceImpl 클래스를 외부에서는 어떻게 호출하고 제어해야 제대로 사용할 수 있을까? 그 역할을 해주는 것이 바로 스프링 컨테이너다.

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() { return new MemberServiceImpl(memberRepository()); }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

스프링 컨테이너 를 사용하기 전에 컨테이너에서 사용할 을 지정해주는 DI컨테이너의 역할을 알아야만 한다. 위와 같이 @Bean 애노테이션을 MeberServiceMemberRepository 메서드에 달아주면 스프링 빈이 등록되고, 스프링 컨테이너에서 빈을 호출하면 구현체가 return된다. memberService 빈을 호출하면 MemberServiceImpl 클래스가 참조객체로 호출된다. 여기에 생성자 파라미터로 memberRepository 빈도 함께 호출되어 MemoryMemberRepository 구현체가 파라미터로 들어가게 된다. 이것이 외부에서 의존성을 주입하는 DI컨테이너이다.

클래스에 @Configuration 애노테이션을 선언해주면 해당 클래스의 빈들은 스프링 컨테이너에서 관리되고, 빈에서 반환되는 클래스는 싱글톤으로 관리된다. 싱글톤 패턴에 대해서는 다음 글에서 설명하도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MemberApp {

    public static void main(String[] args) {
    
    //스프링 컨테이너를 사용 안한 기존방식
    AppConfig appConfig = new AppConfig();
    MemberService memberService = appConfig.memberService();
    
    //스프링 컨테이너를 사용하는 방식
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

    Member member = new Member(1L, "test1");
    memberService.join(member);
    
    }
}

위의 코드에서 ApplicationContext 인터페이스가 바로 스프링 컨테이너라고 할 수 있다.
스프링 컨테이너를 호출할 때 위의 DI컨테이너로 사용한 클래스(AppConfig)를 파라미터로 등록하여 호출해주면 DI컨테이너에 선언된 빈들이 자동으로 스프링 컨테이너의 빈 저장소에 등록된다. 기존 방식이라면 AppConfig 클래스를 참조객체로 호출하여 MemberServiceImpl 클래스를 호출한다.
그러나 이 방식으로 하게되면 스프링 컨테이너를 사용하는 것이 아니기 때문에 싱글톤 패턴을 사용하지 못한다. 즉, 호출시마다 서로 다른 주소의 객체가 생성되는 것이다. 그러나 스프링 컨테이너(ApplicationContext)를 통해 호출하게 되면 등록된 빈들을 getBean 메서드를 사용하여 자유롭게 호출할 수 있고, 호출된 빈의 return 구현체들은 싱글톤 패턴이 적용된다.
싱글톤이 적용되면 요청시마다 새로운 주소의 MemberServiceImpl 클래스가 생성되는 것이 아니라 스프링 컨테이너에 한번 등록된 클래스를 공유하는 방식으로 사용할 수 있다. 이러한 방식은 불필요한 메모리 낭비를 막을 수 있기 때문에 효과적이다.
그러나 모든 사용자들이 하나의 클래스를 공유하는 것이기 때문에, 클래스 내부에 값이 변경될 수 있는 상태 변수들은 주의해서 사용해야 한다.

이처럼 스프링 컨테이너를 활용하여 외부에서 클래스를 직접 제어하게 되고, memberService 빈만 호출하여도 MemoryMemberRepository 구현체가 의존성 주입되어 MemberServiceImpl 클래스를 호출할 수 있다. 상황에 맞게 파라미터로 들어갈 구현체 클래스들을 빈으로 설정하여 불러와 사용할 수 있다. 또한 스프링 컨테이너로 호출된 클래스들은 모두 싱글톤 패턴이 적용되어 공유하여 사용할 수 있는 장점이 있다.

This post is licensed under CC BY 4.0 by the author.