public class MemberService {
//memberservice는 memberrepository 객체에 의존하고 있다.
private final MemberRepository memberRepository = new MemberRepository();
//memberRepository 메서드 사용, 강한결합
public void createMember(Member member){
memberRepository.postMember(member);
}
public Member getMember(Long memberId){
return memberRepository.getMember(memberId);
}
public void deleteMember(Long memberId){
memberRepository.deleteMember(memberId);
}
}
MemberRepository라는 객체를 다른 MockRepository 라는 객체로 교체하면 아래 구현한 메서드들이 새롭게 의존관계를 설정한 MockRepository라는 객체에 존재하지 않으면 해당 코드도 모두 변경해야하고 객체간의 관계가 변경될 때마다 우리가 직접 해당 코드를 찾아 수정하고 문제점이 없는지 살펴봐야 한다.
package com.codestates.section2week4.member;
public class MemberService {
//// memberservice는 memberrepository 객체에 의존하고 있다.
// private final MemberRepository memberRepository = new MemberRepository();
private MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void createMember(Member member){
memberRepository.postMember(member);
}
public Member getMember(Long memberId){
return memberRepository.getMember(memberId);
}
public void deleteMember(Long memberId){
memberRepository.deleteMember(memberId);
}
}
생성자를 통해 의존성을 주입받음으로써 우리는 객체가 생성되는 순간 의존관계를 설정을 한다.
여기서의 문제는 객체를 생성할때마다 결국 직접 주입할 객체를 직접 작성해서 생성자로 넣어줘야한다.
public class DependencyConfig {
public MemberService memberService(){
return new MemberService(memberRepository());
}
public MemberRepository memberRepository(){
return new MemberRepository();
}
public CoffeeService coffeeService(){
return new CoffeeService(coffeeRepository());
}
public CoffeeRepository coffeeRepository(){
return new CoffeeRepository();
}
}
지금까지는 프레임워크의 도움이 아닌, 직접 설정 파일을 통해 의존성 주입을 할 수 있는 방식을 사용했다.
이제 스프링에서 지원하는 DI를 학습해보자.
스프링 컨테이너는 내부에 존재하는 애플리케이션 빈(Bean)의 생명주기를 관리한다.
개발자가 정의한 Bean을 객체로 만들어 Bean의 생성, 관리, 제거등의 역할을 하고
개발자가 필요로 할 때 제공한다.
🙂 : "아, 스프링 컨테이너는 빈의 생명주기를 관리하는 녀석이구나."
🧐왜 사용하지?
객체지향 프로그래밍은 낮은 결합과 캡슐화가 특징이다. 그러나 서로 다른 클래스가 참조가 심할 수록 의존성이 높아지고 이는 객체지향적이지 못하다. 객체간 의존성을 낮추고 높은 캡슐화를 위해 Spring 컨테이너가 사용된다.
구현 클래스에 의존하는게 아니라 인터페이스에 의존하도록 설계한다.(PSA)
✒️어떻게 스프링 컨테이너가 생성되는가?
ApplicationContext 인터페이스 구현체를 통해 스프링 컨테이너를 만든다.
- DependencyConfig.class 등의 구성 정보를 지정해줘서 스프링 컨테이너를 생성한다.
- DependencyConfig에 있는 구성 정보를 통해서 스프링 컨테이너는 필요한 객체들을 생성한다.
- 애플리케이션 클래스는 구성 메타데이터와 결합되어 ApplicationContext 생성 및 초기화된 후 완전히 구성되고 실행 가능한 시스템 또는 애플리케이션을 갖는다.
// Spring Container 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DependencyConfig.class);
📌스프링 컨테이너의 종류
BeanFactory : 스프링 컨테이너의 최상위 인터페이스입니다.
- BeanFactory는 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리하는 역할을 합니다.
- getBean() 메소드를 통해 빈을 인스턴스화할 수 있습니다.
- @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용해 빈 등록을 합니다.
ApplicationContext : BeanFactory의 기능을 상속받아 제공합니다.
- 빈을 관리하고 검색하는 기능을 BeanFactory가 제공하고 그 외에 부가기능을 제공합니다.
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
PetStoreService service = context.getBean("memberRepository", memberRepository.class);
getBean 을 사용하여 bean의 인스턴스를 가져올 수 있으나 응용 프로그램 코드에서는 getBean() 메서드로 호출하여 사용하면 안된다.
스프링은 다양한 환경 설정을 지원한다. 스프링은 어떻게 이런 다양한 설정 형식을 지원할까?
BeanDefinition
참고 : https://velog.io/@jy9922/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EC%84%A4%EC%A0%95-%EB%A9%94%ED%83%80-%EC%A0%95%EB%B3%B4-BeanDefinition
'아.. BeanDefinition이란 추상화 때문에 스프링에서 다양한 설정 형식을 지원하는구나!'
new AnnotationConfigApplicationContext(AppConfig.class)
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 여기서는 AppConfig.class 를 구성 정보로 지정했다.
핵심 포인트
컨테이너의 의미
소프트웨어 개발 용어의 관점에서 컨테이너란 내부에 또 다른 컴포넌트를 가지고 있는 어떤 컴포넌트를 의미한다.
- 컨테이너는 먼저 객체를 생성하고 객체를 서로 연결합니다.
- 객체를 설정하는 단계를 지나 마지막으로 생명주기 전반을 관리합니다.
- 컨테이너는 객체의 의존성을 확인해 생성한 뒤 적절한 객체에 의존성을 주입합니다.
- 스프링은 스프링 컨테이너를 통해 객체를 관리합니다.
- 스프링 컨테이너에서 관리되는 객체를 빈(Bean)이라고 합니다.