Spring

[Spring] Java 기반 컨테이너, Component Scan, 의존 관계 주입 방법

jungha_k 2022. 10. 18. 14:35
 

[Spring] 스프링 컨테이너(Spring Container), 빈(Bean), 빈스코프(Bean Scope) 란? +싱글톤(Singleton) 스코프

스프링 컨테이너 Spring Container = 내부에 존재하는 애플리케이션 빈의 생명주기를 관리 (= Bean 생성, 관리, 제거 등의 역할) * 스프링 프레임워크의 핵심 컴포넌트! 스프링 컨테이너는 무엇인가? = Ap

wlikeoxy.tistory.com

에 대한 추가 공부


 

개발자의 상황에 따라서 빈을 등록하는 방법이 달라지는데

정말 개략적인 설명은 

 

수동으로 빈을 등록 - @Configuration, @Bean

자동으로 빈을 등록 - @ComponentScan

 

이라 말 할 수 있다.

 


@Bean and @Configuration

: 자바 기반 설정의 가장 중요한 어노테이션

 

메서드가 Spring Container 내에서 관리할 새 객체를 인스턴스화, 구성 및 초기화 하는 것을 나타내는데에 사용된다.

수동으로 스프링 컨테이너에 빈을 등록하는 방법!

 

(* Spring Container? : 자바 객체(=Bean) 들을 관리해줌, BeanFactory와 ApplicationContext 가 있다.)

 

 

 

@Bean 어노테이션을 통해 수동으로 스프링 컨테이너에 빈 등록

 

ex)

// DependencyConfig 클래스
컨텍스트를 인스턴스화할 때
@Configuration
public class DependencyConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

 

설정 클래스에 @Configuration 을 붙여준다.

(@Configuration = 해당 객체가 bean definitions의 소스임을 나타내줌)

스프링 컨테이너는 @Configuration 클래스를 찾아서 @Bean이 있는 모든 메소드를 찾아서 빈을 생성해준다.

(사실상 @Configuration과 @Bean 은 짝꿍! 싱글톤을 보장받을 수 있기 때문이다.)

 


Java 코드에서 애너테이션을 사용해 Spring 컨테이너를 구성하는 방법

: @import 어노테이션

 

@Configuration
public class DependencyConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(DependencyConfigA.class)
public class DependencyConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

@import(어쩌구.class)를 통해 다른 구성 클래스에서 @Bean definitions 를 가져올 수 있다.

 

 


⬆ 위의 방법들 :
스프링 빈 등록할때 자바코드의 @Bean 으로

설정 정보에 등록할 스프링 빈들을 직접 작성하는 방식

 

 

➡ 수작업 등록시 설정 정보 커지고, 누락하는 등 문제 발생할 수 有


@ComponentScan 

: 설정 정보 없이 자동으로 스프링 빈 등록!

 

@Component가 붙은 모든 클래스를 자동으로 스프링 빈으로 등록해줌

설정정보에 붙여 준다.

(@Component 와 @ComponentScan 이 짝궁)

 

 

ex)

package com.codestates.section2week4;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan
public class AutoDependencyConfig {

}

* @ComponentScan 사용 시 @Component 뿐만이 아닌

@Configuration이 붙은 설정 정보도 자동으로 등록된다.

 

@Configuration 에 @Component이 붙어있기 때문!

@Controller, @Service, @Repository@Component 를 포함함 

 

 

* @Autowired : 생성사 의존성 주입에 필요한 설정 정보 대신 의존관계 자동 주입을 해줌

 


@ComponentScan 스캔 대상

: 하위 클래스들의 소스 코드에는 @Component를 포함하고 있다.

따라서 @ComponentScan 했을 때 함께 자동으로 찾아서 빈 등록해줌

 

 

  • @Component
  • @Controller & @RestController : MVC 컨트롤러
  • @Service : 핵심 비즈니스 로직
  • @Repository : 데이터 접근 계층
  • @Configuration : 스프링 설정 정보

다양한 의존관계 주입(DI) 방법

 

  • 생성자 주입
  • 수정자 주입 (setter 주입)
  • 필드 주입
  • 일반 메서드 주입

 

 

생성자 주입

: 생성자를 통해 의존 관계 주입

 

생성자에 @Autowired를 하면 

@Component로 등록된 빈에서 / 생성자에 필요한 빈들을 / 스프링 컨테이너에 주입

 

  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장
  • 불변과 필수 의존 관계에 사용됨
  • 생성자가 1개만 존재하는 경우에는 @Autowired를 생략해도 자동 주입 됨
  • NullPointerException 을 방지할 수 있다
  • 주입받을 필드를 final  로 선언 가능

ex) 

@Component
public class CoffeeService {
  private final MemberRepository memberRepository;
  private final CoffeeRepository coffeeRepository;

  @Autowired
  public OrderServiceImpl(MemberRepository memberRepository, CoffeeRepository coffeeRepository) {
    this.memberRepository = memberRepository;
    this.coffeeRepository = coffeeRepository;
  }
}

 

수정자(setter) 주입 

 

생성자 대신 set필드명 메서드를 생성하여 의존 관계 주입 

@Component
public class CoffeeService {
  private final MemberRepository memberRepository;
  private final CoffeeRepository coffeeRepository;

  @Autowired
  public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }

  @Autowired
  public void setCoffeeRepository(CoffeeRepository coffeeRepository) {
    this.coffeeRepository = coffeeRepository;
  }
}

 

주입 순서 : 

@Component 붙은 클래스를 스프링 빈으로 등록

@Autowired 있는 것들을 자동으로 주입 

 


필드 주입

: 필드에 @Autowired 붙여서 바로 주입

 

ex)

@Component
public class CoffeeService {
  @Autowired
  private final MemberRepository memberRepository;
  @Autowired
  private final CoffeeRepository coffeeRepository;
}

코드가 간결하지만, 외부에서 변경이 불가 - 테스트하기 힘들어짐

 


일반 메서드 주입 

 

: 일반 메서드를 사용해 주입(그것이.. 일반 메서드 주입이니까)

 

 


과거에는 수정자, 필드 주입을 많이 사용했지만

최근에는 대부분 생성자 주입 사용이 권장된다!

 

 

생성자 주입 사용 이유

 

1. 불변

의존 관계 주입 = 애플리케이션 실행 ~  종료 까지 변경되어서는 안됨!

 

생성자 주입 : 객체 생성시 최초 1번만 호출되고 다시는 호출되는 일이 없다

불변하게 설계할 수 있음 

(* 수정자 주입 : public 으로 열어두어 변경이 가능함.. 좋지 않다!)

 

 

2. 누락

 

생성자 주입 사용시 주입 데이터 누락시 컴파일 오류가 발생

NPE 에러 방지 가능 (Null Point Exception)

 

 

3. final 키워드 사용 가능 

 

생성자 주입 사용시 필드에 final 키워드 사용 가능

(나머지 방법들은 생성자 이후에 호출되는 형태.. final 키워드 사용 불가능)

 

 

4. 순환참조

 

순환 참조 방지 할 수 있음

(* 순환 참조 : 여러 컴포넌트 간에 의존성 有 (A➡B, B➡C, C➡A..)

 

순환 참조 시 앱 구동이 실패하게 됨