Spring

[Spring] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 #4

solfa 2024. 9. 6. 04:38

본 글은 해당 강의를 보고 작성한 글입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

[지금 무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 | 김영한 - 인프

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확

www.inflearn.com


#4 스프링 빈과 의존관계

1. 스프링 빈을 등록하고, 의존관계를 어떻게 설정하는지 알아보기

 

회원 컨트롤러에 의존관계 추가

- main/java/hello.hellospring/controller/MemberController 클래스 생성

@Controller
public class MemberController {

}

- 이렇게까지만 코드를 작성해놓으면 기능은 없지 스프링이 처음 뜰 때 스프링 컨테이너라는 통이 생기는데, '@Controller'가 있으면 이를 보고, MemberController 객체를 생성하여 스프링에 넣어두고 스프링이 관리한다!

 

-> 스프링 컨테이너에서 스프링 빈이 관리된다!

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) { // command + n
        this.memberService = memberService;
    }
}

- MemberService를 가져다 써야 함

- new MemberService()로도 할 순 있지만, 스프링이 관리를 하게 되면 다 스프링 컨테이너에 등록하고 스프링 컨테이너로 부터 받아서 쓰도록 바꿔야 한다. 왜냐하면 new로 하면 멤버컨트롤러 말고 다른 여러 컨트롤러들이 멤버서비스를 가져다 쓸 수 있음 예를 들어 주문 컨트롤러에서도 가져다쓸수있고,,  멤버서비스는 여러개 생성할 필요없고 하나만 생성해서 공용으로 쓰면됨

-따라서 스프링 컨테이너한테 등록하고 쓰면 됨! 스프링 컨테이너에 등록하면 딱 하나만 등록됨.

- 생성자로 연결해주기 : command + n + constructor (생성자 만들기) 후 '@Autowired' 추가

- MemberController는 스프링 컨테이너가 뜰 때 생성을 하는데, 그 때 이 생성자를 호출함. 이때 생성자에 @Autowired가 있으면 멤버 서비스를 스프링이 스프링 컨테이너에 있는 멤버 서비스를 가져다가 연결시켜줌

- 즉, 생성자에 @Autowired가 있으면 객체 생성 시점에 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 주입해줌! -> DI(Dependency Injection)

※ 생성자 1개라면 @Autowired 생략가능

- 그러나 이대로 실행하면 회원 서비스를 찾을 수 없다는 오류가 발생
- @Autowired는 스프링 컨테이너에서 멤버 서비스를 가져옴
- hellocontroller는 스프링이 뜰 때 스프링 컨테이너에 등록됨 그런데 @Autowired니까 스프링 컨테이너에서 관리하는 멤버 서비스를 가져다가 스프링이 넣어줌. 즉, 연결시켜줌
- 그런데  안됨. 왜냐, 멤버 서비스를 애노테이션(@)없이 순수한 자바 클래스(코드)이기에 스프링이 얘를 알 수 있는 방법이 없음. 
- 따라서 해결책 : @Service를 넣어준다! -> 스프링에 올라올 때 '서비스네!'하고 스프링이 스프링 컨테이너에 멤버 서비스를 등록해줌
-리포지토리에는 @Repository 추가
- controller/service/repository -> 되게 정형화된 패턴임. 컨트롤러로 외부 요청을 받고, 서비스에서 비즈니스 로직을 만들고 리포지토리에서 데이터를 저장하는 패턴
-이렇게 해놓으면 스프링이 컨트롤러/서비스/리포지토리를 쫙 가지고 올라옴
- 그리고 컨트롤러와 서비스를 연결시켜줘야 하는데, 연결시켜줄때 @Autowired 씀
- 생성자에 @Autowired쓰면 멤버 컨트롤러가 생성될 때 스프링빈에 등록되어 있는 멤버 서비스 객체를 가져다가 넣어줌
-이게 바로 DI! 의존관계 주입해주는 것!
- 멤버 서비스에 가보면 얘는 멤버 리포지토리가 필요로 함. 생성자에 @Autowired추가
- 그러면 멤버 서비스를 스프링이 생성할 때 '어 서비스네!' 하면서 스프링 컨테이너에 등록을 하면서 생성자를 호출하는데 이때 @Autowired가 있으면 리포지토리가 필요함을 인지하고 스프링 컨테이너에 있는 멤버 리포지토리를 넣어줌
- 현재는 구현체로 메모리멤버리포지토리가 구현체로 있으니까 이걸 서비스에 주입해줌
- 이로써 세가지가 연결됨!
- 이후 실행해보면 문제 없이 잘 실행됨 하지만 아직은 연결만 된 상태인 것! 회원 컨트롤러 관련된 기능이 없기 때문에

- 컨트롤러는 자동등록 & 서비스와 리포지토리가 스프링 컨테이너에 스플이 빈으로 등록되었다!

 


스프링 빈을 등록하는 2가지 방법 

1. 컨포넌트 스캔과 자동 의존관계 설정
--> 컨포넌트 스캔 : @ ~~ 를 추가한 것

--> ex) @Service 대신 @Component도 가능! @Service 안에 @Component가 등록되어 있기 때문에 가능

--> @Component 애노테이션이 있으면 스프링 빈으로 자동 등록됨
--> 스프링이 올라올 때 컨포넌트 관련 애노테이션이 있으면 얘네는 다 스프링이 객체를 하나씩 생성해서 스프링 컨테이너에 등록함.
--> @Autowired는 그들간의 연관관계를 만듦. 즉 연결해줌, 스프링을 쓸 땐 웬만한건 모두 스프링빈에 등록하고 써야 이점이 많음
--> 컨포넌트 스캔의 대상  : hello.hellospring 패키지를 포함한 하위 패키지만!!


스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본적으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유함!

따라서 같은 스프링 빈이면 모두 같은 인스턴스이다.

설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

 

helloController는 스프링이 제공하는 컨트롤러여서 스프링 빈으로 자동 등록, @Controller가 있으면 자동 등록됨!


2. 자바 코드로 직접 스프링 빈 등록하기 

※ @Service, @Repository, @Autowired 애노테이션 제거하고 진행한다.

hello.hellospring/SpringConfig 클래스 생성

package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService(){
        return new MemberService();
    }
}

- 스프링이 뜰 때, @Configration 읽고 @Bean을 보고 스프링 빈에 등록하라는 뜻으로 인식

- command + p 해보면 MemberRepository 생상자에 넣어줘야 한다는 거 확인

package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

- 스프링이 뜰 때, 멤버 서비스와 멤버 리포지토리를 둘 다 스프링 빈에 등록하고, 스프링 빈에 등록되어 있는 멤버 리포지토리를 멤버 서비스에 넣어줌! 이로써 3가지가 모두 연결된 상황 완성

※ controller는 스프링이 관리하는 것이기에 어쩔 수 없이 컴포넌트 스캔으로 올라가고, 컴포넌트 스캔이기에 @Autowired로 연결

 

DI 3가지

- <DI 3가지> : 필드 주입, setter 주입, 생성자 주입 

- 위의 예시는 생성자 주입!

- 생성자를 빼고 필드에다가 @Autowired -> 필드 주입 (별로 안 좋음)

- setter 주입 :  command + n -> setter + @Autowired

- setter 단점 : public으로 열려있어야 함. setMemberService는 한 번 호출 후 변경할 일 없는데, 변경으로 인한 문제 발생할 수 있음.

 

-> 결국 의존관계가 실행중에 동적으로 변하는 경우가 거의 없으므로 '생성자 주입'이 가장 좋다

 

- 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용함

- 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록

- 지금은 데이터 저장소가 선정되지 않았다는 가상의 시나리오가 존재하여 일단 메모리 저장소로 하고, 추후에 교체 예정임. 저장소 확정 후 리포지토리를 변경할 때 기존 코드를 거의 수정하지 않을 수 있음!

-> 이때 '자바 코드로 직접 스프링 빈 등록하기' 사용!

이런 식으로 SpringConfig 파일 코드만 조금 수정하면 됨

 

주의 : @Autowired` 를 통한 DI는 `helloController` , `memberService` 등과 같이 스프링이 관리하는 객체 에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다. (스프링 빈으로 등록된 것에만 적용된다는 것)

 

728x90