국개장
[2025 스프링부트 스터디] #4주차 본문
섹션 5. 스프링 빈과 의존관계
스프링을 쓰면 웬만한 것들은 다 스프링빈으로 등록해서 써야 한다. 그렇게 해야 얻는 이점이 많다고 한다.
컴포넌트 스캔과 자동 의존관계 설정
private final MemberService memberService = new MemberService();
위처럼 쓰면 각 컨트롤러에서 MemberService 객체를 별도로 다 만들게 되는 비효율이 발생한다.
그러므로 spring container에 등록을 하고 사용해서 객체 하나만 만들어지도록 하자.
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
이렇게 memberService 생성자 오버로딩을 작성하고, @Autowired를 추가해준다. 이렇게 하면 멤버 서비스를 스프링 컨테이너에 있는 멤버 서비스에 연결을 딱 해준다고 한다. (중복 데이터 방지?)
하지만 이것만으로는 충분치 않다. 서비스 코드를 보면 이는 그저 순수한 자바 코드이기 때문에, 스프링에서 인식하려면 서비스 클래스에 추가로 @Service라는 구문을 작성해주어야 한다고 한다. 이렇게 해주면 그제서야 얘를 서비스라고 인식하고 스프링 컨테이너에 멤버 서비스를 딱 등록해준다.
...
import org.springframework.stereotype.Service;
...
@Service
public class MemberService {
// private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
레포지토리도 마찬가지로 스프링이 인식하도록 @Repository 구문을 추가한다.
...
import org.springframework.stereotype.Repository;
...
@Repository
public interface MemberRepository {
Member save(Member member);
...
그리고, repository 또한 @Autowired를 하여 스프링 빈에 등록한다.
import org.springframework.beans.factory.annotation.Autowired;
...
@Service
public class MemberService {
// private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
스프링 빈을 등록하는 2 가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
2. 자바 코드로 직접 스프링 빈 만들기
우리는 방금 위에서 스프링 빈을 등록하는 방법 중 첫 번째 방법을 적용한 것이다.
1. 컴포넌트 스캔과 자동 의존관계 설정
- '@Component' 애노테이션이 있으면 스프링 빈으로 자동 등록된다.
- '@Controller' 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.
- '@Component'를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다.
- '@Controller'
- '@Service'
- '@Repository'
아무대나 @Component를 붙여서 패키지를 만든다고 다 스프링 빈이 되는 것은 아니다. 우리는 이 폴더 구조에서 SpringBootStudyApplication.java를 실행시키고 있기 때문에 저 파일과 연결된 패키지 내의 하위 패키지들 까지만 처리를 하여 스프링 디너로 등록해준다.
스프링 빈은 기본적으로 싱글 톤이라고 한다. 인스턴스를 하나만 만든다는 것이다. 설정에 따라 여러 개 존재하게 할 수 있지만, 대부분 싱글 톤이 쓰인다고 한다.
자바 코드로 직접 스프링 빈 등록하기
package wink.spring_boot_study;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import wink.spring_boot_study.repository.MemberRepository;
import wink.spring_boot_study.repository.MemoryMemberRepository;
import wink.spring_boot_study.service.MemberService;
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
위와 같이 '@Bean' 애노테이션을 쓰면, 스프링 빈을 직접 만들어서 지정해줄 수 있다. 위의 컴포넌트 스캔 방식과 동일한 구조가 됐다.
참고 : XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않으므로 생략한다.
생략.
참고: DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다. 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.
@Controller
public class MemberController {
@Autowired private MemberService memberService;
}
필드 주입 방식, 이렇게 하면 스프링 들 때만 얘를 넣어주고 이거를 중간에 내가 바꿔치기 할 수 있는 방법이 아예 없다.
@Controller
public class MemberController {
private MemberService memberService;
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
// memberService.setMemberRepository(); // 호출되지 말아야할 메서드가 호출되면 안된다.
}
}
setter 주입 방식, 누군가가 멤버 컨트롤을 호출했을 때 이게 public으로 열려 있어야 하는데, 그러면 누군가가 set함수 호출해서 바꾸면 문제가 생기게 된다.
참고 : 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new DBMemberRepository();
}
}
구현체 변경 시 직접 빈 구현 방식으로는 저렇게 memory를 실제 DB로 바꿔주기만 하면 된다고 한다. 컴포넌트 방식일 경우에는 하나하나 수정해주어야 하는 번거로움이 있다.
참고: '@Autowired'를 통한 DI는 'helloController', 'MemberService'등과 같이 스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.
컴포넌트나 @Bean을 해주지 않고 @Autowired만 해준 상태에서는 의존성 주입이 동작하지 않는다는 의미이다.
더 이상의 자세한 설명은 생략한다. (스프링 핵심 원리 강의 홍보)