스프링 어플리케이션 컨텍스트와 스프링 빈

ideafy·2024년 12월 5일

스프링

목록 보기
2/4

스프링은 어플리케이션의 흐름에 관한 제어권을 가져 개발자가 객체를 직접 관리하지 않고 스프링의 어플리케이션 컨텍스트에서 객체의 생성과 소멸까지의 생명주기를 관리해준다.

어플리케이션의 흐름에 관한 제어권을 가진다

이 말을 들으면 어떤 느낌인지는 알겠지만, 정확히 무엇을 뜻하는지는 딱봐서 알긴 어렵다.
개발자가 흐름을 관리하는 전통적인 방식의 예를 먼저 살펴보자.

public class MainApp {
    public static void main(String[] args) {
        UserController controller = new UserController();
        controller.handleRequest();
    }
}

위 코드에서는 UserController라는 클래스가 있고 new 연산자로 객체를 생성한다.
다음 라인에서 요청을 처리하는 메소드가 실행된다.

이런 전통적인 개발 방식에서는 개발자가 직접 프로그램의 흐름을 조정할 수가 있기 때문에 실수로 인한 오류가 발생할 가능성이 있다.
예를 들어 한 개발자가 실수로 new 연산자를 이용해서 객체를 생성하지도 않고 메소드를 호출하는 경우를 생각해보자. 물론 대부분의 개발자는 정상적인 흐름을 갖고 개발해 나갈 수 있겠지만, 분명한 것은 개발자가 실수를 유발할 요소가 존재한다는 것이다.

더 나아가서 개발자가 수동으로 의존성을 관리하면 의존성이 변경될 때 코드 전반에서 수정이 필요하게 된다. 유지보수성을 떨어트리고 개발자가 불필요한 코드 중복이나 실수를 유발하게 될 가능성이 높다.

그래서 스프링 프레임워크는 제어의 역전(IoC)를 통해 개발자가 직접적으로 객체 생성이나 의존성 주입, 요청처리 같은 어플리케이션의 흐름을 관리하지 않고 안정적으로 객체들을 관리할 수 있는 방법을 제시해주었다.


제어의 역전(IoC)

개발자가 어플리케이션의 흐름을 관리하는 전통적인 방식과 달리 스프링 프레임워크가 직접 어플리케이션의 흐름에 관여하는 제어권을 갖는다. 이때 제어권이란 앞서 설명했듯이 객체의 생성이나 소멸 등의 생명주기와 더불어 의존성 주입, 요청처리를 의미한다.

IoC 관점에서 개발자는 코드의 흐름이나 객체의 생성과 관련된 코드를 작성하지 않고 프레임워크가 제공하는 정의 방식에 의해서 코드를 정의만 한다.

프레임워크는 개발자가 정의한 코드만을 보고 객체를 생성하고 코드가 동작하는 순서를 결정하여 실행한다.

코드로 예시를 들어보겠다.

@RestController
public class UserController {
    @GetMapping("/users")
    public String getUsers() {
        return "List of users";
    }
}

컨트롤러에서 "/users"에 대한 get 요청을 처리하는 간단한 코드이다.
어플리케이션이 실행되면 사용자는 "/users"에 접속하면 "List of users"라는 값을 받아볼 수가 있는 것이다.

근데 생각해보면 뭔가 이상하지 않은가?
url과 반환되는 문자열이 정의돼서 대충 이렇겠구나라는 감은 잡히지만, 문자열을 출력해주는 메소드도 없고 더군다나 UserController라는 클래스의 getUsers() 메소드가 실행되는 것 같은데 기존에 개발자가 new 연산자로 해주던 객체를 생성하는 코드도 보이지 않는다.
다만 눈에 띄는 것은 @가 붙은 어노테이션들이 각각 클래스와 메소드 위에 붙어있다는 것이다.

그러면 우리는 자연스럽게 이 어노테이션들이 객체의 생성과 메소드의 호출이 관련되어 있다는 것을 유추해 볼 수 있다.

그리고 이것이 앞서 말한 개발자가 코드로 작성하는 정의이고 이 정의를 보고 스프링이 자동으로 객체를 생성하고 순서대로 동작하는 작업을 수행하는 제어의 역전이 일어난 것을 확인할 수 있는 상황이다.


Bean Factory

스프링 프레임워크의 핵심 컨테이너 중 하나이며 스프링의 스펙이다.
BeanFactory는 스프링 IoC 컨테이너의 가장 기본적인 인터페이스로, 의 생성 및 관리를 담당한다.

Bean
스프링이 제어권을 가져가서 직접 생성하고 의존관계를 부여하는 객체를 의미한다.

스프링은 이를 통해 의존성 주입(DI)를 구현하며, 스프링 어플리케이션에서 객체의 생명주기와 구성을 관리하는 중요한 역할을 한다.
BeanFactory는 내부적으로 Lazy-Loading 방식을 사용해서 빈이 필요할 때 빈을 로딩한다고 한다.

BeanFactory의 기능에서 확장된 기능을 제공하는 ApplicationContext가 있다.

ApplicationContext

스프링 어플리케이션의 중앙 인터페이스로, 애플리케이션의 설정 정보를 관리한다. 또 BeanFactory 기능을 확장하여, 애플리케이션 이벤트 처리, 리소스 로딩, 메시지 지원 등의 기능을 제공한다.

내부적으로는 Eager-Loading 방식을 사용하여 어플리케이션 실행 시 모든 빈을 미리 로딩시켜놓는다.

BeanFactory는 lazy-loading 방식을 사용하는데, ApplicationContext는 왜 eager-loading 방식을 사용할까?

lazy-loading의 경우 빈이 필요한 시점에 빈을 로딩하므로 의존성 주입, 빈 정의 설정, 빈 간의 순환 의존성 등의 에러가 발생할 가능성이 있다. 반면에 eager-loading 방식을 사용하면 어플리케이션 실행 중 필요한 빈이 이미 생성되어 있어 런타임 중 예외가 발생할 가능성을 줄이고 안정적인 상태를 보장한다.

따라서 초기 로딩속도는 느릴지라도 어플리케이션 실행 중 빈 관련 에러를 사전에 방지할 수 있고 AOP, 트랜잭션등 BeanFactory에 비해서 고급기능을 제공하므로 대부분의 스프링 어플리케이션에서는 ApplicationContext를 사용한다.

하지만 BeanFactory와 ApplicationContext의 주요 기능은 빈을 생성하고 관리하는 것이므로 혼용해서 쓰일 때가 있는데, BeanFactory라고 말을 할때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것이다.

ApplicationContext라고 말을 할때는 별도의 정보를 참고해서 빈의 생성, 관계 설정 등의 제어의 총괄에 초점을 맞춘 것이다.

그리고 이 빈들 중에서 스프링 IoC 컨테이너에서 관리되는 객체는 SpringBean이라고 한다.

SpringBean

SpringBean은 JavaBean과 이름만 비슷할 뿐 전혀 다른 개념이다.
JavaBean은 재사용 가능한 컴포넌트로, 기본 생성자를 가지며, getter와 setter 메소드를 통해 프로퍼티에 접근할 수 있는 단순한 POJO(Plain Old Java Object)이다.

Spring 프레임워크에서는 애플리케이션의 객체들을 Bean으로 등록하고, 이들의 생성, 생명 주기, 의존성 관리 등을 컨테이너가 담당한다.

생성자 주입, setter 주입, field 주입으로 의존성 주입이 이루워지고, 이를 통해 느슨한 결합과 코드 재사용성, 테스트 용이성 등의 이점을 얻을 수 있다.

Spring 에서 중요하게 관리하는 객체로 이해하면 된다.

SpringBean 총 6개의 스코프를 가지는데 singleton(기본), prototype, request, session, websocket, application이 있다.

기본 스코프가 Singleton인 이유는 일단 한 번만 생성할 수 있는 싱글톤 객체를 재사용함으로써 얻는 효율성과 한 번 생성된 싱글톤 객체를 스프링이 관리하게 되기 때문에 DI의 기본 철학과 일치하는 방식이기 때문이다.

SpringBean을 등록하는 방법

  1. @Configuration + @Bean
  2. @Component

두 가지 방법이 있는데 @Component 방식은 @Service, @Controller 등 스테레오 타입으로 많이 쓰인다.

profile
재밌게 공부하고 싶어요

0개의 댓글