kimyu0218
  • [spring] 스프링 컨텍스트
    2024년 11월 10일 14시 16분 59초에 업로드 된 글입니다.
    작성자: @kimyu0218

    스프링

    프레임워크는 어플리케이션을 개발하는 데 사용하는 기본 기능을 제공하는 소프트웨어 집합이다. 스프링은 자바 어플리케이션을 위한 프레임워크다.

    • 스프링 코어 :  스프링의 기본 기능을 제공하고, 대표적으로 스프링이 앱의 인스턴스를 관리할 수 있도록 컨텍스트를 제공한다.
    • 스프링 MVC
    • 스프링 데이터 액세스
    • 스프링 테스팅

     
    스프링은 IoC를 원칙으로 동작한다. 즉, 스프링에게 제어 권한을 위임한다. 우리는 Configuration을 작성하여 스프링에 제어를 지시한다. 이렇게 스프링에게 제어 권한을 위임하여 인스턴스를 관리하게 되면 AOP를 통해 인스턴스의 메서드를 가로치는 것도 가능하다.

    📁 스프링 프로젝트 구조
    • `src` 폴더
      • `main` 폴더 : 어플리케이션 소스 코드를 저장하는 곳으로, 자바 코드는 `java`, 구성 정보는 `resources` 폴더에 저장한다.
      • `test` 폴더 : 어플리케이션 테스트 코드를 저장한다.
    • `build.gradle` : 어플리케이션의 종속성을 관리한다.

     

    스프링 컨텍스트

    컨텍스트는 일종의 메모리 공간으로, 스프링이 관리하는 인스턴스를 저장한다. 스프링은 컨텍스트의 인스턴스를 어플리케이션에 연결한다. 이러한 인스턴스를 빈이라고 부른다.
     

    컨텍스트에 빈을 추가하는 방법은 크게 세 가지다.

    1. `@Bean` 어노테이션 사용
    2. 스테레오 타입* 어노테이션 사용
    3. 프로그래밍
    *스테레오 타입 : 컴포넌트 스캔을 통해 빈을 자동으로 등록할 수 있다. 대표적으로 `@Component`, `@Controller`, `@Service`, `@Repository` 등이 있다.

     
    빈을 추가하는 방법을 예제와 함께 자세히 살펴보자. 여기서는 `@Bean`과 스테레오 타입 어노테이션을 사용한 방법만 다룰 것이다.
     
    `@Bean` 어노테이션을 사용하여 빈을 등록하기 위해서는 1) 구성 클래스를 정의하고, 2) 구성 클래스에 객체 인스턴스를 반환하는 메서드를 추가한 후 3) 메서드에 `@Bean` 어노테이션을 추가해야 한다.

    @Configuration // 1. 구성 클래스 정의
    public class ApplicationConfig {
    
        @Bean // 3. 빈 어노테이션 추가
        Member member() { // 2. 객체 인스턴스를 반환하는 메서드 추가
        	Member m = new Member();
            m.setName("Alice");
            return m;
        }
    }
    🚨 일반적으로 메서드는 동작을 나타내므로 동사를 지향하나 빈을 추가하는 경우는 예외적으로 이 규칙을 따르지 않는다.

    스프링은 타입과 이름을 기반으로 빈을 찾는다. 만약 타입이 같은 빈이 여러 개 존재한다면 `NoUniqueBeanDefinitionException`이 발생한다.

    org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type '[BEAN_CLASS]' available: expected single matching but found [N]: ...

    같은 타입의 빈이 여러 개 존재할 경우, 빈 이름으로 주입 받을 빈을 선택할 수 있다. 스프링은 기본적으로 `@Bean`이 달린 메서드 이름을 빈 이름으로 사용한다. 다른 이름을 지정하고 싶은 경우에는 `@Bean("[ALIAS]")`를 이용하여 이름을 변경할 수 있다.

    💡 컨텍스트에 동일한 타입의 빈이 여러 개 있을 때 그 중 하나를 `@Primary`를 사용하여 기본 빈으로 만들 수 있다. 기본 빈은 같은 타입의 빈이 여러 개 있고, 주입 받을 빈의 이름을 지정하지 않았을 때 기본으로 주입 받을 빈을 의미한다.

     
    스테레오 타입을 이용하여 빈을 등록하기 위해서는 1) 컨텍스트에 추가할 클래스 앞에 `@Component` 애너테이션을 붙이고, 2) 구성 클래스 위에 `@ComponentScan` 어노테이션으로 컴포넌트를 어디에서 찾을 수 있는지 지시한다.

    @Component // 1. 컨텍스트에 추가할 클래스 앞에 스테레오 타입 어노테이션 추가
    public class Member {
    	...
    }
    
    @ComponentScan(basePackages="[BASE_PACKAGE_PATH]") // 2. 스테레오 타입의 빈을 찾을 수 있는 위치 표시
    @Configuration
    public class ApplicationConfig {
    }

     
    지금부터 빈 간 관계를 설정해보자. 관계를 설정하는 방법은 세 가지가 있다.

    1. 빈에서 다른 빈을 생성하는 메서드를 직접 호출하여 의존성을 주입한다. (와이어링)
    2. 빈 어노테이션이 붙은 메서드에 매개변수를 이용하여 의존성을 주입한다. (오토 와이어링)
    3. `@Autowired` 어노테이션을 이용하여 의존성을 주입한다. (오토와이어링, IoC 기반 DI)

    1번과 2번의 경우 코드를 함께 간단하게 알아보자.

    // 1. 메서드 직접 호출
    @Configuration
    public class Application {
    	@Bean
        public Apple apple() {
        	Apple a = new Apple();
            return a;
        }
        
        @Bean
        public Member member() {
        	Member m = new Member();
            m.setApple(apple()); // member 빈에서 apple 빈 메서드 호출
            return m;
        }
    }
    // 2. 메서드 매개변수 이용
    @Configuration
    public class ApplicationConfig {
    	@Bean
        public Apple apple() {
        	Apple a = new Apple();
            return a;
        }
        
        @Bean
        public Member member(Apple apple) { // 메서드 매개변수에 빈 전달
        	Member m = new Member();
            m.setApple(apple);
            return m;
        }
    }

     
    `@Autowired` 어노테이션은 빈 관계를 설정할 때 가장 많이 사용되는 방법이다. 필드, 생성자, setter 메서드에 사용하여 스프링이 자동으로 의존성을 주입하도록 한다.
     
    하지만 필드 주입과 setter 주입은 지양되는 편이다.`final` 키워드를 사용할 수 없어 객체의 불변성을 보장할 수 없기 때문이다. 또한, 대부분의 빈은 싱글톤 스코프로, 동일한 싱글톤 빈을 여러 객체가 참조하게 된다. 이때, 외부에서 의존성을 변경할 수 있다면 경쟁 상태를 초래할 위험이 있고, 이는 동시성 문제로 이어질 수 있다.

    💡 필드 주입은 리플렉션을 이용하여 객체 생성 후에 의존성을 주입한다. 리플렉션은 프로그램이 실행되는 동안 자신의 구조를 수정할 수 있는 기능이다. 따라서 필드 주입 시에 `final` 키워드를 사용할 수 없다.
    // 3. @Autowired 어노테이션을 이용한 의존성 주입 (생성자 주입)
    @Component
    public class Member {
    	private Apple apple;
        
        @Autowired // 생성자가 하나만 있는 경우, @Autowired 어노테이션을 생략할 수 있다
        public Member(Apple apple) {
        	this.apple = apple;
        }
    }

    스프링이 어플리케이션 객체의 의존성을 관리하도록 하는 것은 편리하지만 순환 의존성이 발생할 가능성이 있다. 순환 의존성은 서로 다른 빈이 서로를 의존하는 경우를 의미한다.

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
    Error creating bean with name '[BEAN]':
    Requested bean is currently in creation: Is there an unresolvable circular reference?

    참고자료

    댓글