스프링 빈의 의존관계 방법은 두가지가 존재한다
- 컴포넌트 스캔과 자동 의존 관계 설정
- 자바 코드로 직접 스프링 빈 등록하기( = 설정 파일에 직접 빈을 등록하는 방법)
각 방법별로 어떻게 스프링 빈을 설정하는지 살펴보자
컴포넌트 스캔과 자동 의존관계 설정
컴포넌트 스캔의 정의는 @Component를 가진 모든 대상을 가져와 빈에 등록하기 위해 찾는 과정을 의미한다.
아래와 같은 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;
}
}
@Controller 어노테이션이 있으면 스프링이 기본적으로 스프링 컨테이너에 bean으로 등록하여 관리를 하게 된다. 어노테이션 @Autowired를 등록하면, 스프링이 연관된 bean 객체를 스프링 컨테이너에서 찾아서 넣어주게 된다. 잠시 짚고 넘어가자면 스프링에서는 스프링 컨테이너에 스프링 Bean을 등록할 때 기본적으로 싱글톤 패턴으로 등록한다. 즉 하나의 객체를 공유한다는 의미이다. 다른 말로, 동일한 스프링 Bean 객체로 의존성을 받아오면, 모두 동일한 인스턴스인 것이다. 이와 같이 객체의 의존관계를 외부에서 넣어주는것을 DI(Dependency Injection, 의존성 주입) 이라고 한다.
이렇게만 하고 실행을 하면 아래와 같이 오류가 나온다.
MemberService타입의 bean을 찾을 수 없다는 메세지를 내보내고 있다.즉, 이는 MemberService객체가 bean에 등록되어있지 않다는 것이다. MemberService는 Service요소에 해당하므로, 클래스 위에 @Service어노테이션을 붙여주자. 이 @Service어노테이션은 스프링 비즈니스 로직에 사용된다.
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;
}
//회원가입
// 같은 이름의 회원이 존재하지 않는다라는 가정
public Long join(Member member){
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m ->{
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
//전체회원조
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberid){
return memberRepository.findById(memberid);
}
}
그 다음 MemberService생성자는 Repository에 해당하는 MemberRepository타입의 객체를 받아야 하므로, 동일하게 @Autowired어노테이션을 지정함으로서 의존성 주입이 되게끔 한다. MemberRepository타입의 클래스 또한 Spring Bean에 등록을 해주어야 한다. 다만, MemberRepository타입 클래스는, 레포지토리이므로, @Repository 어노테이션을 사용해 주어야한다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class MemoryMemberRepository implements MemberRepository{
// 동시성 문제가 있기 때문에, 실무에서는 ConcurrentHashMap을 사용
private static Map<Long, Member> store = new HashMap<>();
// sequence : id값을 생성하는 변수이다. 실무에서는 동시성 문제를 고려하여 Atomic Long을 사용한다
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(),member);
return member;
}
// Clear Sequence
public static void clearSequence(){
sequence = 0L;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear(); // 레포지토리를 완전히 비운다
}
}
@Repository 어노테이션은 스프링 데이터 접근 계층에서 사용된다. 결론적으로 이 세 구성요소의 의존성 주입 관계를 나타내면 아래와 같이 나타낼 수 있다.
컴포넌트 스캔 방식은 @Componenet를 가진 대상을 bean으로 등록하는 방식이라고 했다. 근데 memberController, memberService, memberService, memberRepository는 어떻게 bean객체에 등록되었을까? 그 이유는 이 세가지 클래스에 사용된 어노테이션들이 @Componenet 어노테이션을 가지고 있기 때문이다. 즉 스프링은 @Componenet를 포함하는 어노테이션 또한 Spring Bean으로 자동등록된다.
자바 코드로 직접 스프링 빈 등록하기
이번에는 직접 Spirng Bean을 등록해보자. Service, Repository 구성요소의 @Service, @Repository, @Autowired 어노테이션을 모두 지워주고, 프로젝트 디렉토리 하위에 SpringConfig라는 클래스를 만든다. 그리고 이 클래스 위에는 @Configuration 어노테이션을 작성해준다. 그 다음 메소드 레벨에서는 @Bean 어노테이션을 붙여주면 된다. 여기서 @Bean이 붙은 메소드 명이 각각의 bean 이름이 된다. return되는 인스턴스를 스프링 컨테이너가 Bean으로 활용한다.
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();
}
}
MemberService객체는 MemberRepository객체 의존성을 받는다. memberService()메소드 실행 과정을 해석하면 아래와 같다.
- memberService, memberRepository 모두 Spirng Bean에 등록된다
- Spring Bean에 저장된 memberRepository 객체를 memberService 객체에 넘겨준다.
DI에는 세가지 방식이 있다.
- 필드주입
- setter주입
- 생성자 주입
의존관계가 실행중에 동적으로 변경되는경우는 거의 없으므로, 생성자 주입을 권장한다. 다른 의미로 필드주입, setter주입같은 경우에는 final 선언이 불가능하지만, 생성자 주입은 final선언이 가능하다.
'Back-End > Java Spring Boot' 카테고리의 다른 글
[Spring Boot] 테스트 코드 작성해보기 (0) | 2022.03.13 |
---|---|
[Spring Boot] 일반적인 웹 애플리케이션 계층 구조 (0) | 2022.03.13 |
[Spring Boot] API 방식 (0) | 2022.03.13 |
[Spring Boot] MVC & Template Engine (0) | 2022.03.12 |
[Spring Boot] Spring Static Content (0) | 2022.03.12 |