본문 바로가기
Study/SpringBoot

[Spring] 애플리케이션 컨텍스트

by 검프 2021. 5. 13.

애플리케이션 컨텍스트

오브젝트 팩토리(직접 설정 정보를 넣어주는 것, DaoFactory)에 대응하는 것이 스프링의 애플리케이션 컨텍스트예요. 스프링에서는 이 애플리케이션 컨텍스트를 IoC컨테이너라 하기도 하고, 간단히 스프링 컨테이너라고 부르기도 해요. 또는 빈 팩토리라고 부르기도 해요.

ApplicationContext는 빈 팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로, 같이 봐도 괜찮아요.

https://user-images.githubusercontent.com/48986787/118065377-d3f01800-b3d7-11eb-86d7-ec950a91f5d1.png

특정 클래스를 설정정보를 등록하고, @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만들어줘요. 애플리케이션 컨텍스트의 getBean() 메소드를 호출하면 자신의 빈 목록에서 요청한 이름이 있는지 찾고, 있다면 빈을 생성하는 메소드를 호출해서 오브젝트를 생성시킨 후 클라이언트에게 돌려줘요.

장점

클라이언트는 구체적인 팩토리 클래스를 알 필요가 없어요.

애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해줘요.

애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공해요.

 

스프링 IoC의 용어 정리

빈(Bean)

빈 또는 빈 오브젝트는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이에요. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니라는 사실이에요. 그중에서 스프링이 직접 생성과 제어를 담당하는 오브젝트만을 빈이라고 불러요.

빈 팩토리(Bean Factory)

스프링의 IoC를 담당하는 핵심 컨테이너를 말해요.

보통은 이를 바로 사용하지 않고 애플리케이션 컨텍스트를 이용해요.

빈의 생성과 제어에 관점에서 얘기할 때 불러요

애플리케이션 컨텍스트

빈 팩토리를 확장한 IoC 컨테이너예요. 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일해요. 여기에 스프링이 제공하는 각종 부가 서비스를 추가로 제공해요.

스프링의 제공하는 애플리케이션 지원 기능을 모두 포함해서 이야기 할때 불러요

설정정보/설정 메타정보

애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말해요.

IoC컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용돼요.

컨테이너 또는 IoC 컨테이너

IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트빈 팩토리컨테이너 또는 IoC컨테이너라고 해요.

컨테이너라는 말 자체가 IoC의 개념을 담고 있기 때문에 이름이 긴 애플리케이션 컨텍스트 대신에 스프링 컨테이너라고도 해요

스프링 프레임워크

IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용해요

 

싱글톤 레지스트리

애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리기도 해요

싱글톤 패턴을 적용했지만, 싱글톤 패턴의 단점들을 많이 보완했어요.

즉, 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴 등을 적용하는데 아무런 제약이 없어요.

사용이유

매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다면, 서버가 감당하기 힘들어요. 그래서 서비스 오브젝트(서블릿과 같은)를 만들어 여러 요청이 들어와도 하나의 오브젝트를 반환하게 했어요

평범한 자바 클래스라도 IoC방식의 컨테이너를 사용해서 생성관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있어요. 오브젝트 생성, 제어에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에 있기 때문이에요.

주의점

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 없어요.

그렇기 때문에 기본적으로 무상태 방식으로 만들어져야해요. 즉, 수정가능한 인스턴스 변수(상태)를 두지 않아야해요. 더 나아가 변수를 로컬 변수로 저장하거나, 파라미터로 주고받으면서 사용하게 해야해요.

자신이 사용하는 다른 싱글톤 빈을 저장하려는 용도라면 인스턴스 변수를 사용해도 돼요. 스프링이 한 번 초기화 해주고 나면 이후에는 수정되지 않기 때문에 멀티스레드 환경에서 사용해도 문제가 없기 때문이에요

 

스프링 빈의 스코프

스프링 빈의 디폴트 스코프는 싱글톤이에요.

싱글톤 스코프는 컨테이너 내에서 한 개의 오브젝트만 만들어져서, 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지돼요.,

경우에 따라서는 싱글톤 외에 스코프를 가질 수 있어요. 대표적으로 프로토타입(protoTyep) 스코프가 있어요.

 

의존관계 주입(DI)

제어의 역전(IoC)과 의존관계 주입(DI)

IoC는 소프트웨어에서 자주 발견할 수 있는 일반적인 개념이에요. 객체지향적인 설계나, 디자인 패턴, 컨테이너에서 동작하는 서버 기술을 사용한다면 자연스럽게 IoC를 적용하거나 그 원리로 동작하는 기술을 사용할 수 있어요.

IoC는 용어가 매우 느슨하기에 스프링이 제공하는 IoC방식을 의존관계 주입(Dependency Injection)이라는 좀더 의도가 명확히 드러나는 이름을 사용하기 시작했어요.

물론 스프링이 컨테이너고 프레임워크니 기본적인 동작원리가 모두 IoC이예요

하지만 스프링이 여타 프레임워크와 차별화돼서 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명하게 드러나요

의존관계

class A {
    private B b;
}

class B {

}

의존한다는 것은 의존대상, 여기서는 B가 변하면 그것이 A에 영향을 미친다는 뜻이에요. B의 기능이 추가되거나 변경되거나, 형식이 바뀌거나 하면 그 영향이 A로 전달되는 것이에요.

이렇게 사용의 관계에 있는 경우에 A와 B는 의존관계가 있다고 말해요.

여기서 중요한 것은 A는 B에 의존하지만, B는 A에 의존하지 않아요. 이 말은 B는 A의 변화에 영향을 받지 않는다는 뜻이에요.

의존관계는 컴파일타임 의존관계와, 실행 시점에 결정되는 런타임 의존관계 둘을 분리 해서 생각해야 해요.

컴파일타임 의존관계

코드상에서 드러나는 의존관계에요.

그림으로 살펴보면, UserService는 UserRepository 인터페이스에 의존하고 있어요.

https://user-images.githubusercontent.com/48986787/118071474-d0ae5980-b3e2-11eb-86e9-a09e140fee53.png

class UserService {
    private final UserRepository userRepository;

    UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

interface UserRepository {

}

class UserRepositoryImpl implements UserRepository{

}

코드상에서는 위와 같이 되어있어요.

이렇게 코드상으로 드러나는 의존관계를 컴파일타임 의존관계라 불러요.

하지만 이상하지 않나요?

UserService의 인자로 들어오는 userRepository의 구현체가 뭔지 모르는데, 협력에는 아무런 제약이 걸리지 않아요.

이때 필요한 것이 런타임 의존관계에요

런타임 의존관계

UserService는 UserRepository에만 직접 의존해요. UserRepositoryImpl 클래스의 존재도 알지 못해요.

좀더 얘기하면, 인터페이스를 통해 설계 시점에 느슨한 의존관계를 갖는 경우에 UserService 오브젝트가 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든 것인지 미리 알 수 없어요.

프로그램이 시작되고 UserService가 만들어지고 나서 런타임 시에 의존 관계를 맺는 대상, 즉 실제 사용대상인 오브젝트를 의존 오브젝트라 말해요.

의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말해요

의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제 3의 존재가 있다는 것이에요. 이는 DI이고, 관계설정 책임을 가진 코드를 분리해서 만들어진 오브젝트에요.

의존관계 주입 예제

class UserService {
    private final UserRepository userRepository;

    UserService() {
        this.userRepository = new UserRepositoryImpl();
    }
}

interface UserRepository {

}

class UserRepositoryImpl implements UserRepository{

}

이 코드에 따르면 UserServie는 설계 시점에 이미 UserRepositoryImp이라는 구체적인 존재를 알고 있어요.

따라서 모델링 때의 의존관계 뿐아니라, 런타임 의존관계를 UserService가 결정하고 관리하고 있는거에요(제어의 흐름이 코드에게 있어요)

이때 IoC를 사용하여 제 3의 존재(DI)에게 런타임 의존관계 결정 권한을 넘겨요

class UserService {
    private final UserRepository userRepository;

    UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

interface UserRepository {

}

class UserRepositoryImpl implements UserRepository{

}

처음 코드와 같아졌죠?

3의 존재(DI)는 런타임 시점에 UserServcie가 사용할 UserRepository 타입의 오브젝트를 결정하고, 이를 생성한 후, UserService의 생성자 파라미터로 주입(UserRepositoryImpl의 레퍼런스(참조 값)를 넘겨줌)해서, UserService가 UserRepositoryImpl의 오브젝트와 런타임 의존관계를 맺게 해줘요.

 

이처럼 DI는 1. 두 오브젝트 사이의 런타임 의존관계를 설정해주는 의존관계 주입 작업을 주도하는 존재이며, 2. IoC방식으로 오브젝트의 생성과 초기화, 제공 등의 작업을 수행하는 컨테이너에요.

댓글