[Spring] 스프링 빈과 의존관계
*인프런 김영한의 스프링 입문 강의를 기반으로 작성되었습니다.
스프링 빈(Bean)이란?
- 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다. 스프링 컨테이너가 관리하는 자바 객체를 말하며 하나 이상의 빈(bean)을 관리한다.
- 빈은 인스턴스화된 객체를 의미하며 스프링 컨테이너에 등록괸 객체를 스프링 빈이라고 한다.
여기서 스프링 컨테이너란?
- 자바의 객체의 생명주기를 관리하며 생성된 자바 객체들에게 추가적인 기능을 제공하는 스프링 프레임워크의 핵심 컴포넌트
- 즉, 내부에 존재하는 빈의 생명주기(생성, 관리, 제거 등)를 관리하고 생성된 빈에게 추가적인 기능을 제공
지금까지 서비스를 통해서 회원가입, 리포지토리에 저장 및 꺼내오기를 테스트를 통해 제대로 작동하는지 확인했다.
이제는 화면을 만들어볼려고 한다. 그러기 위해서 컨트롤러와 뷰 탬플릿이 필요하다.
컨트롤을 통해서 외부 요청을 받고, 서비스에서 비즈니스 로직을 만들고, 레포지토리에서 데이터를 저장하는 것이 전형적인 방식이다.
컴포넌트 스캔과 자동 의존관계 설정
회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 추가한다.(회원 컨트롤러는 회원 서비스를 통해 회원가입 및 데이터 조회를 하기 때문에 회원 서비스를 의존하고 있다)
src/main/java/hello.hello_spring/controller 위치에 MemberController.java 파일을 만들어 다음과 같이 코드를 작성해준다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
@Controller 어노테이션이 적용된 클래스는 컨트롤러임을 나타내며 빈으로 등록된다. 생성자에 @Autowired가 있으면 memberService를 스프링이 스프링 컨테이너에 있는 MemberService를 가져와서 연결시켜 준다. 이렇게 객체 의존관게를 외부에서 넣어주는 것을 의존성 주입(Dependency Injection = DI)이라고 한다.
참고
helloController는 스프링이 제공하는 컨트롤러여서 스프링 빈으로 자동 등록된다.
회원 서비스 빈 등록
MemberService 클래스는 순수 자바 클래스이기 때문에 MemberService가 뭔지 알지 못한다. 그래서 memberService.java 파일에 들어가서 @Service 어노테이션을 통해 빈으로 등록해준다. 코드는 다음과 같다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
참고
생성자에 @Autowired를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아 서 주입한다. 생성자가 1개만 있으면 @Autowired 는 생략할 수 있다.
회원 리포지토리 빈 등록
회원 서비스도 회원 리포지토리를 의존하고 있기 때문에 MemberRepository 클래스에 @Repository 어노테이션을 붙여서 스프링 컨테이너에 등록한다. 코드는 아래과 같다.
@Repository
public class MemoryMemberRepository implements MemberRepository {}
참고
스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유한다) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지 만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.
컴포넌트 스캔원리
@Component 어노테이션이 있으면 스프링 빈으로 자동 등록된다. @Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.
@Component를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다. @Controller, @Service, @Repository
자바 코드로 직접 스프링 빈 등록하기
회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행한다.
src/main/java/hello.hello_spring 위치에 SpringConfig.java 파일을 만들어서 아래와 같이 작성해준다.
package hello.hello_spring;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import hello.hello_spring.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();
}
}
스프링이 뜰 때 @Configuration 어노테이션을 읽고 @Bean 어노테이션을 통해 memberRepository를 스프링 빈에 등록 대상으로 인식한다. 마찬가지로 스프링 빈에 등록된 memberService이 컴포넌트 스캔방식으로 빈에 등록된 memberController에 주입된다.
그러면 스프링이 뜰 때 memberService, memberRepository를 둘 다 스프링 빈으로 등록하고 스프링 빈으로 등록되어 있는 memberRepository를 memberService에 넣어준다.
참고
DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다. 의존관계가 실행중에 동적으 로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.
실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 그 리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.
@Autowired를 통한 DI는 helloController, memberService 등과 같이 스프링이 관리하 는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.
정리
- 스프링 빈을 등록하는 두가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
스프링이 띄워질 때 @Component 와 관련된 어노테이션이 있으면, 그것들은 전부 객체를 생성해서 스프링 컨테이너에 등록해주고 @Autowired는 연관관계를 만든다. 그래서 MemberController가 MemberService를 사용할 수 있고, MemberService가 MemberRepository를 사용할 수 있게 된다.
2. 자바 코드로 직접 스프링 빈 등록하기