1. IoC(Inversion of Control)란?

의존성 관리를 외부에 위임하여 객체의 생성과 관리를 제어하는 개발 패턴

2. 누가? 왜?

  1. 누구한테 위임하는가?

주로 프레임 워크에 위임합니다. 대표적으로 Spring 프로레임워크라고 볼 수 있겠습니다.

  1. 왜 위임하는가?

개인적으로 생각하는 위임하는 주요 장점중 하나는 개발자가 비즈니스 로직에 집중할 수 있다는 것입니다.

그렇다면, 의존성을 프레임워크에 위임하고 개발자는 비즈니스 로직 개발에 집중 할 수 있다. 정도로 보면 되겠네요.

3. 예시

DI에서 활용했던 코드를 사용해서 탐구해보겠습니다.

일반적으로 개발자가 객체를 생성관리 한다면 다음과 같을 것입니다.

LottoService lottoService = new LottoService(new RandomNumberGenerator());

그렇다면, 객체생성을 프레임워크에 위임하다고 하는데 그러한 패턴을 코드로 우선 보겠습니다.

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);

        LottoService myService = context.getBean(LottoService.class);

ApplicationContext란 객체에게 LottoService.class타입Bean을 달라고 하네요 🤔 그런데 여기서 중요한 부분은 확실하게 new를 사용하지 않은것으로 봐서는 객체 생성을 프레임워크에 위임한 것으로 볼 수 있습니다.

🚫그런데 실행했더니
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.LottoService' available 에러가 발생하네요 🤔

Spring 의 마법을 보기위해서 무언가 설정이 더 필요한가 봅니다. 😂 생각해보니 그럴 수 있을것 같아요.
왜냐면 무엇을 Bean으로 관리해줘라고 설정하지 않았거든요.

Bean등록 방법은 엄청 간단합니다!
XML,Java Config, Component Scan 이렇게 Bean을 등록하는 방법이 있는데요. 이번에는 많이 사용하는 Component Scan 방식을 사용해보겠습니다.

@Service 어노테이션이란 것만 붙여주면 됩니다. 그러면 Bean으로 등록해줍니다.

@Service
public class LottoService {
    private final NumberGenerator numberGenerator;

    public LottoService(NumberGenerator numberGenerator) {
        this.numberGenerator = numberGenerator;
    }
}

다시 실행해보겠습니다.

Parameter 0 of constructor in com.example.demo.LottoService required a bean of type 'com.example.demo.NumberGenerator' that could not be found. 에러가 발생하네요 🤔

LottoService 생성자의 0번째 파라미터(NumberGenerator)를 Bean에서 찾을 수 가 없데요 😱
Bean으로 관리하는 객체라면 당연히 생성자 주입해야하는 객체 또한 Bean에서 찾을수 있어야 할 것같네요 😂

그럼 NumberGeneratorBean으로 등록해주면 됩니다.
@Component 어노테이션을 붙여주면 됩니다. 그러면 Bean으로 등록해줍니다.

@Component
public class RandomNumberGenerator implements NumberGenerator {

    public RandomNumberGenerator() {
    }

    @Override
    public int generate() {
        Random random = new Random();
        return random.nextInt(45) + 1;
    }
}

드디어 장상작동 되네요.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-17T17:10:59.007+09:00  INFO 73775 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 17.0.6 with PID 73775
2023-05-17T17:10:59.008+09:00  INFO 73775 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
2023-05-17T17:10:59.167+09:00  INFO 73775 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.267 seconds (process running for 0.447)
LottoNumber{value=41}

@Component, @Service… ??

@Component, @Service, @Repository, @Controller 등의 어노테이션을 사용하여 Bean으로 등록할 클래스에 지정 할 수 있습니다.
스프링 부트는 기본적으로 클래스패스를 스캔하여 어노테이션이 지정된 클래스를 자동으로 Bean으로 등록합니다.

이와 같은 방법을 사용하여 Bean을 등록하면 Spring IoC 컨테이너가 해당 Bean을 인식하고 필요한 곳에서 주입할 수 있습니다.

🤔그런데 NumberGenerator 구현체가 여러개면 무엇을 주입할지 어떻게 정할까?

@Primary 어노테이션을 사용하면 됩니다.
@Primary 어노테이션을 사용하면 여러개의 Bean 중에서 우선적으로 주입할 Bean을 지정할 수 있습니다.

@Component
@Primary
public class RandomNumberGenerator implements NumberGenerator {

    public RandomNumberGenerator() {
    }

    @Override
    public int generate() {
        Random random = new Random();
        return random.nextInt(45) + 1;
    }
}

하지만 이렇게 사용하면 NumberGenerator를 주입받는 모든 곳에서 RandomNumberGenerator가 주입됩니다. @Qualifier 어노테이션을 사용하면 주입할 Bean을 지정할 수 있습니다.

public LottoService(@Qualifier("manualNumberGenerator") NumberGenerator numberGenerator) {
    this.numberGenerator = numberGenerator;
}

 

🧐어노테이션???

@Service 이런것을 클래스나 메소드 위에 붙이는 것을 말합니다.
쉽게 말해 주석입니다. 하지만 좀 더 의미있는 주석이라고 보면 됩니다.
프레임워크단에서 @Service이게 붙어 있으면 아 이건 이런 기능을 하는 클래스 혹은 메소드구나 인식하도록 주석을 달아 놓았다고 보면 됩니다.

어노테이션은 런타임에 리플렉션을 통해 읽어들일 수도 있으며, 프레임워크나 라이브러리에서 특정 기능을 활성화하거나 설정할 때 사용될 수도 있습니다.