본문 바로가기
기록해보기

스프링 컨테이너를 왜 사용할까?

by titlejjk 2023. 6. 6.

스프링 빈이 무엇인지 스프링 컨테이너가 무엇인지 알아보았다.

스프링 컨테이너는 서버가 시작될 때 함께 시작되는 Class를 담는 공간(컨테이너)

스프링 빈은 그 컨테이너에 담긴 Class 하나하나를 스프링 빈이라 불렀다.

 

이번에는 그런 스프링 컨테이너가 왜 탄생했는지? 왜 사용하는지에 대해 알아보겠다.

그러기 위해 다음 요구사항을 생각해보겠다.

예로 책 이름을 메모리에 저장하는 API를 매우 간단하게 구현하도록하겠다.

이때 Service, Repository는 Spring Bean이 아니어야 한다.

그럼 전체적인 그림은 위처럼 되는데 DB를 사용할 것이 아니기 때문에 BookMemoryRepository라 해두었다.

전체적인 느낌만 잡아보자

repository와 service package안에 user package만 존재하는데 Controller도 마찬가지이다. 그래서 book package와 BookController Class도 만들어 주겠다.

이 다음에 repository에도 book package 그리고 BookMemoryRepository Class와 service에도 book package에 BookService Class를 생성해 주겠다.

그럼 이제 Controller가 service를 호출하게 해준다.

package com.group.libraryapp.controller.book;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    
    @PostMapping("/book")
    public void saveBook(){
        
    }
}

다음으로 BookController가 BookService를 받기 위해서 (스프링 빈이아닐경우)에 new BookService();로 인스턴스화를 시켜준다.

saveBook메소드에는  bookService.saveBook을 호출할 코드를 구현부에 작성해준다.

@RestController
public class BookController {

    private final BookService bookService = new BookService();

    @PostMapping("/book")
    public void saveBook(){
		bookService.saveBook();//BookService에서 가져올 메소드
    }
}

BookService Class에서는 saveBook메소드를 만들어주고 위에 Controller처럼 BookMemoryRepository를 인스턴스화시켜준다.

package com.group.libraryapp.service.book;

import com.group.libraryapp.repository.book.BookMemoryRepository;

public class BookService {

    private final BookMemoryRepository bookMemoryRepository
            = new BookMemoryRepository();

    public void saveBook(){
        bookMemoryRepository.saveBook();
    }
}

BookMemoryRepository Class에는 

package com.group.libraryapp.repository.book;

import java.util.ArrayList;
import java.util.List;

public class BookMemoryRepository {

    //private final List<Book> books = new ArrayList<Book>();

    public void saveBook(){
        // book.add(new Book());
    }
}

Controller - Service - Repository 이 삼단계층이 완성이 되었다.

위 Class들은 스프링 빈을 사용하지 않고 작성한 Class들이다.

 

위 처럼 만들어 두었는데 갑자기 요구사항이 바뀌었다!

더보기

이제 Memory가 아니라 MySQL과 같은 DB를 사용해야한다!

JdbcTemplate은 Repository가 바로 설정할 수 있다고 해보자!

원래는 BookMemoryRepository라는 Memory에 저장할수 있는 Class를 구현했는데, 갑자기 MySQL이라는 DB를 사용해 달라고 하는 것이다.

강사님 ppt

BookController랑 BookService는 재활용이 가능하기 때문에 

이런식으로 바꿔주면 좋을 것 같다.

잘하긴 했는데 어딘가 부족하다?..

 

데이터를 메모리에 저장할지 MySQL에 저장할지는 Repository의 역할이다. Repository라는 계층이 저장하는 역할을 가지고 있기 때문인데, Repository의 역할만 바꾸고 싶지만 위처럼 BookService도 Repository의 코드를 사용하기 때문에 같이 바꾸어 주어야 한다.

 

지금은 BookMemoryRepository를 BookService 한 곳에서만 사용하지만 만약 여러군대서 쓰게 된다면 그 여러군대 모든 곳을 찾아가서 바꿔줘야 하기 때문에 매우 불필요하다. 매우 비효율적이다.

 

그러면? 어떻게 하면 Repository를 다른 Class로 바꾸더라도 BookService를 변경하지 않을 수 있을까?

 

Java의 Interface를 활용하면 된다!

BookController는 BookService를 BookService는 BookRepository Interface를 사용한다. 그리고 BookMemoryRepository는 BookRepository를 구현하고 있다.

그렇게 되면 BookMemoryRepository 타입을 직접 사용하는 것이 아니라 Interface를 사용하게 되고 새로운 코드가 나오면 BookMySqlRepository가 BookRepository를 구현하게끔하고 앞 부분은 바꿀필요가 없게끔 만들어준다.

강사님 ppt

Interface를 활용한다는 것은 BookMemoryRepository가 있는 package에 java Interface로 BookRepository를 만들어 준다.

그 다음에 BookMemoryRepository가 BookRepository를 구현하게 하고 BookRepository에는 다음과 같은 코드를 써준다.

package com.group.libraryapp.repository.book;

public interface BookRepository {

    void saveBook(); //앞에 public은 추상메소드이기 때문에 작성안해도 된다.

}

그리고 BookServie에는 BookMemoryRepository를 가지고 있는데 이것을 BookRepository로 변경해준다.

이름도 변경해주어야 하는데 이때 단축키가 하나 있다 ⇧ F6 을 누르면된다.

사용되는 변수이름이나 함수이름의 모든 부분을 한번에 바꾸어준다.

그런 다음  BookMySqlRepository JavaClass를 만들어준다.

이 BookMySqlRepository에 BookRepository를 implements해주고 오버라이드 해준다 단축키는 ^ o이다.

이렇게 해주면 원래 Interface를 아예 사용하지 않았다면 BookService에 있는 코드를 바꿔야했지만

이 부분만 

= new BookMySqlRepository();

로 바꿔주면 된다.

package com.group.libraryapp.service.book;

import com.group.libraryapp.repository.book.BookMemoryRepository;
import com.group.libraryapp.repository.book.BookRepository;

public class BookService {

    private final BookRepository bookRepository
            = new BookMemoryRepository();

    public void saveBook(){
        bookRepository.saveBook();
    }
}

Interface를 사용하지 않는다면 줄 전체를 바꿔야하지만 사용한다면 어떤 구현 타입의 인스턴스를 사용할지만 정해서 바꿔주면 된다.

 

BookService의 변경 범위가 줄어들긴 했지만 아직도 부족한 느낌이 있다.

어찌되었건 위처럼 코드를 바꿔주는건 마찬가지(?)니까?

그렇다면 정말 BookRepository를 사용하는 코드를 단 한줄도 바꾸지 않고 어떻게 해야 BookRepository요구하는 사항을 바꿀수 있을까?

그래서 등장한 것이 스프링 컨테이너 이다.

더보기

컨테이너가 BookService를 대신 인스턴스화 하고,

그 때 알아서 BookRepository를 결정해준다.

이 얘기를 했었는데

스프링 컨테이너는 스프링컨테이너는 스프링컨테이너의 등록된 class를 대신 인스턴스화하고 그 때 알아서 필요한 의존성들을 결정해준다.

즉 이경우에 위에 만들어둔 BookService와 BookRepository가 모두 스프링빈이라면 스프링컨테이너는 BookService를 대신 인스턴스화할 것이고 그 때알아서 BookRepository를 결정해줄 것이다.

BookMemoryRepository와 BookMySqlRepository가 스프링 컨테이너 안에 들어있다.

BookService도 스프링 컨테이너에 들어있다.

위에서 작성한 코드는 스프링빈으로 작성이 안되었지만 @Service, @Repository를 붙여주면 스프링컨테이너에 등록이 될 것이다.

이 때 BookService를 인스턴스화 하는 과정에서 BookMemoryRepository를 쓸지 BookMySqlRepository를 

스프링 컨테이너가 선택하게 한다면 우리는 코드를 단 한줄도 바꾸지 않고 BookMemoryRepository를 BookMySqlRepository로 바꿀 수 있게 된다.

 

위에 개념을 알고 코드를 작성해보자.

먼저 BookService부터 @Service를 붙여준다.

그럼 BookController에서 직접 new BookService를 직접 가져오는 것이 아니라 생성자를 만들어서 스프링빈을 주입받게 한다.

생성자를 통해 스프링 컨테이너가 대신 BookService를 넣어주게 바뀌게 된것이다.

BookMySqlRepository가 없다고 생각하고 BookMermoryRepository에 @Repository를 해준 뒤에

BookService에서도 위 처럼 똑같이 new BookRepository를 하는 것이 아니라 생성자를 통해서 BookRepository를 주입받게 된다.

 

그럼 이상황에서 다시 BookMySqlRepository를 만들어준다.(implements BookRepository도 해주고 오버라이드도 해준다)

만들어준 BookMySqlRepository에도 @Repository를 해준다.

 

여기까지하면

스프링컨테이너가 BookMemoryRepository를 쓸지  BookMySqlRepository 쓸지 알아서 결정해주게 된다.

이런방식

new 를 통해 어떤 것을 쓸지를 정하는 것이 아닌 스프링컨테이너가 대신 결정해 주는 방식을

제어의 역전(IoC, Invversion of Control)이라고 한다.

컨테이너가 특정구현체를 선택해 BookService에 넣어주는 과정을 의존성주입(DI, Dependency Injection)라고 한다.

 

기능 구현을 바꾸기 위해 한줄한줄 코드를 찾아서 변경하는게 아니라 스프링 컨테이너를 통해 어떤 구현타입을 쓸지 대신 결정해주는 제어의 역전을 썼고 이 덕분에 Class를 갈아 끼울 때 그 Class를 사용하던 곳에 있는 코드를 변경없이 원하는 Class를 주입할 수 있게 된 것이다.

 

그렇다면 위 BookMemoryRepository와 BookMySqlRepository 둘중 스프링컨테이너가 주입시킬까?

스프링컨테이너도 두개 이상이 스프링빈으로 등록이 되면 어떤 것을 사용할지 모른다. 그러므로 무엇을 쓰라고 할지 알려주어야 한다.

이전처럼 new xxx해서 사용하는 쪽에 알려주는 것이 아니라 사용당하는 쪽 (BookMySqlRepository)에 알려주면된다.

 

어떻게 알려주나?

이 스프링이 우선권이 있어 하고 @Primary 어노테이션을 붙여준다.

와 이거진짜 좋다 ㄷㄷ;;

@Primary는 우선권을 결정하는 어노테이션이다.

 

이번 강의는 들으면서 진짜 이렇게까지해야하나 막막하다 했는데 이렇게 사용할수있다니...스프링을 개발해준 개발자님들께 영광을..

댓글