회원 가입을 해서 리포지토리에 저장하고 꺼내올 수 있는 로직을 구성해보았다. 그러한 입력을 받고 반환된 결과를 출력하려면 컨트롤러와 뷰 템플릿이 필요하다.
멤버 컨트롤러가 멤버 서비스를 통해 회원가입하고, 데이터를 조회하는 것을 멤버 컨트롤러가 멤버 서비스를 의존한다고 얘기한다.
의존 관계가 스프링 부트에서 어떻게 사용되는지 알아본다.
MemberController를 생성할 때, @Controller라는 애너테이션을 붙여주었다. 이전에 만들었던 MemberService와 MemberRepository의 클래스들도 @Service, @Repository라는 애너테이션들이 존재한다.
Controller는 외부 요청을 받고, Service에서 비즈니스 로직을 실행하고, Repository에서 데이터를 저장한다. 세 가지는 정형화된 패턴이기 때문에 이렇게 따로 애너테이션이 존재한다.
이 세가지 애너테이션들을 살펴보면 안에 @Component라는 애너테이션이 포함되어있다. 스프링은 이렇게 @Component 애너테이션을 가진 컴포넌트들을 스캔하여 객체를 생성한 후 스프링 컨테이너 안에 넣어둔다.
앞서 memberService는 하나의 인스턴스만 가지고 사용해야 한다고 했다. 만일 객체가 여러 개 생성되었다면, 이 인스턴스에 회원가입이 되어있는데 저 인스턴스에는 회원가입이 되어있지 않는 등 문제가 생길 수 있어서 그랬다. 마찬가지로 MemberController에서도 같은 memberService 인스턴스를 사용하도록 해야 한다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService){
this.memberService = memberService;
}
}
스프링은 컴포넌트 애너테이션이 달린 객체들을 생성하여 컨테이너에 넣어두는데(=스프링 빈에 등록), 그 중 중 @Autowired라는 애너테이션을 가진 객체들을 서로 연결해준다.
멤버 컨트롤러 생성자 메서드에 Autowired를 붙였고, 이 생성자 메서드는 memberService를 받는다. 즉 컨트롤러가 생성이 될 때 스프링은 빈에 등록된 객체 중 memberService 객체를 찾아 컨트롤러에 집어 넣어주게 된다. 의존관계를 주입해주는 이것을 의존성 주입이라고 한다.
마찬가지로 MemberService는 MemoryMemberRepository가 필요하기 때문에, MemberService의 생성자 메서드에도 @Autowired를 붙여준다.
스프링 애플리케이션은 실행된 패키지를 포함하여 하위 패키지까지 컴포넌트 스캔을 한다. 따라서 하위 패키지가 아니거나 동일한 레벨의 패키지에 있는 @Component 애너테이션은 스캔되지 않고, 스프링 빈으로 등록되지도 않는다.
기본적으로 스프링 빈은 전부 싱글톤으로 생성된다.
위에서는 스프링이 애너테이션을 참고하여 자동으로 스프링 빈을 등록하고 연결해주었다. 이번에는 직접 스프링 빈을 다뤄보기 위해, @Controller를 뺀 다른 애너테이션들을 제거하고 SpringConfig를 만든다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
- @Configuration이라는 애너테이션을 붙이면, 스프링이 시작할 때 등록된 @Bean 객체들을 생성하여 컨테이너에 등록한다.
- 먼저 MemberService를 등록하였는데, MemberService는 MemoryMemberRepository가 필요하다. 따라서 밑에 빈을 하나 더 등록하고, 매개변수에 MemoryMemberRepository 메서드를 호출하여 넘겨준다.
- 스프링이 MemberService의 생산자 메서드에 있는 MemoryMemberRepository를 등록된 빈들에서 찾아 연결해준다.
어느 방법으로 하든 Spring을 시작하면 오류 없이 서버가 잘 켜지는 것을 확인할 수 있다.
두 방법 다 각각의 장단점을 가지고 있다.
과거에는 Spring 애너테이션이 아니라 xml 문서를 가지고 bean을 설정했는데, 현재는 거의 사용하지 않고 대부분이 위처럼 자바 코드로 설정한다.
의존성 주입에는 필드 주입, setter 주입, 생성자 주입 3가지가 있다. MemberService에서 사용한 방식은 생성자 주입이다.
만약 MemberController에서 생성자가 아니라
private final MemberService memberService;
에 @Autowired를 붙였다면, 이는 필드 주입이다.
필드 주입은 스프링이 처음 시작할 때만 사용되고, 활용이 제한되기 때문에 별로 추천하지 않는다고 한다.
Setter 주입은 Setter 메서드에 @Autowired를 붙인 것으로, 객체 생성과 의존성 주입을 별개로 취급할 수 있다. 한 번 세팅이 되고 나면 바뀔 일이 없는데도 Setter가 퍼블릭이기 때문에 중간에 아무나 접근하고 바꿀 수 있다는 것이 단점이다.
이러한 단점때문에 생성자 주입이 추천된다. 처음 애플리케이션이 빌드되면서 세팅이 한꺼번에 이루어지는 시점에 한 번만 사용되고 더이상 사용되지 않기 때문이다. 변경 및 호출되지 않아야 할 메서드가 호출될 일이 없으므로 가장 바람직하다.
의존 관계가 실행 중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.
만약 동적으로 변하는 경우가 있다면, 차라리 Config 파일을 수정한 후 다시 서버를 킨다.
실무에서는 정형화된 컨트롤러, 서비스, 레포지토리의 코드에 주로 컴포넌트 스캔을 사용한다. 정형화되지 않았거나, 상황에 따라 구현 클래스가 변경되어야 하면 설정(@Configuration)을 통해 스프링 빈으로 등록한다.
우리는 DB를 따로 정하지 않고 구현하였다는 시나리오이므로, 기존의 코드를 변경할 필요 없이 구현 클래스를 변경하기 위하여 @Configuration을 사용해 빈을 등록하는 방법을 사용한다.
이 시나리오에서, 만약 컴포넌트 스캔을 사용하고 있었다고 하면 수정해야 할 부분이 많아진다.
@Autowired를 통한 DI는 스프링이 관리하는 객체에서만 동작하고, 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.