src/main/java/hello/hellospring/controller/MemberController
package hello.hellospring.controller;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
private final MemberService memberService = new MemberService();
// MemberService()에 생성자 매개변수x -> 오류
}
이런 식으로 @Controller 애노테이션을 붙이면 스프링 부트가 실행될 때 스프링 컨테이너가 MemberController 객체를 생성해서 스프링 컨테이너에 넣어두며, 해당 객체는 스프링이 관리하는 대상이 된다.
→ 이것을 스프링 컨테이너에서 스프링 빈이 관리된다. 라고 한다.
스프링에 의해 관리되도록 하려면, 그 대상을 스프링 컨테이너에 등록해야 하며, 사용할 때도 스프링 컨테이너로부터 받아서 쓰도록 설계해야 한다.
→ 왜냐하면 만약 위 코드처럼 new를 통해 객체를 생성하면 MemberController 말고 다른 컨트롤러들(주문 컨트롤러, 홈 컨트롤러 등)에서도 MemberService를 가져다가 쓸 수 있게 된다. → 굳이 여러 개의 인스턴스를 생성할 필요 없고 하나만 생성해 놓고 공용으로 사용하면 된다. → 즉, 한번만 만들어서 스프링 컨테이너에 등록해 두는 것이 더 효율적이다.
그러면 연결을 어떻게 해줄까?
src/main/java/hello/hellospring/controller/MemberController
package hello.hellospring.controller;
import hello.hellospring.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;
}
}
@Autowired 애노테이션을 생성자에 사용하면, 객체 생성 시점에 생성자에 필요한 객체를 스프링 컨테이너에 등록된 스프링 빈 중에서 찾아서 넣어준다.(스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다.) 이렇게 객체 의존 관계를 외부에서 넣어주는 것을 DI(의존성 주입)이라고 한다.
이전 테스트에서는 개발자가 직접 넣어줬고, 여기서는 @Autowired를 이용해서 스프링이 넣어준다.
하지만 HelloSpringApplication을 실행하면 오류가 발생한다.(생성자 매개변수의 memberService에 빨간줄)
왜?? → memberService가 스프링 빈으로 등록되어 있지 않기 때문에!
스프링 컨테이너는 스프링 빈으로 등록되어 있지 않은 대상은 찾을 수가 없다. → 스프링이 memberService를 찾아서 넣어줘야 하는데 memberService가 스프링 빈으로 등록되어 있지 않으므로 넣어줄 수가 없다.(memberService는 현재 순수한 자바 클래스이고, 스프링이 이 친구를 알 수 있는 방법이 없다.)
그럼 어떻게 해야할까?
→ memberService 클래스에 @Service 애노테이션을 추가해준다. 그러면 스프링이 올라올 때 스프링이 memberService를 스프링 빈으로 등록해 준다.
→ 마찬가지로 MemoryMemberRepository 클래스에도 @Repository 애노테이션을 추가해준다.
src/main/java/hello/hellospring/controller/MemberController
package hello.hellospring.controller;
import hello.hellospring.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;
}
}
src/main/java/hello/hellospring/service/MemberService
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/......./
}
src/main/java/hello/hellospring/repository/MemoryMemberRepository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class MemoryMemberRepository implements MemberRepository {
/*
동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
*/
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
/ .../
}
위 코드들 처럼 작성해주면, 아래 그림처럼 의존관계가 설정된다.
<aside> 💡 컨트롤러, 서비스, 리포지토리
</aside>
<aside> 💡 생성자에 @Autowired를 쓰면, 컨트롤러가 생성이 될 때, 스프링 빈에 등록되어 있는 memberService 객체를 가져다가 넣어준다. 이게 바로 Dependency Injection(DI, 의존관계 주입)이다.
</aside>
src/main/java/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();
}
}
참고: 인터페이스는 new 안됨
memberService와 memberRepository를 스프링 빈으로 등록하고, 스프링 빈에 등록되어 있는 memberRepository를 memberService에 넣어준다.
return new MemberService(memberRepository()); → @Autowired와 같은 기능
컨트롤러의 경우에는 컴포넌트 스캔 방식으로 한다.(@Controller, @Autowired 사용)
참고: XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않으므로 생략한다.
참고: DI에는 필드 주입, setter 주입, 생성자 주입 3가지가 있다.
package hello.hellospring.controller;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
// 필드 주입
@Autowired private final MemberService memberService;
// setter 주입
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
// 생성자 주입
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
결론: 의존관계가 실행 중에 동적으로 변하는 경우(서버가 떠 있는 도중에 변하는 경우)는 거의 없으므로 거의 100퍼센트 생성자 주입을 사용한다.
실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.
주의: @Autowired를 통한 DI는 MemberController, MemberService 등과 같이 스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.