[스프링 입문] Section 4. 스프링 빈과 의존관계

 

본 포스팅의 내용은

인프런 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 기반으로 작성했습니다.

 

📜 개요

스프링 빈을 등록하는 두 가지 방법으로

1. 컴포넌트 스캔과 자동 의존관계 설정

2. 자바 코드로 직접 스프링 빈에 등록하는 방법이 있다.

 

🌱 컴포넌트 스캔과 자동 의존관계 설정

서비스, 도메인 리포지토리를 만들었고 이제는 화면을 붙이고 싶다. 

그렇다면 Controller와 View를 정의해야 한다.

 

먼저, 멤버 컨트롤러를 만들어야 하는데 멤버 컨트롤러멤버 서비스를 통해서 회원 가입을 하고 데이터를 조회할 수 있어야 한다. 이 구조에서 의존관계가 형성되는데 멤버 컨트롤러가 멤버 서비스에 의존한다고 표현한다.

 

가령 멤버 컨트롤러에서 멤버 서비스를 다음과 같이 생성할 수 도 있다. 하지만 이와 같이 멤버 서비스를 생성하는 것은 문제가 있는데,  다른 여러 컨트롤러들이 멤버 서비스를 사용할 수 있고 각각의 컨트롤러마다 서로 다른 인스턴스를 사용하게 될 것이다. 하지만 멤버 서비스는 여러 인스턴스를 사용할 필요 없이 하나의 인스턴스만 생성해서 공용으로 사용해도 되는 인스턴스이다.

@Controller
public class MemberController {
    private final MemberService memberService = new MemberService(...);
}

 

따라서 직접 객체를 생성하는 것이 아닌, 외부로부터 객체를 받게 되면 자연스럽게 의존도가 낮아진다.

public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

 

이처럼 외부로부터 객체를 주입받는 것을 DI(Dependency Injection)이라고 하는데, 스프링은 스프링 컨테이너라는 곳에 객체를 으로 보관했다고 필요할 때 객체를 주입하는 DI 기능을 제공한다.

 

결론부터 말하면 다음과 같이 @Autowired를 붙여주면 스프링 컨테이너에 등록된 memberservice를 주입해 준다.

그렇다면 memberService는 어떻게 컨테이너의 빈에 등록하는가? 바로 @Service 어노테이션을 붙여주면 된다.

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

 

@Component 어노테이션이 붙으면 스프링은 해당 클래스의 객체를 자동으로 빈으로 등록한다.

마찬가지로 @Controller, @Service, @Repository 어노테이션도 내부적으로 @Component 어노테이션을 포함하기 때문에 스프링 빈으로 자동 등록된다.

 

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

Configuration 클래스에서 직접 스프링 빈을 등록해 보자.

기존에 작성했던 회원 서비스와 회원 리포지토리의 @Service, @Repository 어노테이션은 제거하고, SpringConfig 클래스를 생성하고 @Configuration 어노테이션을 붙여주면 기본 설정은 끝난다.

 

객체를 생성하고 반환하는 메서드에 @Bean 어노테이션만 붙여주면 스프링 컨테이너에 빈의 형태로 객체가 등록된다.

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

🖥️ 정리

참고 1. 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유한다) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

 

참고 2. DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다. 의존관계가 실행 중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.

 

필드 주입

@Autowired
private MemberService memberService;

 

setter 주입, 단점 setter 주입을 위해선 메서드가 public으로 열려있어야 한다. 일반적으로 한 번 의존 관계가 주입되면 도중에 바뀔 일이 잘 없는데 public으로 메서드를 계속 열어놔야 하는 단점이 있음.

@Autowired
public void setMemberService(MemberService memberService) {
    this.memberService = memberService;
}

 

생성자 주입, 가장 많이 사용되는 방법으로 생성되는 시점에 DI를 하고 이후엔 다시 생성자가 호출될 일 없으니 안전하다.

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

 

 

참고 3. 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.

 

참고 4. @Autowired를 통한 DI는 helloController , memberService 등과 같이 스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.