Spring

Spring 개념 정리 ) IoC와 DI, IoC Container와 Bean

banasu0723 2024. 8. 19. 02:35

IoC란?

  • 설계 원칙
  • 제어의 역전 (Inversion of Control)

 

DI란?

  • 디자인 패턴
  • 의존성 주입
  • 의존성이란?

 

 

→ 우리는 DI 패턴을 이용하여 IoC 설계 원칙을 구현하고 있다

 

 

 

  • 강하게 결합돼있는 Consumer 와 Chicken
public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

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

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

//이 때는 치킨이 아니라 피자가 먹고 싶을 때 코드 수정이 힘들다 -> 약하게 결합시켜야함

 

 

  • 약하게 결합시키기 - Interface 활용 (약한 결합 및 약한 의존성)
public class Consumer {

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

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

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("피자를 먹는다.");
    }
}

 

 

DI 사용 )

 

주입이란 ?  여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것필드에 직접 주입

  • 필드에 직접 주입 (Food 를 Consumer에 포함시키고 Food에 필요한 객체를 주입받아 사용할 수 있다.)
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("피자를 먹는다.");
    }
}

 

  • 메서드를 통한 주입 (set 메서드를 사용하여 필요한 객체를 주입받아 사용할 수 있다)
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("피자를 먹는다.");
    }
}

 

 

제어의 역전(IoC)

  • 제어의 흐름이 Consumer → Food 에서 Food → Consumer 로 역전
    • Food를 Consumerd에게 전달해주는 식으로 변경함으로써 Consumer은 추가적인 코드변경 없이 어느 Food가 되었든지 전부 먹을 수 있게 되었다.

 

 

IoC Container

  • DI를 사용하기 위해서는 객체 생성이 우선 되어야하며, Spring 프레임워크가 필요한 객체를 생성하고 관리하는 역할을 대신 해준다.
  • 빈(Bean) : Spring이 관리하는 객체
  • Spring IoC 컨테이너 : 'Bean'을 모아둔 컨테이너

 

  • Spring 'Bean' 등록 방법
@Component //'Bean' 으로 등록하고자 하는 클래스 위에 설정
public class MemoService { ... }

 

  • Spring 서버가 뜰 때 IoC 컨테이너에 'Bean'을 저장
    • @Component가 설정된 클래스에 대해서 Spring이 해 주는 일 확인하기
// 1. MemoService 객체 생성, 'Bean'이름 : 클래스의 앞글자만 소문자로 변경
MemoService memoService = new MemoService();

// 2. Spring IoC 컨테이너에 Bean (memoService) 저장
// memoService -> Spring IoC 컨테이너

왼쪽에 bean 아이콘 표시 확인, IoC에서 관리할 'Bean' 클래스라는 표시

 

 

  • Spring 서버가 뜰 때 @ComponentScan에 설정해 준 packages 위치와 하위 packages 들을 전부 확인하여 @Component가 설정된 클래스들을 ‘Bean’으로 등록 해준다
@Configuration
@ComponentScan(basePackages = "com.sparta.memo")
class BeanConfig { ... }
  • @SpringBootApplication에 의해 default 설정 되어있다. ( com.sparta.memo/MemoApplication.java )

 

 

 

Spring 'Bean' 사용 방법 )

 

  • @Autowired

1. 필드 위에

@Component
public class MemoService {
		
    @Autowired // Spring에서 IoC 컨테이너에 저장된 memoRepository 'Bean'을 해당 필드에 DI
    private MemoRepository memoRepository;
		
		// ...
}

 

2. 'Bean'을 주입할 때 사용할 메서드 위에

@Component
public class MemoService {

    private final MemoRepository memoRepository;

    @Autowired //객체의 불변성을 확보할 수 있기 때문에 생성자를 사용하여 DI를 하는 것이 좋다
    public MemoService(MemoRepository memoRepository) {
        this.memoRepository = memoRepository;
    }
		
		// ...
}

 

 

  • @Autowired 적용 조건
    • Spring IoC 컨테이너에 의해 관리되는 클래스에서만 가능
    • Spring IoC 컨테이너에 의해 관리되는 ‘Bean’객체만 DI에 사용될 수 있습니다.
  • @Autowired 생략 조건
    • Spring 4.3 버젼 부터 @Autowired 생략가능
      • 단, 생성자 선언이 1개 일때만 가능
    • Lombok 의 @RequiredArgsConstructor를 사용
@Component
@RequiredArgsConstructor // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {

    private final MemoRepository memoRepository;
    
//    public MemoService(MemoRepository memoRepository) {
//        this.memoRepository = memoRepository;
//    }

		...

}

 

 

  • ApplicationContext : BeanFactory등을 상속하여 기능을 확장한 Container
    • BeanFactory 는 'Bean'의 생성, 관계설정등의 제어를 담당하는 IoC 객체
    • 스프링 IoC 컨테이너에서 'Bean'을 수동으로 가져오는 방법
@Component
public class MemoService {

		private final MemoRepository memoRepository;

    public MemoService(ApplicationContext context) {
        // 1.'Bean' 이름으로 가져오기
        MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");

        // 2.'Bean' 클래스 형식으로 가져오기
        // MemoRepository memoRepository = context.getBean(MemoRepository.class);

        this.memoRepository = memoRepository;
    }

		...		
}

 

 


 

3 Layer Annotation

  • Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용된다