Backend Developer

[Spring] Bean 과 IOC Container의 의존성 주입

🤨Bean?

spring 개발을 하다보면 코드에서 Bean을 많이 볼 수 있다.
빈이란 추상적으로 말하자면 spring이 만들어주는 객체이다.

❓Spring이 객체를 만들어준다고?

우리가 보통 객체를 생성한다고 하면 어떻게 하는가?
예시를 들어 생각해보자. 예시는 저번 포스트에서 이어진다. ==> 보고 오자!

public interface Product {
	// void ~
}
public class Paper implements Product {
	//public void ~
}
public class Store{
	private Product product;

    public Store (Product product){
    	this.product = product;
    }

    //product 객체를 이용한 구현부
}
public class Main {
    public static void main(String[] args) {
        Product product = new Paper();  // product 객체를 주입
        Store store = new Store(product);
    }
}

보통 객체의 생성에 있어서는 사용자가 관리한다. 쉽게 new를 붙여서 객체 생성을 해야한다고 옛날의 java 수업을 기억해보자.

bean이 spring이 만든 객체라는 뜻은
spring이 객체를 생성해주고 또 초기화해주고 소멸시키는 통상 객체의 생체주기를 관여하는 행동이라고 불리는 행위에서의 객체에 해당하는 것이 바로 bean이다.

❓클래스(객체)가 bean인 건 어떻게 아는데?

스프링으로 간단한 프로젝트를 클론코딩이라도 해봤던 사람이라면 알만한 방법이다.
생각해보자. 우리는 서비스 객체, 컨트롤러 객체, 레포지토리 객체를 구현하고 실제로 웹이 돌아가는 걸 알면서도 의문을 품지 않았다. 혹은 품었더라고 넘어갔었다.
나는 이런 질문이 떠오른다.

이 컨트롤러 클래스는 도대체 어디서 만들어졌고 호출되거나 주입됐길래 알아서 돌아가는 거지??

그렇다. 우리가 넣었던 @Service @Repository @ Controller 혹은 @Component 어노테이션이 붙은 클래스들ㅇ bean이 되는 것이다. 스프링은 이들을 찾아내서 bean으로 등록한다.

bean 으로 등록된 객체는 spring에서 만들어주고 초기화해주는 것이다. 정확히는 spring IoC 컨테이너에서 해주는 것이다.

빈 등록을 그렇게 하는 거였다고?

bean에 대해서 찾아볼 때 빈을 등록해왔던 방법이 많이 바뀌었다는 것을 알 수 있었다.
본디 bean을 등록하려면 xml 설정 파일에 정의를 해야했었고 차츰 우리가 아는 어노테이션 기반 방법도 생겨났고 configuratoin 어노테이션을 활용해서 config 클래스 안에 bean을 등록하는 방법도 생겼다.

사실 이 부분에 대해서는 다음 포스트에서 자세히 다루려고 한다.
더 찾아봐야하기 때문에,,,,나는 아무것도 모르고 스프링을 한다고 했었나😭.

IoC 컨테이너가 말아주는 DI

그래서 bean 이야기를 왜 했냐?
바로 spring을 사용하면 쉽게 할 수 있는 의존성 주입에 대해서 설명하기 위해서였다. 의존성 주입에 대해서는 간단하게 정리한 포스트가 있었는데 방법론만 설명한 것이고 뿌리를 제대로 이야기 안 한 것 같아서이다.

스프링의 IoC 컨테이너는 객체의 생체주기를 관리해준다고 위에서 설명했었다. 과연 그뿐일까?

당연히 아니다. spring은 bean 객체에서 의존하는 객체의 의존성 주입에 대한 사용자의 역할도 뺐어가버린다.

public interface Product {
	// void ~
}
public class Paper implements Product {
	//public void ~
}
@Component
public class Store {
    private Product product;

    @Autowired
    public Store(Product product) {
        this.product = product;
    }

    public void sellProduct() {
        product.sell();
    }
}

자 게시물 초반의 코드와 비교해서 바뀐 점을 찾아보자.
어노테이션이 추가되지 않았는가? 또 무슨 변화가 있을까?
바로 main 메서드 부분을 쓰지 않았다. 그냥 빠트린 것이 아니라 이제는 필요없어진 것이다.
@Component 로 인해서 Store store = new Store가 필요 없어졌고 @AutoWired 덕분에 그 뒤에 Product product = new Paper();를 하고 생성자에 추가해주는 메서드도 필요없어진 것이다.

❓그러면 저 프로덕트가 종인지 펜인지 어떻게 알아?

좋은 질문이다!!Product product = new Paper(); 이 코드가 없어져 버려 product의 구현체에서 무엇을 택할지가 모호해졌다. 인터페이스에 대한 구현체가 하나일 땐 알아서 단일의 구현체를 찾아 주입해준다. 하지만 구현체가 2개 이상이면? 이에 대한 방법으로 2가지가 있다.

@Primary

@Primary 어노테이션을 활용할 수 있다. 이 어노테이션은 스프링에게 말한다.

“얘가 우선적인 구현체야!”

spring은 한 interface에 여러 구현체가 있다면 저 어노테이션이 붙은 구현체로 주입해준다.

@Qualifier

@Component
public class Store {
   private Product product;

   @Autowired
   @Qualifier("paper")  // Bean 이름을 지정하여 Paper 객체 주입
   public Store(Product product) {
       this.product = product;
}

   public void sellProduct() {
       product.sell();
   }
}

@Qualifier 어노테이션은 구현체에 붙여주는 어노테이션이 아니라 해당 클래스(인터페이스)를 의존하는 객체(여기선 Store)의 의존성 주입부에 붙는다. 그리고 @Qualifier의 구현체의 이름인 인자를 보고 찾아서 의존성을 주입한다.

마치며

꽤 긴 시간동안 찾아보고 블로그를 작성해봤는데 모르고 쓰던 것을 이제서야 알 게 된 것이 부끄러웠다. 앞으로도 clean 한 코드를 위해서 물심양면의 노력을 하겠다.

Reference

https://velog.io/@seasame_oil/Spring-bean
https://velog.io/@falling_star3/Spring-Boot-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88bean%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84
https://velog.io/@falling_star3/SpringBoot-%EC%8A%A4%ED%94%84%EB%A7%81%EA%B3%BC-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8

그리고 Loving ChatGPT..