Spring

IoC와 DI

테니드2 2025. 3. 19. 00:38

IoC (Inversion of Control) - 제어의 역전

스프링에서 일반적인 JAVA 객체를 new로 생성하여 개발자가 직접 관리 하는 것이 아닌 Spring Container에게 모두 맡김으로 객체 관리 권한이 개발자에서 →프레임워크 넘어 갔음을 “제어의 역전”이라고 한다.

 

 IoC의 핵심 개념

- 의존성 역전: 객체가 다른 객체를 생성하거나 관리하는 대신, 외부에서 객체를 주입받아 사용합니다.
- 의존성 주입: 객체가 필요로 하는 의존성을 외부에서 제공받는 방식으로, 이는 생성자, setter 메서드 등을 통해 이루어집니다.
- 컨테이너: 객체의 생성 및 관리를 담당하는 역할로, Spring Framework에서는 ApplicationContext가 이러한 역할을 수행합니다.

 

IoC의 장점

 

- 결합도 감소: 객체 간의 결합도가 낮아져 코드의 유연성과 재사용성이 높아집니다.
- 테스트 용이성: 의존성을 쉽게 교체할 수 있어 테스트가 용이합니다.
- 유지보수성 향상: 객체 간의 의존 관계가 명확해져 유지보수가 쉬워집니다.

 

// 의존성 역전 이전
public class Car {
    private Engine engine;
    
    public Car() {
        engine = new GasolineEngine(); // 직접 생성
    }
}

// 의존성 역전 이후 (의존성 주입)
public class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine; // 외부에서 주입
    }
}

 

 

DI(Dependency Injection) - 의존성 주입

  • 의존성으로 부터 격리시켜 코드 테스트에 용이
  • DI를 통하여, 불가능한 상황을 Mock와 같은 기술을 통하여, 안정적으로 테스트 가능하다.
  • 코드를 확장하거나 변경 할 때 영향을 최소화 한다
  • 순환참조를 막을 수 있다
public interface IEncoder {
    String encode(String message);
}



import java.util.Base64;

public class Base64Encoder implements IEncoder {

    @Override
    public String encode(String message)
    {
        return Base64.getEncoder().encodeToString(message.getBytes());
    }
}



import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class UrlEncoder implements IEncoder{

    @Override
    public String encode(String message){
        try {
            return URLEncoder.encode(message,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}



public class Encoder {
    private IEncoder iEncoder;

		// 외부에서 의존하는 객체 주입 -> 의존성 주입(DI)
    public Encoder(IEncoder iEncoder) {
        this.iEncoder = iEncoder;
    }


    public String encode(String message)
    {
        return iEncoder.encode(message);
    }

}

public class Main {
    public static void main(String[] args) {

        String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";


        Encoder encoder = new Encoder(new Base64Encoder());
        String result = encoder.encode(url);
        System.out.println(result);

    }
}

 

@Component

객체의 생명 주기를 개발자가 아닌 스프링 컨테이너에 할당하기 위해 @Component 어노테이션을 사용하여 빈을 정의한다.

빈을 정의하면 스프링 프레임워크에서 의존성을 주입(DI) 해준다. DI 방법은 여러가지가 있다.

  1. 생성자 주입
@Service
public class MyAccountService implements AccountService {
    private final RiskAssessor riskAssessor;
    
    @Autowired
    public MyAccountService(RiskAssessor riskAssessor) {
        this.riskAssessor = riskAssessor;
    }
}

 

    2. Setter 주입

@Service
public class MyAccountService implements AccountService {
    private RiskAssessor riskAssessor;
    
    @Autowired
    public void setRiskAssessor(RiskAssessor riskAssessor) {
        this.riskAssessor = riskAssessor;
    }
}

 

    3. 필드 주입

@Service
public class MyAccountService implements AccountService {
    @Autowired
    private RiskAssessor riskAssessor;
}

 

 

package com.example.springioc;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return context;
    }
}

정의된 빈들은 ApplicationContext에서 찾을 수 있다.

 

package com.example.springioc;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Encoder {
    private IEncoder iEncoder;

    public Encoder(@Qualifier("base64Encoder") IEncoder iEncoder) {
        this.iEncoder = iEncoder;
    }

    public void setIEncoder(IEncoder iEncoder) {
        this.iEncoder = iEncoder;
    }

    public String encode(String message)
    {
        return iEncoder.encode(message);
    }

}

생성자를 통해 의존성을 주입을 받을 때 동일한 타입의 빈들이 여러 개 있을 경우 @Qualifier 어노테이션을 통해 어떤 빈을 주입 받을지 선택할 수 있다.

 

 

package com.example.springioc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
public class SpringIocApplication {

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

        Base64Encoder base64Encoder = context.getBean(Base64Encoder.class);
        UrlEncoder urlEncoder = context.getBean(UrlEncoder.class);

				//@Qualifier("base64Encoder")으로 base64Encoder 빈 주입
        Encoder encoder = context.getBean(Encoder.class);

				// Encoder 클래스로 정의된 빈 중에 이름으로 빈을 찾아 주입 
        Encoder encoder2 = context.getBean("urlEncode", Encoder.class);

    }
}


@Configuration
class AppConfig{

    @Bean("base64Encode")
    public Encoder encoder1(Base64Encoder base64Encoder) {
        return new Encoder(base64Encoder);
    }

    @Bean("urlEncode")
    public Encoder encoder2(UrlEncoder urlEncoder) {
        return new Encoder(urlEncoder);
    }

}