최태현님의 스프링 기초 강의를 듣다가 너무 좋은 내용이 나와 강의 내용을 정리했다.
스프링 컨테이너를 사용하는 이유(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와 동시에 적용된다면 호출하는 쪽에서 적용한 것이 우선된다.
'백엔드 개발' 카테고리의 다른 글
#050. 코프링: direnv로 로컬 환경변수 주입하기 (0) | 2023.07.18 |
---|---|
#049. 코프링: 코틀린으로 커뮤니티 신고기능 구현하기 (0) | 2023.07.17 |
#047. 코프링: ktlint 적용하기 & pre-commit 룰 만들기 (0) | 2023.07.09 |
#046. 실행계획 EXPLAIN 까막눈 벗어나기 🦅 (0) | 2023.07.02 |
#045. 리팩터링: NULL + Boolean 대신 ENUM 타입 사용하기 (1) | 2023.07.02 |