DI 처리 - 명시적 DI

박준서·2024년 10월 16일
8

Web

목록 보기
7/16
post-thumbnail

Spring Framework는 DI를 어떻게 처리할까 ?

  1. 개발자는 비즈니스 로직을 담은 Bean 들을 POJO 형태로 작성한다.
  2. 개발자는 각 Bean들을 어떻게 생성하고 has-a 관계를 맺어줄지에 대한 설명서인 메타정보를 작성해서 전달한다.
  3. Spring Container는 메타정보에 근거해 Bean 객체를 만들고, Setter / 생성자를 호출해서 의존성을 주입한다.
  4. 클라이언트 코드에서 Container에게 Bean을 요청한다.
  5. 요청에 따라 Container는 관리하고 있던 Bean 객체를 반환한다.

“DI를 처리하는 방법은 메타 정보를 만드는 방식에 따라 명시적 DI와 묵시적 DI로 나뉜다. ”

명시적 DI

  • Bean을 생성하고 의존성을 주입하는 코드를 별도의 파일에 “이것은 Bean 입니다.” 라고 작성하는 것이다.
  • 처리하는 방법은 다음과 같다.
    • XML Config
    • Java Config
  • 최근 Spring Boot로 넘어오면서는 거의 Java Config를 사용한다고 한다.

📌 명시적 DI를 위해서는 @Configuration@Bean 이라는 annotation이 사용된다.

@Configuration

  • Java Config를 이용해서 메타 정보를 설정하기 위해서는 클래스에 @Configuration 애너테이션을 선언해주어야 한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
  • @Configuration 의 @Target은 ElementType.TYPE이다. Target은 말 그대로 어디다가 적용할 것인지를 가리키는 것이다.
    • JAVA에서 TYPE은 Class 이다. 즉, @Configuration은 클래스 기반 Annotation 인 것이다.

@Bean

  • @Configuration 클래스에 Bean을 선언하기 위해 사용하는 Annotation 이다.
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
	@AliasFor("name")
	String[] value() default {};
}
  • @Bean은 메서드 레벨에서 사용하는 Annotation 이다. 이 때, 해당하는 메서드의 이름은 Bean의 이름이 된다. 이름을 바꾸려는 경우에는 @Bean의 name(==value) 속성을 이용할 수 있다.
@Configuration // 이것은 설정 파일 입니다. 
public class PhoneConfig {
	
	@Bean // 빈 생성 선언
	public LPhone lPhone() { //LPhone 타입의 Bean 생성, Bean 이름은 lPhone
		return new LPhone(); // 실제로 사용될 Bean 객체 반환
	}
	
	@Bean(name = "mysPhone") // 빈 객체 선언 : 빈의 이름은 mysPhone
	public Sphone sphone() {
		return new Sphone();
	}
	
	@Bean
	public PhoneUser phoneUser() {
		PhoneUser user = new PhoneUser(lPhone()); // 생성자를 통해 의존성인 phone을 주입 
		return user;
	}
}

@SpringBootApplication

  • 위 예시에서 명시적인 Bean 등록을 위해서 @Configuration이 들어간 클래스를 만들고 작업 했는데, 사실 그렇게 하지 않아도 된다.
  • 최초 프로젝트를 생성할 때 main 메서드를 가지고 생성된 시작 클래스를 살펴보면 @SpringBootApplication이 설정되어 있는데 이 것은 내부적으로 @SpringBootConfiguration을 가지고 있다. 또 이것이 다시 @Configuration을 가지고 있다.
  • 따라서 Config를 따로 생성하지 않고 직접 이 클래스에 @Bean을 설정해도 무방하다. 하지만 많은 설정들이 작성되면 복잡도가 높아지기 때문에 따로 설정파일을 만들고 분산하는 것이 좋다.

Bean 객체 관리

  • Spring Container에 생성된 빈을 요청하고 어떻게 관리되는지 보자. Spring Container에 Bean을 요청할 때 getBean 메서드를 사용한다.
  • getBean : 타입 기반으로 관리하는 Bean을 반환한다.
    • Spring Container의 기본적인 빈 관리 방식
  • Bean으로 만든 대상은 주로 무상태 (stateless)한 객체이다. Spring Container은 이런 Bean들을 위해 별도로 Singleton Design Pattern을 적용하지 않아도, 자동으로 하나의 객체만 만들어서 재사용 한다.
	@Test
	@DisplayName("spring은 bean을 singleton을 관리하는지 테스트")
	public void singleTonTest() {
		// given
		//when
		PhoneUser user = ctx.getBean(PhoneUser.class);
		LPhone phone = ctx.getBean(LPhone.class);
		//then
		Assertions.assertSame(user.getPhone(), phone);
	}
  • 위 테스트 코드에서 같은 객체임을 확인 할 수 있다.

Type 기반의 Bean 관리

  • 위에서 말했듯이, Spring Container는 Bean을 Type 기반으로 관리한다.
	@Test
	@DisplayName("Spring container는 기본적으로 타입 기반으로 빈 관리")
	public void getPhoneTestByType() {
		Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ctx.getBean(SmartPhone.class));
		
		SmartPhone phone = ctx.getBean(SmartPhone.class);
	}
  • 위의 빈을 불러오는 코드는 실패한다.
    • 현재 SmartPhone 타입의 객체가 LPhone과 SPhone 2개가 있는데, 이 중 누구를 반환해야 할 지 Spring은 알 수 없기 때문이다.
    • Spring Container는 Bean을 Type 기반으로 관리하는데, 동일한 Type의 Bean이 여러 개 있다면 어떤 Bean을 원하는지 알 수 없다.
  • Type 이외에 Bean을 구분할 수 있는 요소가 필요한 데, 그것이 바로 name 이다.
	@Test
	@DisplayName("Spring container는 기본적으로 타입 기반으로 빈 관리")
	public void getPhoneTestByType() {
		Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ctx.getBean(SmartPhone.class));
		
		SmartPhone phone = ctx.getBean("lPhone", SmartPhone.class);
		Assertions.assertEquals(phone.getClass().getName(), LPhone.class.getName());
	}
  • 이렇게 이름 기반의 조건을 추가해서 호출한다. 위 코드에서 SmartPhone을 가져올 때, lPhone 이라는 빈의 name이 보조 수단으로 사용된다.
profile
Back-End Developer

0개의 댓글

관련 채용 정보