본문 바로가기
백엔드 개발

#048. 스프링: 스프링 빈 이해하기(DI, IOC, 등록과 주입)

by iamjoy 2023. 7. 13.

최태현님의 스프링 기초 강의를 듣다가 너무 좋은 내용이 나와 강의 내용을 정리했다.

스프링 컨테이너를 사용하는 이유(DI 와 IOC)

spring의 IOC: 주입 가능한 컴포넌트(Bean)를 스프링 컨테이너에서 관리하는 것
spring의 DI: 아래와 같이 service 레이어에서 직접 repository 클래스를 인스턴스로 만들어 사용하는 것이 아니라 이미 만들어진 인스턴스를 주입받아 사용하는 방식.(IoC를 하기 위한 한 가지 패턴).

(1) spring bean을 사용하지 않는 service 와 repository를 이용해 구현 (메모리를 사용하는 DB와 연결)
사용하는 곳에서 객체를 직접 instanciate 해서 사용한다.

// Controller
@RestController
public class BookController {

  private final BookService bookService = new BookService();

  @PostMapping("/book")
  public void saveBook(@RequestBody BookCreateRequest request) {
    bookService.saveBook(request);
  }
}

 

// Service

public class BookService {

  private final BookRepository bookRepository = new BookRepository();

  @Transactional
  public void saveBook(BookCreateRequest request) {
    bookRepository.save();
  }
}

 

// Repository

class BookRepository {

   fun save(){}
}

 

(2) 이제 Memory가 아니라 MySQL과 같은 DB를 사용해야한다. JdbcTemplate은 Repository가 바로 설정할 수 있다고 해보자.

Repository를 바꿨을 뿐인데 Service까지 바꿔야한다.
만약 bookRepository를 1000곳에서 사용하고 있다면? 코드 수정에 대한 두려움이 생김
어떻게 하면 repository를 다른 class로 바꾸더라도 bookservice를 변경하지 않을 수 있을까? -> (3) interface 사용

// MySQLRepository 생성

class BookMySQLRepository {

   fun save(){}
}

 

// Service

@Service
public class BookService {

//  private final BookRepository bookRepository = new BookRepository(); //<--- 주석처리하고
  private final BookMySQLRepository bookRepository = new BookMySQLRepository(); //<--- MySQLRepository와 연결

  @Transactional
  public void saveBook(BookCreateRequest request) {
    bookRepository.save();
  }
}


(3) interface 사용
-> Repository를 생성하는 줄 전체가 아니라 어떤 instance를 사용할 것인지만 바꿔줄 수 있게 된다.

 

// BookRepository Interface

interface BookRepository {
    fun save()
}

 

//BookMemoryRepository

class BookMemoryRepository : BookRepository {

   override fun save() {

   }
}

 

// BookMySQLRepository

class BookMySQLRepository: BookRepository{

   override fun save() {

   }
}

 

아래와 같이 Repository를 생성하는 줄 전체가 아니라 어떤 instance를 사용할 것인지만 바꿔주면 된다.

//Service 수정

@Service
public class BookService {

  private final BookRepository bookRepository = new BookMySQLRepository(); //<--- 구현체 부분만 갈아끼워주면 된다.

  @Transactional
  public void saveBook(BookCreateRequest request) {
    bookRepository.save();
  }
}

 

직접 인스턴스를 만드는 것과 비교해서 발전했지만.. 어딘가 부족하다.
BookService의 변경 범위가 줄어들긴 했지만, 아쉽다!
우리가 원하는 건 Service의 코드를 단 한 줄도 바꾸지 않고 어떻게 하면 repository를 변경할 수 있을까?이다.

그래서 등장한 것이 스프링 컨테이너이다.
스프링 컨테이너는 유저 서비스를 대신 인스턴스화 하고 그 때 그 때 알아서 필요한 Repository를 결정해준다. (IoC)

annotation을 이용해 스프링 컨테이너에 등록한다. @Controller, @Service, @Repository 등

// Controller

@RestController
public class BookController {

  private final BookService bookService; 

  public BookController(BookService bookService){ // <--------------- 인스턴스를 직접 만들지 않고 생성자를 만들어서 spring bean을 주입받게 된다.
    this.bookService = bookService;
  }

  @PostMapping("/book")
  public void saveBook(@RequestBody BookCreateRequest request) {
    bookService.saveBook(request);
  }
}

 

// Service

@Service
public class BookService {

  private final BookRepository bookRepository;
  public BookService(BookRepository bookRepository){
    this.bookRepository = bookRepository;
  }
  @Transactional
  public void saveBook(BookCreateRequest request) {
    bookRepository.save();
  }
}

 

// Repository

// MySQL과 연결된  repository @Primary를 이용해 SpringBean에게 어느 repository를 사용할 지 알려준다
@Primary
@Repository
class BookMySQLRepository: BookRepository{

   override fun save() {

   }
}

// 메모리 DB와 연결된 repository
@Repository
class BookMemoryRepository : BookRepository {  

   override fun save() {

   }
}

 

스프링 빈에 등록하는 방법

(1) @Configuration + @Bean 사용

@Configuration: 클래스에 붙이는 어노테이션, @Bean을 사용할 때 함께 사용해야한다.
@Bean: 메소드에 붙이는 어노테이션이다.
개발하는 과정에서 유저가 직접 만든 클래스는 @Service, @Repository를 사용하는 게 더 좋고
외부의 라이브러리를 가져다 쓸 때에는 @Configuration + @Bean 조합을 사용하는 것이 더 좋다.

// BookRepository

class BookRepository (jdbcTemplate: JdbcTemplate){}

 

// Configuration

@Configuration
public class BookConfiguration {

    @Bean
   public BookRepository bookRepository(JdbcTemplate jdbcTemplate) {
     return new BookRepository(jdbcTemplate);
   }
}

 

(2) @Compoenet (컴포넌트) 사용

이 어노테이션이 붙은 클래스들은 스프링 서버가 뜰 때 자동으로 '컴포너트로' 감지된다.
자동으로 등록되는 Bean 들 중 하나. @Repository 같은 어노테이션을 열어보면 @Component가 숨겨져 있다.
이 어노테이션 덕분에 사용했던 어노테이션들이 자동으로 감지된 것이다. 
@Controller,@Service, @Repository 모두 아니면서 개발자가 직접 작성한 클래스를 스프링 빈으로 등록할 때 사용된다.

스프링 빈 주입 방법

(1) (가장 권장) 생성자를 만들 때 주입하기
@Autowired: 생성자의 매개변수에 주입해달라는 의미였는데 이제 자동으로 가능

(2) setter + @Autowired 사용하기 -> 누군가 setter를 사용하면 오작동할 수 있다.

(3) 필드에 직접 @Autowired 사용 -> 테스트가 어려워진다.

(4) @Qualifier: Bean의 이름 지정하기
사용법1: 여러개의 후보군 중 특정해서 가져오고 싶은 경우 호출하는 쪽에서(서비스에서) 빈의 이름을 정해서지정해서 가져올 수 있다. 예를 들면 @Qualifier("appleService")를 하면 AppleService가 등록되어 있는 지 확인하고 호출한다.
사용법2: 호출하는 쪽과 호출되는 쪽에서 특별한 이름으로 지정해서 호출 할 수도 있다 @Qualifier("main")
Primary와 동시에 적용된다면 호출하는 쪽에서 적용한 것이 우선된다.