본문 바로가기
Spring Boot, JPA

스프링 - Bean 등록(자동, 수동)과 Lite vs Full mode

by suhsein 2024. 11. 12.
728x90

스프링 빈의 등록

자동 등록 - @Component

빈의 자동등록은 컴포넌트 스캔을 거쳐서 이루어진다.
클래스 상단에 @Component 어노테이션이 있으면 컴포넌트 스캔을 거쳐서 스프링 빈으로 자동등록 된다.

 

이 밖에도 @Component를 포함하는 @Controller, @Service, @Repository도 자동등록된다. 생성자 위에 @Autowired 어노테이션을 붙여서 의존관계 주입이 가능하고, 해당 클래스의 생성자가 하나 뿐이라면 @Autowired는 생략 가능하다.

 

예를 들어 @RestController는 @RequestMapping에 @Responsebody를 추가한 기능을 한다. 그럼에도 @RequestMapping + @Responsebody 와 @RestController 사이에는 차이가 존재한다. 바로 빈의 자동등록이 되는가와 되지 않는가의 차이이다.

 

@Controller, @Service, @Repository 등의 어노테이션을 붙여서 컨트롤러, 서비스, 리포지토리 등을 자동등록하는 이유는 싱글톤 패턴에 있다.

 

빈으로 등록하면, 싱글톤으로 관리가 되어서 객체가 여러번 생성되는 것을 막아서 메모리 공간의 낭비를 줄일 수 있기 때문이다.

 

빈의 자동등록에서는 빈 이름을 해당 클래스의 첫글자만 소문자로 바꾼 것으로 한다.

ex) JellyFish -> jellyFish

 

빈의 중복된 자동 등록을 하게 되는 경우 ConflictingBeanDefinitionException 예외가 발생한다.

수동 등록 - @Bean

어떤 인터페이스의 구현체를 지정하는 등의 이유로 수동 등록을 사용할 수 있다. 수동 등록은 수동 등록 메서드를 통해서 이루어진다. 또한 수동 등록을 위한 Configuration 클래스를 따로 생성할 수도 있다.

@Bean

수동 등록을 하는 빈들은 @Component 대신 @Bean을 붙인다.


수동 등록 시에는 메서드 명으로 빈 이름을 지정하고, 빈 이름은 겹치지 않도록 만들어야 한다. 자동 등록된 빈에 대하여 수동 등록을 하게 되는 경우, 예외는 발생하지 않는다. 다만 수동 등록된 빈이 우선권을 가지게 된다.

 

public class FooBarConfig {
    @Bean
    public FooBar fooBar(){
        return new FooBarBaz();
    }
}
// 빈 이름은 메서드 명을 따라서 fooBar가 됨

 

@Bean은 보통 단독으로만 사용되지 않고, @Configuration과 함께 사용된다.

 

공식 문서에서는 @Bean을 단독으로 사용하는 경우를 lite mode라고 부르고, @Configuration이 붙은 클래스 내에서 @Bean을 사용하는 경우를 full mode라고 부른다.

@Configuration

클래스 상단에 @Configuration 어노테이션을 붙여서 Configuration 파일로 지정할 수 있다. @Configuration 어노테이션에는 @Component 어노테이션이 포함되어 컴포넌트 스캔의 대상이 된다.

@Configuration
public class FooBarConfig {
    @Bean
    public FooBar fooBar(){
        return new FooBarBaz();
    }
}

// FooBar is an interface
// FooBarBaz implements FooBar

왜 @Configuration을 사용하는가? Lite mode vs. Full mode

앞에서 @Bean만 사용하는 경우를 lite mode, @Configuration을 함께 사용하는 경우를 full mode라고 한다고 했다. 그런데 왜 @Configuration을 사용하는 것이 권장될까?

 

lite mode에서는 빈 간의 종속성 선언을 할 수 없다.

 

예를 들어 서비스와 리포지토리를 각각 빈으로 등록할 때, 서비스가 리포지토리에 종속되어 있는 경우를 종속성이라고 할 수 있다.

 

서비스 빈 메서드에서는 매개변수로 리포지토리를 필요로 하기 때문에 리포지토리 빈 메서드를 실행하게 된다.

public class MyConfig {
    @Bean
    public MyController myController() {
       return new MyControllerImpl(myService());
    }
    @Bean
    public MyService myService() {
       return new MyServiceImpl(myRepository());
    }
    @Bean
    public MyRepository myRepository() {
       return new MyRepositoryImpl();
    }
}
// controller -> service -> repository 종속
// 빈 간의 종속성 선언을 위해서 매개변수로 다른 빈 메서드를 호출하고 있다. 

 

위와 같은 상황에서 full mode는 CGLIB를 통한 프록시 객체를 생성한다. 그리고 캐싱을 통해서 이미 생성된 빈이 있는지 확인하는 과정을 거친다.

 

그런데 lite mode는 다른 빈의 메서드를 호출하게 될 때 프록시를 사용하지 않고 표준 자바 호출을 사용하게 된다. 즉, 싱글톤을 보장하지 않는 것이다.

 

그러므로 스프링 컨테이너를 통한 자원 관리를 위해서 full mode를 사용하는 것이 권장된다.

출처 https://docs.spring.io/spring-framework/reference/core/beans/java/basic-concepts.html

@Import

의도적으로 Configuration 클래스를 컴포넌트 스캔 대상 패키지 밖으로 빼어 컴포넌트 스캔에서 제외시킬 수 있다.

빈으로 여러 개의 Configuration 클래스가 모두 Bean으로 등록되는 것을 원치 않는 경우 이런 방법을 사용한다.

 

이 때 특정 Configuration 파일만 Bean으로 등록하는 방법이 바로 @Import를 사용하는 것이다.

application 클래스에서 @Import 어노테이션으로 Configuration 클래스를 명시한다.

// Application Class

/**
* scanBasePackage를 지정하면, 그 이하의 패키지만 컴포넌트 스캔
* FooBarConfig.class가 hello.spring.app 밖의 패키지에 존재하는 경우
**/

@SpringBootApplication(scanBasePackages = "hello.spring.app")
@Import(FooBarConfig.class)
// 여러 Config 클래스를 동시에 import 하는 경우 파라미터로 배열
// -> @Import({FooConfig.class, BarConfig.class})
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
728x90