[스프링 입문] 4. 스프링 빈과 의존관계
📌 소개
스프링 빈 : 컨테이너가 관리하는 자바 객체를 빈(Bean) 이라고 함
자바에서 new를 통해 생성되는 객체를 의미하는 것이 아니라 컨테이너에서 생성하고 관리하는 객체를 의미한다.
스프링 빈을 등록하는 방법
- 컴포넌트 스캔과 자동 의존 관계 설정
- 자바 코드로 직접 스프링 빈 등록
📌 스프링 빈을 등록하는 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
✔ 컴포넌트 스캔
- 스프링 컨테이너가 자동으로 클래스를 검색하고 등록하는 기능
- spring Application에서 컴포넌트 스캔을 활용하면 개발자가 일일이 빈을 등록할 필요 없이, 클래스를 스캔하여 자동으로 빈으로 등록 가능
- DI를 적용할 수 있음
@Component
: 에노테이션이 있으면 스프링 빈으로 자동 등록됨@Controller
: 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다@Service
@Repository
✔ 자동 의존관계 설정
- 스프링 컨테이너가 관리하는 빈 사이의 의존관계를 자동으로 설정하는 기능
- 스프링 빈을 생성하면서 해당 빈이 필요로 하는 의존 객체를 자동으로 주입
@Autowired
: 필드, 생성자, 메서드 등에 적용하여 해당 의존성을 주입받을 수 있도록 함@Inject
cf)
스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록(유일하게 하나만 등록해서 공유)
➡ 같은 스프링 빈이면 모두 같은 인스턴스임
➡ 설정으로 싱글톤이 아니게 할 수는 있지만, 특별한 경우가 아니면 대부분 싱글톤
2. 자바 코드로 직접 스프링빈 등록
📌 컴포넌트 스캔과 자동 의존관계 설정
🔶 main/.../hello.hellospring/controller/MemberController
@Controller
public class MemberController{
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService){
this.memberService = memberService;
}
}
✔ @Controller
- 스프링 컨테이너에 @Controller가 있으면 MemberController 객체를 생성해서 스프링 컨테이너에 넣어둠
- spring container가 관리(= spring been 관리)
✔ 생성자 생성
- 단축키 : command + N => Constructor
private final MemberService memberService = new MemberService()
를 여러 개 생성할 필요가 없음 (공용으로 사용)- 스프링 컨테이너에 등록을 하고 사용 (하나만 등록됨)
private final MemberService memberService;
까지 작성 후 단축키 이용해 생성자 생성
✔ @Autowired
: 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌
- 스프링 컨테이너에 있는 memberService를 가져와 연결해줌
- MemberController는 spring container가 뜰 떄 생성 -> 만들어진 생성자 호출
- MemberController가 생성될 때 스프링빈에 등록되어있는 MemberService 객체를 넣어준다
의존성 주입(Dependency Injection, DI)
: 객체 의존관계를 외부에서 넣어주는 것
최종 MemberController.java
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;
}
}
❗ 오류 발생
Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.
- @Autowired는 스프링 컨테이너에서 MemberService를 가져움
- MemberController는 Annotation이라도 걸어두어서 spring인걸 알 수 있지만, MemberService는 순수 java 코드이기 때문에 spring인지 알 수가 없다.
MemberService.java 코드에
✔ @Service
추가
- MemberService 클래스 위에 @Service 추가
- spring이 스프링 컨테이너에 MemberService를 등록함
✔ @Autowired
추가
- MemberService 위에 @Autowired 추가
- MemberService는 MemberRepository가 필요함
@Autowired
가 spring이 MemberService를 생성할 때 스프링 컨테이너에 등록하면서 MemberService 생성자 호출- 스프링 컨테이너에 있는 MemberRepository에 구현체 MemoryMemberRepository를 넣어줌
최종 MemberService.java
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);
}
}
📌 자바 코드로 직접 스프링 빈 등록하기
- @Service, @Autowired, @Repsitory 등을 사용하지 않고 직접 코드로 작성하여 스프링 빈 등록
먼저 Annotation을 제거한다
➡ MemberController의 @Controller, @Autowired만 남겨두고 다른 파일의 annotation들을 제거함
SpringConfig.java
🔶 main/.../hello.hellospring/SpringConfig.java
@Configuration
public class SpringConfig{
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepositort memberRepository(){
return new MemoryMemberRepository();
}
}
✔ @Configurtion
SpringConfig class 위에 @Configuration 작성
✔ @Bean
memberService를 스프링빈에 등록
최종 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();
}
}
두 방법의 차이
1. DI(Dependency Injection)
세가지 방법 존재
- 필드 주입
- setter 주입
- 생성자 주입 ( 주로 생성자 주입 사용, 의존관계가 실행중에 동적으로 변하는 경우는 거의 없기때문)
✔ 필드 주입 : 안좋음, 변경할 수 없음
@Autowired private MemberService memberService;
✔ setter 주입 : setter를 통해 들어옴(commane + N)
private MemberService memberService;
@Autowired
public void setMemberService(MemberService memberService){
this.memberService = memberService;
}
- setMemberService 위에 Autowired를 넣어주면 된다.
- 하지만, setMemberService를 설정하면 바꿀 일은 없지만, 호출할 때 public으로 열려있어야 하므로 public하게 노출된다. (변경하면 문제가 발생함)
✔ 생성자 주입 : 생성자를 통해 들어옴 가장 추천
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService){
this.memberService = memberService;
}
2. 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔
을 사용하고 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 설정으로 통해 스프링 빈
으로 등록
해당 상황 : 상황에 따라 구현클래스를 변경해야 하는 상황
- 현재 데이터 저장소가 선정되지 않아 interface로 설계를 하고, 구현체로 MemoryMemberRepository를 사용하고 있다.
- 나중에 MemoryMemberRepository를 다른 리포지토리로 교체하기로 했다. (기존의 코드를 하나도 변경하지 않고 교체할 것
- 바꾸려는 리포지토리를 데이터베이스에 실제 연결하는 리포지토리로 바꿀 것이다.
- 스프링 빈에 등록 -> 코드 변경 간단
기존 구현체 : MemoryMemberRepository()
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
바꾼 구현체 : DbMemberRepository()
public MemberRepository memberRepository(){
return new DbMemberRepository();
}
❗ 주의
@Autowired
를 통한 DI는 HelloController
, MemberService
등과 같이 스프링이 관리하는 객체(spring container에 올라가있는 것들)에서만 동작함
➡ 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않음
이 글은 <김영한_스프링 입문>을 수강하고 참고하여 작성한 글입니다
PREV
NEXT
'인프런 Spring 강의 정리 > 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술' 카테고리의 다른 글
[스프링 입문] 6. 스프링 DB 접근 기술 (0) | 2024.01.20 |
---|---|
[스프링 입문] 5. 회원 관리 예제 - 웹MVC 개발 (0) | 2024.01.20 |
[스프링 입문] 3. 회원 관리 예제 - 백엔드 개발 (0) | 2024.01.20 |
[스프링 입문] 2. 스프링 웹개발 기초 (0) | 2024.01.20 |
[스프링 입문] 1. 프로젝트 환경설정 (0) | 2024.01.20 |