package hello.hellospring.controller;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller // annotation 역할 : spring bean으로 해당 클래스를 spring-container에서 관리
public class MemberController {
private final MemberService memberService;
@Autowired//Controller 와 Service 간의 연결 수행하는 어노테이션
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
생성자에 @AutoWired가 있으면 스프링이 연관된 객체(여기서는 MemberService)를 찾아서 알아서 연결해준다. 이런 식으로 외부에서 객체 의존관계를 주입해주는 것을 DI라고 한다. 이전 테스트 케이스 작성에서는 개발자가 직접 코딩으로 넣는 방식이었고, 여기서는 @AutoWired에 의해 스프링이 직접 의존관계를 주입해준다.
만약 Controller만 스프링 빈으로 등록하면 아래와 같은 에러가 발생한다.
이는 스프링 컨테이너가 MemberController는 찾았지만 MemberService는 찾지 못해서 발생했다.
이를 해결하기 위해 우리는 Service,Repository 객체도 스프링 빈으로 등록해야 한다.
서비스와 레포지토리 객체 스프링 빈 등록하기
@Service//서비스 스프링 빈으로 등록
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
@Repository//레포지토리 스프링 빈으로 등록
public class MemoryMemberRepository implements MemberRepository{}
Component Scan 원리
@Component 어노테이션이 붙어있으면 스프링 빈에 자동 등록된다.
@Service, @Repository, @Controller는 @Component를 가지고 있는 어노테이션들이다. 그래서 스프링 빈에 자동 등록된다.
@ SpringBootApplication 어노테이션은 @ComponentScan이라는 어노테이션을 포함하고 있다.
@ComponentScan 어노테이션은 해당 클래스와 같은 Level의 패키지의 하위 클래스들에 대해 컴포넌트 스캔을 진행하면서 객체를 스프링 빈에 등록한다.
참고로, 스프링은 스프링 컨테이너에 스프링 빈을 등록할때 기본으로 싱글톤으로 등록한다.(유일하게 하나만 등록해서 그걸 공유하는 방식) 따라서 동일한 스프링 빈이라면 동일한 인스턴스이다.
자바 코드로 직접 스프링 빈 등록하기
회원 서비스와 회원 레포지토리의 @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(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
SpringConfig가 직접 스프링 등록하기 위한 클래스임을 설정하는 @Configuration 어노테이션을 달아준다.
이후, 각각의 MemberService, MemberRepository에 대해서 @Bean 어노테이션을 달아주고 리턴 값으로 자기자신의 생성자 또는 메모리 구현체를 넘긴다.
언제 자바 코드로 직접 스프링 빈 등록하는 방식이 컴포넌트 방식보다 유리할까?
대부분의 정형화된 실무에서 사용하는 구조는 편리한 컴포넌트 방식으로 스프링 빈을 등록하지만, 정형화된 틀이 없거나 상황에 따라 구현 클래스를 직접 수정해야 하는 일이 있는경우에는 SpringConfig로 직접 스프링 빈을 등록하는게 유리하다.
예를들어 해당 프로젝트 DB대신 메모리 레포지토리를 사용하고 있기에 추후에 변경할 예정이므로 컴포넌트 스캔 방식보다 SpringConfig 방식이 좀 더 유리하다.