[spring]IoC, DI

kongta2·2023년 12월 14일

spring 복습 2일차
IoC와 DI를 좀 더 공부하려고 한다.

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

  • 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다.
  • 전통적인 프로그래밍에서 흐름은 프로그래머가 작성한 프로그램이 외부 라이브러리의 코드를 호출해 이용한다. 하지만 제어 반전이 적용된 구조에서는 외부 라이브러리의 코드가 프로그래머가 작성한 코드를 호출한다.
  • 오브젝트(Bean) 생성, 관계설정, 사용, 제거 등 오브젝트 전반에 걸친 모든 제어권을 애플리케이션이 갖는게 아니라 프레임워크의 컨테이너에게 넘기는 개념

목적

  • 작업을 구현하는 방식과 작업 수행 자체를 분리한다.
  • 모듈을 제작할 때, 모듈과 외부 프로그램의 결합에 대해 고민할 필요 없이 모듈의 목적에 집중할 수 있다.
  • 다른 시스템이 어떻게 동작할지에 대해 고민할 필요 없이, 미리 정해진 협약대로만 동작하게 하면 된다.
  • 모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.

DI(Dependency Injection, 의존성 주입)

  • 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴
  • 객체를 직접 생성하는 것이 아니라 외부에서 생성 후 주입시켜 주는 방식
  • DI를 통해 객체 간의 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있다.

DI의 3가지 방법

  • 필드 주입
  • 메서드(Setter) 주입
  • 생성자 주입

필드 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.food = new Chicken();
        consumer.eat();

        consumer.food = new Pizza();
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

Food를 직접 Consumer에 포함시키고 Food에 필요한 객체를 주입받아 사용.

public class Consumer {
	@Autowired
    private Food food;
}
  • @Autowired를 사용하면 자동으로 의존관계 주입, 편하게 의존성 주입 가능
  • 주입하기 쉬운만큼 제한없이 추가될 수 있어 위험하다.(단일 책임의 원칙(SRP) 위반
  • 의존성이 숨는다, 즉 참조 관계를 눈으로 확인하기 어렵고 순환참조를 막을 수가 없다.
  • 생성자 이후에 호출되므로, 필드에 final키워드를 사용할 수 없어 객체가 변할 수 있다.

메서드(Setter) 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.setFood(new Chicken());
        consumer.eat();

        consumer.setFood(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

Set메서드를 이용하여 필요한 객체를 주입 받아사용

public class Consumer {
	private Food food;
    
    @Autowired
    public void setFood(Food food) {
    	this.food = food;
    }
}
  • @Autowired 어노테이션과 set 메서드를 통해 의존관계 주입
    • NullPointerException이 발생할 수 있다.
    • 생성자 이후에 호출되므로, 필드에 final키워드를 사용할 수 없어 객체가 변할 수 있다.

생성자 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();

        consumer = new Consumer(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

생성자를 사용하여 필요한 객체를 주입

	private Food food;
    
    public Consumer(Food food) {
    	this.food = food;
    }
}
  • spring에서 가장 권장하는 방식
  • 다른 DI방법과 다르게 final키워드 사용이 가능하다.
  • NullPointerException이 발생하지 않는다.
  • 순환의존성을 알 수 있다.
  • @Autowired 생략이 가능하고, Lombok라이브러리를 사용해 생성자 또한 생략이 가능하다.
profile
2025.04.01~

0개의 댓글