[SPRING]IoC(제어의 역전), DI(의존성 주입)

라미·2024년 2월 25일
0

spring

목록 보기
6/17
post-thumbnail

IoC(제어의 역전), DI(의존성 주입)

IoC(Inversion of Control)

  • 제어의 역전을 의미한다.
  • IoC는 사용할 객체를 직접 생성하지 않고, 객체의 생명주기 관리를 외부(Spring Container or IoC Contaioner)에 위임하는 것이다.

DI(Dependency Injection)

  • 의존성 주입을 의미, IoC 패턴 구현 방법중 하나.
  • 객체가 직접 자신이 필요로 하는 의존성을 생산하거나 찾지 않고 외부에서 주입받는 디자인 패턴
  • 미리 준비된 객체를 주입해준다(외부 컨테이너가 생성한 객체!)

📝 IoC와 DI를 사용하지 않은 경우

public class UserService {
    private UserDao userDao;

    public UserService() {
        this.userDao = new UserDao();
    }
    
    // ...
}

📝 IoC와 DI를 사용한 경우

public class UserService {
    private UserDao userDao;

    // Constructor Injection
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    // ...
}

🤔의존성이란!?

📝 강한 결합의 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("치킨을 먹는다.");
    }
}
  1. 위 코드에서 Consumer 가 원하는게 변경 된다변 코드의 변경이 있을 수 밖에 없다! (eat() 변경, 새로운 클래스 작성 등)
  2. 이것을 해결하기 위해서 Interface를 활용해야 한다!(다형성 구현!)

📝 약한 결합 or 약한 의존성

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("피자를 먹는다.");
    }
}
  1. chicke, pizza는 food interface를 상속받는다.
  2. eat() 메서드는 매개변수로 FOOD interface를 받아 원하는게 변경되도 코드의 변경없이 쉽게 대처가 가능하다!(다형성 구현됨!)

🤔주입이란!?

필요로 하는 객체를 해당객체에 전달하는 것.

📝 필드를 통한 주입

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 내용 위와 동일
  • 포함관계를 통한 주입

📝 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 내용 위와 동일

📝 생성자를 통한 주입

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 내용 위와 동일

🤔제어의 역전이란!?

결과적으로 DI 를 통해 IoC를 이룬다!
강한 결함 코드 예시의 제어의 흐름: Consumer → Food (직접 객체 생산)
약한 의존 코드 예시의 제어의 흐름 : Food → Consumer (외부를 통해 필요한 객체를 주입받음)


IoC Container 와 Bean

Bean : 스프링이 관리하는 객체
IoC Container : Bean을 모아둔 컨테이너!

일반 class를 Spring이 관리하는 Bean으로 등록하려면 어떻게 해야 할까?

Bean으로 등록하고자 하는 클래스에 @Component annotation을 주면 된다!

@Component //스프링이 관리하는 bean 으로 등록!
public class MemoService {
    private final MemoRepository memoRepository;
    public MemoService(MemoRepository memoRepository) {
        // 생성자를 통해 생성이 될때 jdbcTemplate 을 받으면서 memoRepository 생성
        this.memoRepository = memoRepository;
    }
}

Spring 서버가 뜰 때 @ComponentScan에 설정해준 packages 위치와 하위 packages 들을 전부 확인하여 @Component가 설정된 클래스들을 Bean으로 등록해준다!

Spring Boot는 @SpringBootApplication에 의해 default로 설정이 되어있다!!! (프로젝트 생성시 함께 생성되는 Application.java 파일에 설정됨!)

Bean을 사용한 의존성 주입

@Autowired
스프링에서는 @Autwired라는 어노테이션(annotation)을 통해 의존성 주입을 명시할 수 있다.

스프링 4.3 이후 버전은 생성자 주입 시 @Autowired 어노테이션을 생략할 수 있다.

📝 생성자 주입

@RestController
public class DIController {

    MyService myService;

    @Autowired
    public DIController(MyService myService) {
        this.myService = myService;
    }
    
	//코드 생략
}

📝 setter를 통한 주입

@RestController
public class DIController {

    MyService myService;

    @Autowired
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
    
    //코드 생략
}

📝 필드를 통한 주입

@RestController
public class DIController {

    @Autowired
    private MyService myService;
    
    //코드 생략
}

스프링 공식 문서에서 권장하는 의존성 주입 방법생성자를 통해 의존성을 주입받는 방법이다.
다른 방식과는 다르게 레퍼런스 객체 없이는 객체를 초기화할 수 없도록 설계하기 때문이다.

스프링 4.3 이후 버전은 생성자 주입 시 @Autowired 어노테이션을 생략이 가능하기 때문에 Lombok을 통해 의존성을 주입할 수도 있다.(생성자가 하나만 있을 경우! autowired 생략가능)

📝 롬복을 통산 생성자 주입

@RestController
@RequiredArgsConstructor //final이 붙은 필드의 생성자를 자동으로 생성
public class DIController {

    privite final MyService myService;
    
	//Controller Codes
}

📝 IoC 컨테이너에 직접 접근하여 주입하는 법
그냥 알아두기!

@Component
public class MemoService {
    private final MemoRepository memoRepository;
    public MemoService(ApplicationContext context) { //스프링 IoC에 직접 접근하여 등록된 bean 주입하기
        // 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을 등록핼때 해당 클래스의 역할을 명시하기 위해 사용한다.
1. @Controller, @RestController
2. @Service
3. @Repository

0개의 댓글