[Spring IoC] ApplicationContext와 BeanFactory

IoC 컨테이너란?

IoC(Inversion of Control, 제어의 역전) 컨테이너는 객체의 생성과 생명 주기를 개발자가 직접 관리하는 것이 아니라, Spring이 대신 관리하는 컨테이너다.

💡 즉, 개발자가 new 키워드를 사용해서 객체를 직접 만들지 않고, Spring이 대신 만들어서 주입해 준다.
💡 Spring에서는 BeanFactoryApplicationContext가 IoC 컨테이너 역할을 한다.

tip. 이로 인해 new 를 사용 안하게 되고 어떤 이점이 있는걸까?

new 사용시 직접 구현체를 명시해야된다. 그렇게 되면 구현체가 변경될 때마다 해당서비스에서 구현체를 바꿔줘야된다. 결합도가 높아진다.

// new 사용시
class SampleClass{
    private SampleInterface sampleInterface;

    public SampleClass(){
        // SampleService2로 바뀔 경우 코드의 변경이 필요 하다.
        this.sampleInterface = new SampleService1();
    }
}

// IoC 사용시
class SampleClass{
    private SampleInterface sampleInterface;

    public SampleClass(SampleInterface sampleInterface){
        this.sampleInterface = sampleInterface;
    }
}

ApplicationContext, BeanFactory

Spring에서는 IoC 컨테이너를 구현한 주요 인터페이스인 BeanFactory와 ApplicationContext가 있다.

BeanFactory (IoC 컨테이너의 가장 기본적인 구현체)

  • 가장 기본적인 IoC 컨테이너로, 빈을 등록하고 조회하는 최소한의 기능만 제공.
  • Lazy Loading (지연 로딩) 방식 → 필요할 때 빈을 생성.
  • 자동 DI(@Autowired) 지원 X → 직접 getBean()을 호출해야 함.
  • 대부분의 Spring Boot 프로젝트에서는 사용하지 않음.
  • 하지만 Spring 내부에서는 여전히 핵심적인 역할을 함.
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
MyInterface myService = factory.getBean(MyInterface.class);

ApplicationContext (BeanFactory의 확장판)

  • BeanFactory를 확장한 IoC 컨테이너로, 더 많은 기능을 제공.
  • Eager Loading (즉시 로딩) 방식 → 애플리케이션 시작 시 빈을 미리 생성.
  • 자동 DI(@Autowired) 지원.
  • AOP, 이벤트 리스너, 메시지 소스 등 다양한 기능 포함.
  • Spring Boot에서는 기본적으로 ApplicationContext를 사용함.
  • 대부분의 Spring 애플리케이션에서 IoC 컨테이너로 사용됨.
1. ApplicationContext는 언제부터 등장했을까?
   ✅ 사실 ApplicationContext는 Spring 1.0 (2004년)부터 존재했어.
   ✅ 하지만, Spring 2.x까지는 BeanFactory를 더 많이 사용했기 때문에 익숙하지 않을 수도 있어.
   ✅ Spring 2.x에서는 "경량 컨테이너" 개념 때문에 BeanFactory를 직접 사용하는 경우가 많았음.

2. 왜 BeanFactory 대신 ApplicationContext가 등장했을까?
   Spring 2.x에서 ApplicationContext가 점점 더 많이 사용되기 시작했어. 이유는:

        1) AOP 지원 → ApplicationContext는 AOP와 같은 고급 기능을 지원.
        2) 자동 DI 지원 → @Autowired 같은 기능을 제공.
        3) 이벤트 리스너 지원 → ApplicationContext는 이벤트 기반 프로그래밍을 지원.
        4) 국제화(i18n) 지원 → 다국어 메시지 관리 가능.
        5) BeanFactory보다 사용성이 좋음 → BeanFactory는 getBean()을 일일이 호출해야 했음.

💡 결국 Spring 3.x부터 BeanFactory를 직접 쓰는 경우가 줄어들었고, 대신 ApplicationContext가 IoC 컨테이너의 표준이 됐다
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
특징BeanFactoryApplicationContext
IoC 컨테이너 역할✅ 기본적인 DI 컨테이너✅ 고급 기능 포함
빈 등록/조회getBean() 가능getBean() 가능
Lazy Loading✅ 기본적으로 Lazy❌ 기본적으로 Eager
DI 자동 주입 (@Autowired)❌ 직접 수동 주입 필요✅ 자동 주입 지원
AOP 지원❌ 직접 구현해야 함✅ 자동 지원
이벤트 시스템❌ 지원 안 함✅ 지원
애플리케이션 실행 속도✅ 빠름 (필요할 때 빈 생성)❌ 상대적으로 느림 (미리 로딩)
Spring Boot 기본 컨테이너❌ 거의 안 씀✅ 기본 IoC 컨테이너

tip. ApplicationContext는 결국 BeanFactory를 확장한 개념이다. BeanFactory는 빈을 사용 즉시 생성하는 반면, ApplicationContext는 의존성 주입 과정에서 내부적으로 BeanFactory와 리플렉션을 사용하여 빈을 관리한다.

싱글톤 빈의 공유 문제

싱글톤 빈이 여러 요청(사용자) 간에 공유된다는 점이 장점이지만, 동시성 문제를 조심해야 한다. 필드 변수를 상태값으로 사용하면 위험하다.

@Service
public class SingletonService {
    private String user; // 공유 필드

    public void processRequest(String user) {
        this.user = user; // 사용자 정보 저장
        System.out.println("현재 사용자: " + this.user);
    }
}

📌 스프링에서 빈을 등록하는 주요 어노테이션

Spring에서 빈을 등록할 때 사용하는 대표적인 어노테이션들을 정리해보자!
이 어노테이션들은 Spring IoC 컨테이너(ApplicationContext)에 자동으로 빈을 등록하는 역할을 한다.

1. 주요 컴포넌트 스캔 어노테이션

💡 Spring이 자동으로 감지(@ComponentScan)해서 빈을 등록하는 어노테이션
💡 @Component를 확장한 어노테이션들이 많음!

어노테이션설명
@Component가장 기본적인 빈 등록 어노테이션 (클래스 단위)
@Service서비스 레이어 빈 등록 (비즈니스 로직 담당)
@Repository데이터 액세스 레이어 빈 등록 (DAO, Repository 계층)
@Controller웹 컨트롤러 빈 등록 (Spring MVC에서 사용)
@RestController@Controller + @ResponseBody (REST API 컨트롤러)

📌 @Component (기본적인 빈 등록)

@ComponentSpring에서 자동 감지하여 빈으로 등록하는 가장 기본적인 어노테이션이다.
서비스, DAO, 컨트롤러 등에서 특정한 역할을 부여하는 어노테이션(@Service, @Repository 등)이 있지만,
그게 아니어도 빈으로 등록하고 싶다면 @Component를 사용하면 된다.

@Component
public class MyComponent {
    public void doSomething() {
        System.out.println("Component Bean 실행");
    }
}

📌 @Service (비즈니스 로직 계층)

@Service비즈니스 로직을 담당하는 클래스에 사용한다.
💡 @Component와 기능적으로 동일하지만, 의미적으로 “서비스 레이어”임을 명확히 표현하는 용도이다.

@Service
public class UserService {
    public String getUser() {
        return "User Found!";
    }
}

📌 @Repository (데이터 액세스 계층)

@Repository데이터베이스 액세스를 담당하는 DAO 클래스에 사용한다.
💡 @Component와 기능적으로 동일하지만, Spring이 특정 예외(예: SQLException)를 DataAccessException으로 변환하도록 지원함.

@Repository
public class UserRepository {
    public String findUser() {
        return "User Data";
    }
}

2. 수동 빈 등록 관련 어노테이션

💡 @Component 기반이 아니라, 직접 빈을 수동으로 등록하는 어노테이션
💡 @Bean은 Java 기반 설정에서 사용하고, @Configuration은 설정 클래스를 의미한다.

어노테이션설명
@Bean수동으로 빈을 등록 (메서드 단위)
@Configuration설정 클래스를 정의 (Java 기반 설정)
@ComponentScan@Component 어노테이션이 붙은 클래스를 자동 검색

📌 @Bean (메서드 단위로 빈 등록)

💡 @BeanXML 설정 없이 자바 코드로 빈을 등록할 때 사용한다.
💡 @Component는 클래스에 붙이지만, @Bean메서드 단위로 등록하는 방식이다.
✅ @Bean은 클래스가 아니라, 메서드 단위에서 특정 객체를 Spring의 빈으로 등록하는 방식.
✅ @Component와 달리 클래스 자체가 아니라, 해당 메서드가 반환하는 객체를 빈으로 등록하는 것이 특징.

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}

@Bean이 붙은 myService() 메서드가 실행되어 반환하는 객체가 빈으로 등록된다.
즉, 클래스 자체(AppConfig)가 빈이 아니라, @Bean이 붙은 메서드의 반환값이 빈으로 등록되는 것!
ApplicationContext에서 getBean(UserService.class)로 조회 가능.

📌 @Configuration (Java 기반 설정 클래스)

💡 @Configuration설정 클래스를 만들 때 사용한다.
💡 내부에 @Bean을 포함하고, Spring이 빈으로 관리함.
💡 @Configuration을 사용하면 싱글톤이 보장된다. (Spring이 CGLIB 프록시를 사용하여 @Bean 메서드를 싱글톤으로 보장)

@Configuration
public class AppConfig {
    @Bean
    public UserRepository userRepository() {
        return new UserRepository();
    }

    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserService(userRepository);
    }
}

Spring Boot에서는 대부분 @Configuration을 사용하여 빈을 설정한다.

📌 이쯤에서.. 왜? DataSource 같은 것들은 yml 설정만 해도 자동으로 빈으로 등록이 되며, 또 @Configuration 을 사용한 커스터 마이징도 가능한 걸까?

yml 설정만으로 빈이 등록되는 이유

Spring Boot는 자동 설정(Auto Configuration)과 @EnableAutoConfiguration을 이용하여 DataSource, EntityManagerFactory 등의 빈을 자동으로 등록한다.
설정의 핵심은 spring-boot-starter-* 같은 스타터가 포함되어 있다는 점이다.

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class})
public class DataSourceAutoConfiguration {
    
    @Bean
    @Primary
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

Spring Boot는 @EnableAutoConfiguration을 사용하여 자동 설정 클래스를 실행한다.(@SpringBootApplication에는 @EnableAutoConfiguration이 포함되어 있다.)
이 설정이 @ConditionalOnMissingBean(DataSource.class) 조건을 가지므로, 사용자가 직접 @Bean으로 등록하면 자동 설정을 무시한다.
Spring Boot 스타터(spring-boot-starter-*)는 특정 기능을 쉽게 사용할 수 있도록 미리 구성된 의존성 패키지다.

@EnableAutoConfiguration이 동작하는 원리
  • @EnableAutoConfiguration이 활성화되면 클래스패스(Classpath)를 스캔한다.
  • spring.factories 파일을 읽어서 자동 설정 클래스 목록을 확인한다.
  • 조건(@ConditionalOnClass, @ConditionalOnMissingBean)에 따라 필요한 빈을 등록한다.
  • 자동 설정 파일(spring-boot-autoconfigure.jar) 내부
    • META-INF/spring.factories 파일을 보면 자동 설정 클래스 목록이 포함되어 있다.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
...

DataSourceAutoConfiguration → spring.datasource 설정이 있으면 자동으로 DataSource 빈을 등록한다.
✅ HibernateJpaAutoConfiguration → JPA 관련 설정이 감지되면 EntityManagerFactory를 설정한다.

특정 자동 설정 제외
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
}

📌 @ComponentScan (자동 스캔 범위 지정)

💡 @ComponentScan특정 패키지 내에서 @Component, @Service, @Repository, @Controller 등을 자동으로 찾아서 빈으로 등록함.

@Configuration
@ComponentScan(basePackages = "com.example.myapp")
public class AppConfig {
}

com.example.myapp 패키지 아래의 모든 @Component를 찾아서 빈으로 등록함.

3. 특정 기능을 위한 빈 등록 어노테이션

Spring에는 특정 기능을 위한 빈 등록 어노테이션도 있다.

어노테이션설명
@Scope빈의 스코프 설정 (싱글톤, 프로토타입 등)
@Lazy빈을 필요할 때만 생성 (지연 로딩)
@Primary동일한 타입의 여러 빈이 있을 때 기본값으로 지정
@Qualifier특정한 빈을 명시적으로 주입할 때 사용

📌 @Scope (빈의 라이프사이클 설정)

💡 빈의 스코프(생명 주기)를 설정할 때 사용한다. 기본적으로 singleton이지만, prototype으로 변경 가능.

@Component
@Scope("prototype") // 매번 새로운 객체 생성
public class PrototypeBean {
}
prototype 사용시 ObjectProvider 또는 @Lookup 필요

RequestLogger 컴포넌트에 prototype 스코프가 걸려 있고 이를 불러와 활용하는 예제

@Service
public class RequestService {
    private final ObjectProvider<RequestLogger> requestLoggerProvider;

    public RequestService(ObjectProvider<RequestLogger> requestLoggerProvider) {
        this.requestLoggerProvider = requestLoggerProvider;
    }

    public void logRequest() {
        RequestLogger requestLogger = requestLoggerProvider.getObject(); // ✅ 매번 새로운 객체 생성
        System.out.println("[RequestService] Logging with requestId: " + requestLogger.getRequestId());
    }
}
@Service
public class RequestService {

    public void logRequest() {
        RequestLogger requestLogger = getRequestLogger(); // ✅ 매번 새로운 객체 생성
        System.out.println("[RequestService] Logging with requestId: " + requestLogger.getRequestId());
    }

    @Lookup
    public RequestLogger getRequestLogger() {
        return null; // Spring이 이 메서드를 동적으로 구현하여 Prototype Bean을 반환함
    }
}

📌 @Lazy (필요할 때만 빈 생성)

💡 @Lazy를 사용하면 빈을 애플리케이션 시작 시 바로 생성하지 않고, 필요할 때만 생성한다.

@Component
@Lazy
public class LazyBean {
    public LazyBean() {
        System.out.println("LazyBean 생성됨");
    }
}

ApplicationContext.getBean(LazyBean.class)를 호출할 때만 빈이 생성됨.

여러 개의 같은 타입의 빈을 등록할 때 (@Primary, @Qualifier 활용)

@Configuration
public class PaymentConfig {

    @Bean
    @Primary
    public PaymentService creditCardPaymentService() {
        return new CreditCardPaymentService();
    }

    @Bean
    public PaymentService paypalPaymentService() {
        return new PayPalPaymentService();
    }
}
@Autowired
@Qualifier("paypalPaymentService")
private PaymentService paymentService;

📌 결론

🚀 Spring에서 빈을 등록하는 어노테이션은 @Component 계열(자동 등록)과 @Bean 계열(수동 등록)로 나뉜다.
🚀 대부분 @Service, @Repository, @Controller를 사용하고, 설정 클래스에서는 @Configuration@Bean을 사용한다.
🚀 Spring Boot에서는 @ComponentScan을 사용하여 자동으로 빈을 검색하고 등록한다.


© 2023 Lee. All rights reserved.