1. [Spring Intro] Section 04. 스프링 빈과 의존관계
강의: 김영한의 스프링 입문
1.1. # 컴포넌트 스캔과 자동 의존관계 설정
1.1.1. 1. 컴포넌트 스캔과 자동 의존관계 설정
- 지난 Section3에서 구축했던 백엔드 기능에 화면을 붙여보자.
- 화면 구축을 위해서는 Controller와 View Template이 필요하다.
- 그러려면 MemberController를 만들어야 하는데, MemberController는 MemberService를 통해서 회원가입과 데이터 조회가 가능해야 한다.
- 이러한 관계를 'MemberController가 MemberService에 의존한다'라고 표현한다.
@Controller : 스프링이 처음에 뜰 때 스프링 컨테이너가 생기는데, 이때 @Controller가 있으면 객체(여기에서는 MemberController)를 생성해서 스프링 컨테이너에 넣어 둔다.
이렇게 컨테이너에 넣어두면 스프링이 관리하게 되며, 이 과정을 스프링 컨테이너에서 스프링 빈이 관리된다고 표현한다.
- 아래 사진과 같이 스프링 컨테이너 내부에 초록색 원인 컨트롤러 (= 스프링 빈)이 있음을 확인할 수 있다

1.1.2. 2. 회원 컨트롤러에 의존관계 추가
- src/main/java/hello.hellospring/controller/MemberController.java
- 다음과 같이 Membercontroller 클래스를 작성한다
<java />
package hello.hellospring.controller;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
private final MemberService memberService; // 스프링 컨테이너에서 받아써야하는 입장이다
@Autowired
// 생성자 -> 여기에서 MemberService는 순수한 자바코드로 작성한 클래스 이므로 스프링이 가지고 있는 멤버 서비스와 연결하려고 할 때 에러가 발생한다
public MemberController(MemberService memberService){
this.memberService = memberService;
}
}
- 생성자에 @Autowired 가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다
- 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다
- 이전 테스트에서는 개발자가 직접 주입했고, 여기서는 Autowired에 의해 스프링이 주입해준다
1.1.3. 3. 오류 발생
- 코드를 실행시켜 보면, @Autowired를 사용했음에도 다음과 같은 오류가 발생한다.

- memberService가 스프링 빈으로 등록되어 있지 않다는 오류인데, 그 이유를 자세히 살펴보자.
Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.
- 다음 사진과 같이 현재 상황은 memberService가 스프링 빈으로 등록되어 있지 않다
- helloController는 스프링이 제공하는 컨트롤러이기 때문에 스프링 빈으로 자동 등록이 되어 있었다
- @Controller 주석을 통해 스프링 빈으로 자동 등록을 할 수 있다

- MemberController의 @Autowired를 통해 스프링 컨테이너에서 memberService를 가져와야 하는데, MemberController는 a주석을 걸어두었기 때문에 자동 등록되지만 memberService는 순수한 자바 클래스이기 때문에 스프링이 이를 인식할 수 있는 방법이 없다.
- 이때, MemberService에서 @Service를 선언하고, MemoryMemberRepository에 @Repository를 선언하면 각각이 스프링 컨테이너에 등록된다


1.1.4. 4. 스프링 빈을 등록하는 두 가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
2. 자바 코드로 직접 스프링 빈 등록하기
1.1.5. 5. 컴포넌트 스캔 원리
- @Component 애노테이션이 있으면 스프링 빈으로 자동 등록된다.
- @Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.
- @Component를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다.
- @Controller:외부 요청을 받는다
- @Service: 비지니스 로직을 만든다
- @Repository: 데이터를 저장한다
1.1.6. 6. 회원 서비스 스프링 빈 등록
- java/hello/hellospring/service/MemberService.java
- 다음과 같이 MemberService를 수정한다
<java />
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
참고: 생성자에 @Autowired를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입(DI)한다.
(생성자가 1개만 있으면 @Autowired는 생략할 수 있다.)
1.1.7. 7. 회원 리포지토리 스프링 빈 등록
- java/hello/hellospring/repository/MemoryMemberRepositoryTest.java
- 다음과 같이 MemoryMemberRepository를 수정한다
<java />
@Repository
public class MemoryMemberRepository implements MemberRepository {}
1.1.8. 8. 스프링 빈 등록 이미지
- 이렇게 memberService와 memberRepository가 스프링 컨테이너에 스프링 빈으로 등록되었다.
- 위 과정들이 객체 의존관계를 외부에서 넣어주는 DI(Dependency Injection), 의존성 주입이다.

스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유한다)
따라서 같은 스프링 빈이면 모두 같은 인스턴스다.
설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우 제외하면 대부분 싱글톤을 사용한다.
ex ) 위 스프링 빈 등록 이미지의 상태에서 orderService가 @Autowired를 통해 memberRepository가 필요하다고 하면 같은 인스턴스를 넣어준다.
(설정으로 memberRepository를 @Autowired 할 때마다 다른 인스턴스를 넣도록 할 수 있다)
1.1.9. 9. 스프링 빈 자동 등록 유의사항
- 스프링 빈 자동 등록은 하위 패키지에 대해서만 이루어진다
- 다른 패키지의 경우 @ComponetScan이라는 주석이 포함되어야 한다 ex) @SpringBootApplication

- 우리는 HelloSpringApplication을 실행하기 때문에 hello.hellospring 패키지를 포함한 모든 하위 요소들은 스프링이 전부 컴포넌트 스캔을 실행한다
- 하지만 그 이외는 컴포넌트 스캔을 실행하지 않는다. 즉, 위 사진에서 demo 위치와 패키지에 그 하위 요소인 demo.java에 @Service와 같은 컴포넌트를 추가해도 스캔을 하지 않는다
1.2. # 자바 코드로 직접 스프링 빈 등록하기
- 시작하기 이전에 앞서 작성했던 회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 MemeberController 클래스는 그대로 둔다
1.2.1. 1. 자바 코드로 스프링 빈 등록
- java/hello.hellospring/SpringConfig.java
- 다음과 같이 SpringConfig 클래스를 추가한다
- @Bean을 통해 memberService를 등록해준다 이때 MemberService에는 생성자에서 memberRepository가 필요하다
- 따라서 아래에 memberRepository도 Bean으로 등록해준다
- 이렇게 하면 스프링 빈에 등록되어 있는 memberRepository를 memberService에 넣어줄 수 있으므로 @Autowired와 같은 효과를 가져올 수 있다
<java />
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();
}
}
- 위와 같이 설정했을 때, 자바 코드를 통한 스프링 빈 등록으로 이전 컴포넌트 스캔과 같은 구조를 완성할 수 있다

1.2.2. 2. 컴포넌트 스캔으로 자동 의존관계 설정 vs 자바 코드로 스프링 빈 등록
- 여기서는 향후 메모리 리포지토리를 다른 리포지토리로 변경할 예정이므로, 컴포넌트 스캔 방식 대신에 자바 코드로 스프링 빈을 설정한다
- java코드가 아닌 XML로 설정하는 방식이 있지만 최근에는 잘 사용하지 않는다
- DI에는 필드 주입, setter 주입, 생성자 주입의 3가지 방법이 있다
2.1. 필드 주입
- 변형을 자유롭게 할 수 없으며 실무에서 권장하지 않는다
<java />
@Controller
public class MemberController {
@Autowired private MemberService memberService;
...
2.2. setter 주입
- 누군가가 MemberController를 호출했을 때 setMemberService가 public으로 열려있어야 하며, setMemberService는 한번 세팅되면 잘 바뀌지 않는데 public 하게 노출된다는 단점이 있다
- 이럴 경우 오히려 노출되어 변경되면 더 큰 문제로 이어질 수 있다
<java />
@Controller
public class MemberController {
private MemberService memberService;
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
}
2.3. 생성자 주입
- 조립 시점에 생성자로 한 번만 조립해 놓고 이후로는 변경하지 못하도록 막는 것이 가능하다
- 의존관계가 실행 중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다
<java />
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
2.4 참고
- 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
- 그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.
- 우리는 아직 데이터 저장소가 선정되지 않았다는 것을 전제로 프로젝트를 진행했으므로 이후에 메모리 리포지토리를 다른 리포지토리로 바꿔야 한다
- 이때 기존에 운영 중인 코드를 하나도 변경하지 않고 바꿀 수 있도록 하기 위해
컴포넌트 스캔 방식 대신 자바 코드로 스프링 빈을 설정한다. - SpringConfig 파일의 memberRepository에서 return new MemoryMemberRepository()를 이후에 return new dbMemberRepository()로만 변경해 주면 된다
@Autowired를 통한 DI는 helloController, memberService등과 같이 스프링이 관리하는 객체에서만 동작한다
스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다

'Study > Backend Note' 카테고리의 다른 글
[Spring Intro] Section 06. 스프링 DB 접근 기술 (0) | 2023.03.06 |
---|---|
[Spring Intro] Section 05. 회원 관리 예제 - 웹 MVC 개발 (0) | 2023.03.04 |
[Spring Intro] Section 03. 회원 관리 예제 - 백엔드 개발 (0) | 2023.02.28 |
[Spring Intro] Section 02. 스프링 웹 개발 기초 (0) | 2023.02.26 |
[Spring Intro] Section 01. 프로젝트 환경설정 (0) | 2023.02.24 |