회원 컨트롤러가 회원 서비스와 리포지토리를 사용할 수 있게 의존관계로 설정
보통 라이브러리는 코드를 만드는 사람이 호출해서 사용하지만, 스프링은 반대로 스프링이 주도권을 잡아서 내 코드에 붙은 애노테이션을 보고 라이브러리를 호출해서 실행한다. 그리고 우리는 어떤 라이브러리를 스프링이 가져다 쓰는지 몰라도 된다.
➡️ 제어의 역전 (IoC)과 추상화
스프링은 실행되면 먼저 스프링 컨테이너 라는 공간을 만든다.
소프트웨어 공학에서 컴포넌트(Component)는 "교체가 가능한 독립된 부품"이라는 뜻이다. 스프링은 클래스를 단순한 코드가 아니라, 언제든 갈아 끼울 수 있는 '부품'으로 취급한다. 그래서 그 클래스 위에 시스템의 부품(Component) 도장을 찍어두는 역할을 하는 애노테이션의 한 종류다. (클래스 타입 ❌)
@Controller, @Service, @Repository 이 3개의 애노테이션 안에는 사실 @Component 가 선언되어있다. 그래서 컴포넌트 스캔에 인식될 수 있는 것이다.
확인이 되면 해당 클래스의 객체(스프링 빈)을 생성하고 스프링 컨테이너에 저장한다. 스프링 빈은 위 같은 주요 애노테이션이 붙은 클래스의 객체를 말한다. 스프링 빈은 각각 하나의 객체만 생성할 수 있다.(싱글톤 유지)
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;
}
}
@Autowired 가 보이면 밑의 코드를 참고해서 스프링(외부)이 스프링 컨테이너에 스프링 빈들을 연결시켜준다. 멤버 컨트롤러는 멤버 서비스에 요청을 알려야하는 일을 수행하기 위해서는 멤버 서비스 객체가 필요하다. 여기서 컨트롤러와 서비스는 의존관계이다. 그리고 이 애노테이션은 스프링 빈을 생성할 수 있는 클래스에서만 선언해 사용할 수 있다.
✔️ 의존관계를 외부에서 넣어주는 것을 의존성 주입 (Dependency Injection, DI) 이라고 한다.
내(타입)가 움직여서 어떤 일을 수행하는게 아니라 외부(스프링)에서 조정을 해주는 방식이다. 이 의존성 주입의 장점은 그 대상과 기능이 분리됐기 때문에 객체가 바뀌더라도 코드를 수정하지 않고 그대로 사용할 수 있어서 코드가 유연하고 재사용성이 높아진다.
외부(e.g. 스프링) 에서 내부의 일을 조정할 때 사용할 수 있는 방법
- 표식 남기기 : @Component 나, 이를 가진 애노테이션을 선언해둔다.
- 순찰 : ComponentScan 이 @Component 애노테이션이 붙은 클래스를 다 돌면서 찾는다.
- 조립 (DI) : 스프링이 해당 클래스의 객체를 생성해서 연결한다.
package hello.hello_spring.controller;
import hello.hello_spring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
➡️ @Controller 를 선언하지 않았다. (컨포넌트 스캔이 확인해서 생성하지 못함)
package hello.hello_spring;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import hello.hello_spring.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());
}
// 아직 DB 가 선정이 안되서 변경 가능성이 있는 경우는 이렇게 스프링 컨테이너에 등록한다.
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
➡️ 스프링 빈을 생성하는 클래스를 만들고 ✅ @Configuration 을 선언한다. @Configuration은 스프링 빈을 어떻게 만들고 조립할지 적어놓은 설계도라고 표시하는 애노테이션이다.
@Configuration의 특징 : 싱글톤 유지 ‼️스프링은 @Configuration이 붙은 클래스를 특별하게 처리한다. memberService()를 호출할 때 내부에서 memberRepository()를 또 호출하는데, 자바 문법대로라면 새로운 리포지토리가 계속 만들어져야 하지만, @Configuration 덕분에 스프링은 이미 만들어져 있는 리포지토리가 있다는 걸 알아채고 있던 걸 사용하도록 관리한다.
스프링에서 컨트롤러는 웹과의 연결을 실행할 때 서비스나 리포지토리와 달리 필수적이다. 웹의 요청을 받고 다시 웹에 전송하는 역할을 이 컨트롤러에서 하기 때문이다. 그래서 스프링은 실행될 때 자동으로 컨트롤러의 객체를 생성해 스프링 컨테이너에 저장하고 시작한다.