Home > SpringBoot > πŸ›οΈ[SpringBoot] μŠ€ν”„λ§ λΆ€νŠΈμ˜ 핡심 κ°œλ… - μ˜μ‘΄μ„± μ£Όμž…

πŸ›οΈ[SpringBoot] μŠ€ν”„λ§ λΆ€νŠΈμ˜ 핡심 κ°œλ… - μ˜μ‘΄μ„± μ£Όμž…
SpringBoot

πŸ›οΈ[SpringBoot] μŠ€ν”„λ§ λΆ€νŠΈμ˜ 핡심 κ°œλ… - μ˜μ‘΄μ„± μ£Όμž…

πŸ“– κ°œμš”

Spring Framework의 핡심 κ°œλ… 쀑 ν•˜λ‚˜μΈ μ˜μ‘΄μ„± μ£Όμž…(Dependency Injection) κ³Ό μ œμ–΄μ˜ μ—­μ „(Inversion of Control) 에 λŒ€ν•΄ μ•Œμ•„λ΄…λ‹ˆλ‹€. μ΄λŠ” SpringBoot μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 개발의 κΈ°μ΄ˆκ°€ λ˜λŠ” 맀우 μ€‘μš”ν•œ κ°œλ…μž…λ‹ˆλ‹€.


🧩 μ˜μ‘΄μ„±(Dependency)μ΄λž€?

κΈ°λ³Έ κ°œλ…

  • μ˜μ‘΄μ„±: ν•œ ν΄λž˜μŠ€κ°€ λ™μž‘ν•˜κΈ° μœ„ν•΄ λ‹€λ₯Έ ν΄λž˜μŠ€κ°€ ν•„μš”ν•œ 관계
  • ν‘œν˜„ 방식: β€œA ν΄λž˜μŠ€κ°€ B ν΄λž˜μŠ€μ— μ˜μ‘΄ν•œλ‹€β€ λ˜λŠ” β€œB ν΄λž˜μŠ€κ°€ A 클래슀의 μ˜μ‘΄μ„±μ΄λ‹€β€

Java ν”„λ‘œκ·Έλž˜λ°μ—μ„œμ˜ μ˜μ‘΄μ„±

public class OrderService {
    private PaymentService paymentService;
    
    public OrderService() {
        this.paymentService = new PaymentService(); // OrderServiceκ°€ PaymentService에 의쑴
    }
}

β˜• μ‹€μŠ΅ 예제: 컀피 메이컀 μ‹œμŠ€ν…œ

1단계: 기본적인 μ˜μ‘΄μ„± 관계 κ΅¬ν˜„

EspressoMachine 클래슀

public class EspressoMachine {
    public String brew() {
        return "Brewing coffee with Espresso Machine";
    }
}

CoffeeMaker 클래슀

public class CoffeeMaker {
    private EspressoMachine espressoMachine;
    
    public CoffeeMaker() {
        this.espressoMachine = new EspressoMachine(); // 직접 μ˜μ‘΄μ„± 생성
    }
    
    public void makeCoffee() {
        System.out.println(espressoMachine.brew());
    }
}

μ‹€ν–‰ μ½”λ“œ

public class Main {
    public static void main(String[] args) {
        CoffeeMaker coffeeMaker = new CoffeeMaker();
        coffeeMaker.makeCoffee();
        // 좜λ ₯: Brewing coffee with Espresso Machine
    }
}

🚨 λ‹¨λ‹¨ν•œ κ²°ν•©(Tight Coupling)의 문제점

문제 상황

μƒˆλ‘œμš΄ λ“œλ¦½ 컀피 머신을 μΆ”κ°€ν•˜κ³  μ‹Άλ‹€λ©΄?

DripCoffeeMachine 클래슀

public class DripCoffeeMachine {
    public String brew() {
        return "Brewing coffee with Drip Coffee Machine";
    }
}

κΈ°μ‘΄ CoffeeMaker 클래슀 μˆ˜μ • ν•„μš”

public class CoffeeMaker {
    // private EspressoMachine espressoMachine;     // μ‚­μ œ
    private DripCoffeeMachine dripCoffeeMachine;    // μΆ”κ°€
    
    public CoffeeMaker() {
        // this.espressoMachine = new EspressoMachine();        // μ‚­μ œ
        this.dripCoffeeMachine = new DripCoffeeMachine();       // μΆ”κ°€
    }
    
    public void makeCoffee() {
        // System.out.println(espressoMachine.brew());          // μ‚­μ œ
        System.out.println(dripCoffeeMachine.brew());           // μΆ”κ°€
    }
}

λ‹¨λ‹¨ν•œ κ²°ν•©μ˜ 단점

  • πŸ“ μ½”λ“œ μˆ˜μ • λ²”μœ„ ν™•λŒ€: μ˜μ‘΄μ„± 클래슀 λ³€κ²½ μ‹œ μ‚¬μš©ν•˜λŠ” λͺ¨λ“  ν΄λž˜μŠ€λ„ μˆ˜μ • ν•„μš”
  • πŸ”„ μœ μ§€λ³΄μˆ˜ 어렀움: μ—¬λŸ¬ 곳에 흩어진 μ½”λ“œλ₯Ό λͺ¨λ‘ μ°Ύμ•„μ„œ μˆ˜μ •
  • πŸ§ͺ ν…ŒμŠ€νŠΈ 어렀움: Mock 객체 μ‚¬μš©μ΄ 어렀움
  • πŸ“ˆ ν™•μž₯μ„± λΆ€μ‘±: μƒˆλ‘œμš΄ κ΅¬ν˜„μ²΄ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ λ³€κ²½ λΆˆκ°€ν”Ό

πŸ”— λŠμŠ¨ν•œ κ²°ν•©(Loose Coupling)으둜의 μ „ν™˜

1단계: μΈν„°νŽ˜μ΄μŠ€ μ •μ˜

public interface CoffeeMachine {
    String brew();
}

2단계: κ΅¬ν˜„μ²΄λ“€μ΄ μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„

public class EspressoMachine implements CoffeeMachine {
    @Override
    public String brew() {
        return "Brewing coffee with Espresso Machine";
    }
}

public class DripCoffeeMachine implements CoffeeMachine {
    @Override
    public String brew() {
        return "Brewing coffee with Drip Coffee Machine";
    }
}

3단계: CoffeeMaker 클래슀 λ¦¬νŒ©ν† λ§

public class CoffeeMaker {
    private CoffeeMachine coffeeMachine;
    
    // μ˜μ‘΄μ„±μ„ μ™ΈλΆ€μ—μ„œ μ£Όμž…λ°›λŠ” λ©”μ„œλ“œ
    public void setCoffeeMachine(CoffeeMachine coffeeMachine) {
        this.coffeeMachine = coffeeMachine;
    }
    
    public void makeCoffee() {
        System.out.println(coffeeMachine.brew());
    }
}

4단계: μ™ΈλΆ€μ—μ„œ μ˜μ‘΄μ„± μ£Όμž…

public class Main {
    public static void main(String[] args) {
        CoffeeMaker coffeeMaker = new CoffeeMaker();
        
        // λ“œλ¦½ 컀피 λ¨Έμ‹  μ‚¬μš©
        coffeeMaker.setCoffeeMachine(new DripCoffeeMachine());
        coffeeMaker.makeCoffee();
        
        // μ—μŠ€ν”„λ ˆμ†Œ λ¨Έμ‹ μœΌλ‘œ μ‰½κ²Œ λ³€κ²½
        coffeeMaker.setCoffeeMachine(new EspressoMachine());
        coffeeMaker.makeCoffee();
    }
}

✨ μ˜μ‘΄μ„± μ£Όμž…μ˜ μž₯점

🎯 μœ μ—°μ„± (Flexibility)

// μƒˆλ‘œμš΄ λͺ¨μΉ΄ λ¨Έμ‹  μΆ”κ°€ - CoffeeMaker μ½”λ“œ λ³€κ²½ μ—†μŒ!
public class MokaCoffeeMachine implements CoffeeMachine {
    @Override
    public String brew() {
        return "Brewing coffee with Moka Coffee Machine";
    }
}

// μ‚¬μš© μ‹œ
coffeeMaker.setCoffeeMachine(new MokaCoffeeMachine());

πŸ§ͺ ν…ŒμŠ€νŠΈ μš©μ΄μ„± (Testability)

// ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ Mock 객체 μ‰½κ²Œ μ‚¬μš© κ°€λŠ₯
public class MockCoffeeMachine implements CoffeeMachine {
    @Override
    public String brew() {
        return "Mock brewing for test";
    }
}

// ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œ
coffeeMaker.setCoffeeMachine(new MockCoffeeMachine());

πŸ”§ μœ μ§€λ³΄μˆ˜μ„± (Maintainability)

  • μƒˆλ‘œμš΄ κ΅¬ν˜„μ²΄ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ λ³€κ²½ λΆˆν•„μš”
  • 각 클래슀의 μ±…μž„μ΄ λͺ…ν™•ν•˜κ²Œ 뢄리
  • μΈν„°νŽ˜μ΄μŠ€ 기반 μ„€κ³„λ‘œ ν™•μž₯μ„± 확보

πŸ—οΈ Springμ—μ„œμ˜ μ˜μ‘΄μ„± μ£Όμž…

Spring Container의 μ—­ν• 

SpringBootμ—μ„œλŠ” Spring Containerκ°€ λ‹€μŒκ³Ό 같은 역할을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€:

  1. πŸ“¦ 객체 생성: μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— ν•„μš”ν•œ λͺ¨λ“  Bean 객체λ₯Ό 생성
  2. πŸ”— μ˜μ‘΄μ„± μ—°κ²°: 객체듀 κ°„μ˜ μ˜μ‘΄μ„±μ„ μžλ™μœΌλ‘œ μ£Όμž…
  3. ♻️ 생λͺ…μ£ΌκΈ° 관리: 객체의 생성뢀터 μ†Œλ©ΈκΉŒμ§€ 전체 생λͺ…μ£ΌκΈ°λ₯Ό 관리

Spring의 μ˜μ‘΄μ„± μ£Όμž… 방법

1. μƒμ„±μž μ£Όμž… (ꢌμž₯)

@Service
public class CoffeeMaker {
    private final CoffeeMachine coffeeMachine;
    
    public CoffeeMaker(CoffeeMachine coffeeMachine) {
        this.coffeeMachine = coffeeMachine;
    }
}

2. Setter μ£Όμž…

@Service
public class CoffeeMaker {
    private CoffeeMachine coffeeMachine;
    
    @Autowired
    public void setCoffeeMachine(CoffeeMachine coffeeMachine) {
        this.coffeeMachine = coffeeMachine;
    }
}

3. ν•„λ“œ μ£Όμž…

@Service
public class CoffeeMaker {
    @Autowired
    private CoffeeMachine coffeeMachine;
}

Spring Bean 등둝

@Component
public class EspressoMachine implements CoffeeMachine {
    // κ΅¬ν˜„ λ‚΄μš©
}

@Component
public class DripCoffeeMachine implements CoffeeMachine {
    // κ΅¬ν˜„ λ‚΄μš©
}

🎯 핡심 κ°œλ… 정리

μ˜μ‘΄μ„± μ£Όμž… (Dependency Injection)

객체가 ν•„μš”λ‘œ ν•˜λŠ” μ˜μ‘΄μ„±μ„ μ™ΈλΆ€μ—μ„œ μƒμ„±ν•˜μ—¬ μ£Όμž…ν•˜λŠ” 섀계 νŒ¨ν„΄

μ œμ–΄μ˜ μ—­μ „ (Inversion of Control)

객체의 생성과 관리 μ±…μž„μ„ κ°œλ°œμžμ—μ„œ ν”„λ ˆμž„μ›Œν¬(Spring Container)둜 이전

μž₯점 μš”μ•½

  • βœ… 결합도 κ°μ†Œ: 클래슀 κ°„ μ˜μ‘΄μ„±μ„ μΈν„°νŽ˜μ΄μŠ€λ‘œ 좔상화
  • βœ… μž¬μ‚¬μš©μ„± 증가: λ‹€μ–‘ν•œ κ΅¬ν˜„μ²΄λ₯Ό μ‰½κ²Œ ꡐ체 κ°€λŠ₯
  • βœ… ν…ŒμŠ€νŠΈ 용이: Mock 객체λ₯Ό ν†΅ν•œ λ‹¨μœ„ ν…ŒμŠ€νŠΈ κ°„νŽΈν™”
  • βœ… μœ μ§€λ³΄μˆ˜μ„±: μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ λ³€κ²½ μ΅œμ†Œν™”

πŸš€ λ‹€μŒ 단계

μ˜μ‘΄μ„± μ£Όμž…μ„ μ΄ν•΄ν–ˆλ‹€λ©΄ λ‹€μŒ κ°œλ…λ“€μ„ ν•™μŠ΅ν•΄λ³΄μ„Έμš”:

  • Spring Beanκ³Ό Component Scan
  • λ‹€μ–‘ν•œ μ˜μ‘΄μ„± μ£Όμž… μ• λ…Έν…Œμ΄μ…˜ (@Autowired, @Qualifier, @Primary)
  • Bean Scope (Singleton, Prototype λ“±)
  • 관점 μ§€ν–₯ ν”„λ‘œκ·Έλž˜λ° (AOP)

πŸ’‘ 싀무 팁

μƒμ„±μž μ£Όμž…μ„ ꢌμž₯ν•˜λŠ” 이유

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final EmailService emailService;
    
    // final ν•„λ“œλ‘œ λΆˆλ³€μ„± 보μž₯
    public OrderService(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }
}
  1. λΆˆλ³€μ„± 보μž₯: final ν‚€μ›Œλ“œλ‘œ 객체 μƒνƒœ 보호
  2. ν•„μˆ˜ μ˜μ‘΄μ„± 보μž₯: 객체 생성 μ‹œ λͺ¨λ“  μ˜μ‘΄μ„±μ΄ μ£Όμž…λ¨μ„ 보μž₯
  3. ν…ŒμŠ€νŠΈ μΉœν™”μ : μƒμ„±μžλ₯Ό 톡해 μ‰½κ²Œ Mock 객체 μ£Όμž… κ°€λŠ₯

Spring FrameworkλŠ” μ΄λŸ¬ν•œ μ˜μ‘΄μ„± μ£Όμž…μ„ 톡해 λŠμŠ¨ν•œ 결합을 μœ μ§€ν•˜λ©΄μ„œλ„ κ°•λ ₯ν•œ κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ κ°œλ°œμ„ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.