Post

01 Spring Dependency Injection

01 Spring Dependency Injection

1. DI

1. 의존성

  • 어떤 클래스가 다른 클래스(객체)의 기능을 사용해야 할 때 “의존성이 있다.”
  • Car가 Tire 객체를 사용해야 굴러갈 수 있다면, Car는 Tire에 의존
    1
    2
    3
    
     public class Car {
        private Tire tire = new KoreaTire(); // Car는 KoreaTire에 의존
     }
    

    2. 의존성 주입(Dependency Injection)

  • 의존성을 외부(스프링 컨테이너 등)에서 넣어주는 방식
  • 객체가 스스로 필요한 의존성을 만들지 않고, 외부에서 “주입”받음.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     public class Car {
       private Tire tire;
        
         // 생성자 주입 받음
         public Car(Tire tire) {
             this.tire = tire;
         }
        
         public void drive() {
             System.out.println("Driving with " + tire.getBrand());
         }
     }
    
    1
    2
    3
    4
    5
    
     <!-- XMl방식으로 스프링컨테이너에서 주입해줌. -->
     <bean id="koreaTire" class="com.example.KoreaTire"/>
     <bean id="car" class="com.example.Car">
         <constructor-arg ref="koreaTire"/>
     </bean>
    
    • 기존에는 new를 통해서 개발자가 직접 무엇을 할지 결정해서했으나, 외부에서 주입을 하게됨.
    • 개발자는 인터페이스 또는 흐름을 가지고 어떤 객체가 들어와야한다까지 파악함.

3. note

  • 프레임워크에서 대신 주입한다고 해서, 개발자가 무엇을 주입할지 전혀 모르는 것은 아님.
  • 전반적인 로직의 흐름을 파악해야하고, 사용될 빈들을 등록해야하고, 빈들이 매칭될수 있게 네이밍 규칙등을 지켜야함.
  • 단순히 연결 자체를 프레임워크에서 진행함.
  • 따라서 클래스가 바뀌거나 로직이 변경되는 등의 상황이 발생했을 때,
  • 개발자가 작성하는 클래스 내부에 특정 클래스를 지칭하는 것이 없기 때문에,
  • 프레임워크의 규칙에 따라서 빈의 이름과 매칭만 잘해주면 문제가 없게됨.

2. Bean의 등록

1. Bean

  • 스프링 컨테이너(IoC 컨테이너)가 생성하고 관리하는 객체를 스프링에서는 “ Bean “
  • 일반 자바 객체지만 스프링이 관리하면 그 순간부터 “Bean”

2. Bean의 특징

  • 생명주기 관리: 객체 생성 → 의존성 주입 → 초기화 → 소멸까지 컨테이너가 관리
  • 의존성 관리: 다른 Bean과 연결(주입)하는 역할 수행
  • 싱글톤 기본 전략: 기본적으로 컨테이너 내에서 하나의 Bean 인스턴스만 공유 (필요 시 prototype 등 다른 스코프 가능)

3. Bean의 등록

1. xml

1
<bean id="car" class="com.example.Car"/>

2. java Config

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
    @Bean
    public Car car() { 
        return new Car();
    }
}
// 빈이름은 car가 됨 => 메서드기준

3.어노테이션

  • 빈 생성
    1
    2
    3
    4
    5
    6
    7
    
      @Component
      public class Car { }
      // 일반적으로 아무 이름을 지정하지 않으면 클래스명에서 첫 글자를 소문자로 바꾼 것이 빈 이름
        
      @Component("myCar")
      public class Car { }
      //이경우에는 myCar가됨.
    
  • 종류
어노테이션용도빈 이름 기본 규칙설명
@Component일반 빈클래스명 → 첫 글자 소문자가장 일반적인 컴포넌트 등록용 어노테이션
@Service서비스 계층 빈클래스명 → 첫 글자 소문자비즈니스 로직 구현 클래스에 주로 사용, 기능적 의미 부여
@RepositoryDAO / 데이터 접근 빈클래스명 → 첫 글자 소문자DB 관련 예외 변환 처리 자동 적용
@Controller웹 컨트롤러 빈클래스명 → 첫 글자 소문자MVC 패턴에서 요청 처리 담당
@RestControllerREST API 컨트롤러 빈클래스명 → 첫 글자 소문자@Controller + @ResponseBody 합친 것
@Configuration설정 클래스 빈클래스명 → 첫 글자 소문자@Bean 정의를 포함할 수 있는 설정용 클래스
@Bean자바 Config에서 빈 등록메서드명@Configuration 클래스 내에서 사용, 메서드 반환 객체를 빈으로 등록

4. note

  • ApplicationContext
    1
    2
    3
    4
    5
    
      ApplicationContext context = 
              new ClassPathXmlApplicationContext("beans.xml");
        
      Car car = context.getBean("car", Car.class);
      car.drive();    
    
    • ApplicationContext는 스프링 IoC 컨테이너의 구현체 중 하나로, 빈 생성과 관리, 의존성 주입을 담당
    • 필요한 경우가 생기면 직접 끄집어내서 사용하기도 함.
  • 자바빈 스프링빈
구분자바빈스프링빈
정의자바 규약에 따른 객체스프링 컨테이너가 관리하는 객체
관리 주체개발자가 직접 new스프링 IoC 컨테이너
규칙기본 생성자, getter/setter특별한 규약 없음 (단, 컨테이너 등록 필요)
목적데이터 캡슐화, 컴포넌트화의존성 관리, 객체 생명주기 관리

3. 빈의 주입

1. 생성자 주입(Constructor Injection)

  • 생성자를 통해 의존성을 주입
  • 불변성 보장 (final 필드 가능)
  • 필수 의존성을 강제할 수 있음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      @Component
      public class Car {
        private final Tire tire;
        
        @Autowired
        public Car(Tire tire) {
          this.tire = tire;
        }
      }
    

    2. 세터 주입(Setter Injection)

  • 세터 메서드를 통해 의존성을 주입
  • 선택적 의존성 주입에 유리
  • 필수 의존성을 강제할 수 없음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     @Component
     public class Car {
         private Tire tire;
        
         @Autowired
         public void setTire(Tire tire) {
             this.tire = tire;
         }
     }
    

    3. 필드 주입(Field Injection)

  • 필드에 직접 @Autowired 붙여서 주입
  • 코드 간결
  • 테스트 어렵고, 순환 의존성 문제 발생 가능 → 권장되지 않음
    1
    2
    3
    4
    5
    
     @Component
     public class Car {
         @Autowired
         private Tire tire;
     }
    

4. 주입을 바꾸는 케이스

1. 주입을 바꾸는 케이스

  • 주입을 바꿔야 하는 상황 → 거의 선택적 의존성, 테스트, 런타임 환경 변화, 전략 교체 정도
  • 대부분은 생성자 주입으로 불변성을 보장하는 것이 안전
  • 바꿔야 할 필요가 있는 경우만 세터 주입 활용

2. note

  • 컨테이너가 아닌 개발자가 관리하는 형태가됨.
구분직접 주입자동 주입
객체 생성개발자가 new스프링 컨테이너
의존성 연결개발자가 setter/생성자 호출스프링이 @Autowired, @Bean 등으로 연결
장점자유롭게 런타임 변경 가능코드 간결, 유지보수 용이
단점관리 번거로움런타임에 임의 변경 어려움

3. 케이스

1. 테스트용 Mock 교체

1
2
3
4
5
6
@Test
public void testDrive() {
    Car car = new Car();
    car.setTire(new MockTire()); // 세터 주입으로 테스트용 객체 교체
    car.drive();
}

2. 특정 기능이 활성화될 때만 의존성을 주입하고 싶을 때

1
2
3
4
5
if(userWantsPremiumFeature) {
    car.setNavigation(new AdvancedNavigation());
} else {
    car.setNavigation(new BasicNavigation());
}

3. 다국어, 지역별 설정, 또는 환경 변수에 따라 런타임 시 다른 Bean 선택

1
2
3
4
5
if(region.equals("US")) {
    car.setTire(new AmericanTire());
} else {
    car.setTire(new KoreaTire());
}
This post is licensed under CC BY 4.0 by the author.