1. 컴포턴트 스캔과 자동 의존관계 설정
의존 관계 : Member Controller가 Member Serivce를 통해서 회원가입, 조회할 수 있어야 한다. (Controller가 Service 의존)
회원 컨트롤러에 의존관계 추가
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
를 사용해 MemberController 컨트롤러 생성
스프링 빈 관리 : @Controller
가 있다면 스프링은 스프링 컨테이너에 MemberController라는 객체를 생성, 이 컨트롤러를 컨테이너 안에서 관리한다.
private final MemberService memberService = new MemberService();
와 같이 new를 사용해 직접 MemberService 객체를 생성할 수도 있지만, 다른 컨트롤러가 회원 서비스를 사용할 때마다 회원 서비스 객체가 중복되어 생성될 것이고, 이때 컨트롤러는 굳이 다른 회원 서비스를 각각 생성하여 사용할 필요가 없다.
회원 서비스는 한 번만 생성되어 생성된 하나의 회원 서비스 인스턴스를 각각의 컨트롤러들이 공유하는 것이 좋다.
즉, 등록된 하나의 회원 서비스를 컨테이너로부터 받아 사용해야 한다.
이를 위해서 @Autowired
를 사용한다.
스프링은 @Controller
로 MemberService Controller를 스프링 컨테이너에 등록, 생성자를 호출하게 되고 이때 생성자에 @Autowired
이 있다면 자동으로 스프링 컨테이너에 등록된 MemberService 객체를 찾아 매개변수로 넘겨준다.
객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다.
스프링이 @Autowired에 의해 자동으로 의존성을 주입한다.
main() 메서드 실행하면 오류 발생
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
Parameter 0 of constructor in hello.hellospring.controller.MemberController required a bean of type 'hello.hellospring.service.MemberService' that could not be found.
오류가 난 이유 : 컨트롤러는 컨테이너에 등록되었지만 아직 회원 서비스는 컨테이너에 등록되지 않았기 때문이다.
MemberService는 아직 단순한 자바 코드이므로, 아직 스프링이 이를 인식하고 컨테이너에 스프링 빈으로 등록할 수 없다.
현 소스에서는 memberController는 @Controller를 통해 컨테이너에 등록되었지만 memberService는 스프링 빈으로 컨테이너에 등록되지 않았기 때문에 의존성 주입이 이루어지지 않는다.
memberService를 스프링 빈으로 등록하기 위해 컴포넌트 스캔 을 통해 자동 의존관계를 설정해야한다.
컴포넌트 스캔 : @Controller
를 통해 컨트롤러가 컨테이너에 자동으로 등록되는 원리이다.
컴포넌트 스캔 원리
@Component
: 애노테이션이 있으면 스프링 빈으로 자동 등록된다.@Controller
: 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.
컴포넌트 스캔과 자동 의존관계 설정, 스프링 빈을 자동으로 등록하는 정형화된 패턴 애노테이션
@Controller
@Service
@Repository
세 가지 각각은 @Component
로 대체될 수 있다.
(각각의 클래스는 @Component
를 포함하기 때문이다.)
컴포넌트 관련 애노테이션이 존재한다면 스프링이 객체를 생성, 컨테이너에 스프링 빈으로 등록한다.
MemberService는 @Service
MemoryMemberRepository는 @Repository
로 스프링 컨테이너에 등록된다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Repository
public class MemoryMemberRepository implements MemberRepository {}
MemberService가 @Service
를 통해 스프링 빈으로 등록될 때에도 생성자를 호출한다.
이때, @Autowired
가 있으면 @Repository
를 통해 컨테이너에 등록된 리포지토리 객체에 의존성을 주입한다.
@SpringBootApplication
을 실행하면 속해있는 패키지 내의 @Controller
, @Service
, @Repository
를 통해 각각이 컨테이너에 등록되고, @Autowired
를 통해 의존관계를 가지는 모습이다.
Controller를 통해 외부 요청을 받고, Service에서 비즈니스 로직을 처리, Repository에서 데이터를 저장하는 것이 위에서 언급한 정형화 패턴이라고 할 수 있다.
참고로 스프링은 스프링 컨테이너에 스프링 빈 등록시 중복되지 않게 유일하게 하나의 객체만 싱글톤으로 등록한다.
예를 들어 따로 설정을 하는 경우가 아니라면 다른 컨트롤러(또는 서비스)에서 사용하는 리포지토리는 같은 리포지토리 인스턴스를 사용한다.
마지막으로, @SpringBootApplication
하위에 있는 소스 파일에는 스프링 빈으로 등록된다.(이외는 안된다고 생각한다.)
컴포넌트 관련 애노테이션 : 스프링 객체를 각각 생성하여, 스프링 컨테이너에 등록한다. @Autowired : 연관 관계, 스프링 객체를 연결해준다.
2. 자바 코드로 직접 스프링 빈 등록하기
이번에는 직접 설정 파일을 등록하여 컨테이너에 스프링 빈을 등록하는 방법이다.
이전에 회원 서비스와 회원 리포지토리에 작성했던 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행한다.
1) SpringConfig.java 생성
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();
}
}
클래스명 앞에 @Configuration
을 등록하면 스프링이 설정 파일임을 인식, 컨테이너에 스프링 빈을 등록할 준비를 한다.
@Bean
을 사용하여 다음의 메서드를 호출해 스프링 빈이 등록된다.
memberService메서드가 호출되면 MemberService 객체가 생성되고, 이때 @Bean
을 통해 컨테이너에 등록된 레파지토리가 매개변수로 회원 서비스 객체와 연결, 회원 서비스 객체가 반환된다. 이때 스프링 빈으로 등록된다.
컨트롤러는 컨포넌트 스캔과 동일하게 작동한다.
@Controller
로 스프링 컨테이너에 등록되며
@Autowired
로 스프링 빈에 등록된 객체와 연결된다.
이와 같이, 설정 파일을 직접 등록하여 스프링 빈을 등록할 수 있다.
DI(Dependency Injection)란?
1) 필드 주입
생성자 없이 @Autowired
로 DI를 주입하는 방법이다.
// 필드 주입
@Autowired private MemberService memberService;
2) setter 주입
setter에 @Autowired
로 연결하는 방법이다.
(누구나 들어올 수 있어 위험하다.)
// setter 주입 (누구나 들어올 수 있어 위험하다)
@Autowired
public void setMemberService(MemberService memberService){
this.memberService = memberService;
}
3) 생성자 주입
new를 사용하여 생성자를 호출할 때 DI를 호출하는 방법으로, 일반적인 프로젝트에서는 조립(생성자 호출) 시점에 생성자를 한 번만 호출하여 컨테이너에 스프링 빈을 등록하고, 이후에는 의존관계가 동적으로 변경되는 경우가 거의 없기 때문에 생성자 주입이 주로 권장된다.
실무에서는?
1) 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용
2) 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 할 경우 설정(@Configuration
)을 통해 스프링 빈으로 등록
DB?
현재 임시로 사용하고 있는 MemoryMemberRepository를 나중에 실제 DB로 변경할 예정이기 때문에 컴포넌트 스캔 방식 대신 자바 코드로 스프링 빈을 설정
- 실제로 SpringConfig 파일의 new MemoryMemberRepository();를 new DBMemberRepository(); 만 변경하여 간단히 레파지토리를 변경할 수 있다.
주의해야할 점
@Autowired
를 사용하는 DI는 MemberController, MemberService와 같이 스프링이 관리하는 객체에서만 동작한다.
즉, 스프링 빈으로 등록되어 스프링 컨테이너에 올라가야만 @Autowired
가 동작한다.
ex)
@Configuration
으로 설정 파일은 생성되어 있지만@Bean
으로 서비스, 레파지토리가 등록되지 않은 경우- main() 등에서 직접 new를 사용하여 객체를 생성
이는 스프링 컨테이너에 올라간 객체가 아니므로(스프링 관리하는 객체가 아니기 때문에) @Autowired
는 동작하지 않는다.
'공부 및 활동 > 스프링 강의 정리' 카테고리의 다른 글
[스프링 입문] 6. 스프링 DB 접근 기술 1 (0) | 2021.09.27 |
---|---|
[스프링 입문] 5. 회원 관리 예제 - 웹 MVC 개발 (0) | 2021.09.26 |
(회원 관리 예제) 회원 서비스 테스트 (0) | 2021.09.26 |
(회원 관리 예제) 회원 서비스 개발 (0) | 2021.09.26 |
(회원 관리 예제) 회원 리포지토리 테스트 케이스 작성 (0) | 2021.09.20 |
댓글