IoC와 DI
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 방법은 여러가지가 있다.
- 생성자 주입
@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);
}
}