스프링 입문-스프링 빈
의존 관계 주입
package hello.hello_spring.controller;
import hello.hello_spring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
지금까지 만들던 백엔드의 컨트롤러이다. MemberService의 경우 하나만 있으면 되고 매번 new를 통해 새로 만들어 줄 필요가 없다.
이럴 경우 @Autowired 어노테이션을 사용하면 스프링 컨테이너 내부에 해당 객체(스프링 빈)가 있다면 자동으로 해당 객체를 연결해준다. 이렇게 외부에서 객체 의존관계를 넣어주는 것을 Dependency Injection이라고 한다.
스프링 빈
객체를 스프링 빈으로 스프링 컨테이너에 등록하는 방법은 두 가지가 있는데, 컴포넌트 스캔을 이용하는 것과 직접 자바 코드로 등록하는 것이다.
MemberService 코드에서 @Service 어노테이션을 붙여주면 스프링 빈으로 등록되어 위의 코드가 오류 없이 작동하게 되는데, 이것이 컴포넌트 스캔을 이용한 방식이다.
이전 포스팅의 웹 구조에서 해당 구조를 스프링이 지원한다고 했었는데, @Component 어노테이션을 내장한 @Controller, @Service, @Repository 등의 어노테이션들을 지원하기 때문이다. 해당 어노테이션들이 있으면 스프링 빈으로 자동 등록된다.
스프링 컨테이너는 스프링 빈을 등록할 때 싱글톤으로 등록한다. 따라서 모든 스프링 빈 객체는 같은 인스턴스이다. 설정을 통해 싱글톤이 아니게 할 순 있지만 보통 하지 않는다.
config를 통한 스프링 빈
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();
}
}
config 파일을 생성하여 스프링 빈을 등록해 보았다. 위와 같이 @Configuration 어노테이션이 붙어있으면 config 클래스가 되고, 해당 클래스 내에서 @Bean 어노테이션을 붙여 해당 메서드가 반환하는 객체를 스프링 빈에 등록하는 코드를 작성할 수 있다.
이렇게 config를 통해 스프링 빈을 등록하면 만약 이후에 클래스가 변경되거나 했을 때 설정만 간단하게 수정하면 되므로 이후 수정의 여지가 있는 상황에 자주 사용한다.
참고로 @Autowired를 통한 DI는 스프링 빈으로써 관리되는 객체에서만 작동한다. 내가 임의로 new를 통해 생성한 객체에선 작동하지 않고, 스프링 빈으로 등록되지 않아도 작동하지 않는다.
DI의 세 가지 방법
DI를 구축하는 것엔 세 가지 방법이 있는데, 지금까지 해왔던 생성자 주입, 필드 주입, setter를 통한 주입이 있다.
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}//생성자를 통한 주입
@Autowired private MemberService memberService;
//필드 주입
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}//setter를 통한 주입
필드 주입은 이후 변경의 여지가 없기 때문에 ide에서도 선호하지 않는 방식이라 표시된다. setter를 통한 주입의 경우 setter 메서드가 public으로 노출되어 있어 아무나 호출할 수 있다. 한번 조립되면 의존관계가 런타임에 변경되는 경우는 거의 없기에 리스크에 비해 사용할 필요가 적다. 따라서 생성자 주입이 가장 권장된다.