외부시스템일지, MemoeryDB일지, DB를 사용할지 모르기 구현체를 각 역할에 맞게 여러개로 두었다.
실제 구현에서 사용할 구현체들이다.
public enum Grade {
VIP,
FRIEND
}
package hello.core.useSpring;
import hello.core.pureJava.member.Grade;
public class Member {
private int id;
private String name;
private Grade grade;
public Member(int id, String name, Grade grade) {
this.id = id;
this.name = name;
this.grade = grade;
}
}
여기에 추가로 getter랑 setter까지 넣어주면 된다.
package hello.core.useSpring;
public interface MemberService {
public void register(Member member);
public Member findMemberById(int id);
}
회원이 어떤 기능을 하는지 기억해보자
1. 회원 가입을 수행하는 기능
2. 회원 조회를 수행하는 기능
이를 인터페이스 구현체로 지정해준다.
package hello.core.useSpring.member;
public interface MemberRepository {
public void save(Member member);
public Member findMember(int id);
}
저장소 역할의 DB를 만든다.
DB에서 수행되는 내용은
1. 회원 저장하기
2. DB에 저장된 회원 찾아서 조회하기
이 기능은 MemberService의 역할과는 다르다.
회원을 가입하는 기능을 제공하는 거고 위의 MemberRepository는 회원을 DB에 저장하는 행위를 의미한다.
분리해야하는 이유는 간단하다.
만약에 DB로직이 변경되면?... 하는 기능은 똑같은데 DB 저장 로직이 변경된다면?... 그러면 코드를 직접 고쳐야하게되는 상황이 발생함 -> 변경에는 닫혀있고 확장에는 열려있는 OCP원칙을 위반하게됨
@Repository
public class MemoryMemberRepository implements MemberRepository{
Map<Integer, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findMember(int id) {
return store.get(id);
}
}
MemberRepository를 상속받는 MemoryMemberRepository를 작성한다.
이때 @Repository 애노테이션을 이용해 스프링빈으로 등록한다. 즉 해당 구현체는 스프링 컨테이너에 들어가게 된다.
package hello.core.useSpring.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl implements MemberService{
//AutoWired를 사용한 생성자 주입
@Autowired //생성자가 한 개일 때는 생략 가능
final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void register(Member member) {
memberRepository.save(member);
}
@Override
public Member findMemberById(int id) {
return memberRepository.findMember(id);
}
}
마찬가지로 @Service애노테이션을 통해 Spring 빈으로 등록한다.
MemberService는 다이어그램에서 보면 알 수있듯 MemberRepository에 의존적인데
이를 @Autowired를 이용핸 생성자 주입을 통해서 주입해준다.
이것이 Dependency Injection이다!
이제 회원 가입을 설계한 도메인대로 작성을 완료했다. 한번 테스트해보자!
package hello.core.useSpring.member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest // Spring 컨테이너를 불러오는 설정
class MemberServiceImplTest {
@Autowired
private MemberService memberService; //필드주입
@Test
public void register(){
Member member = new Member(1, "member1", Grade.VIP);
memberService.register(member);
Member findMember = memberService.findMemberById(1);
assertEquals(member, findMember);
}
}
성공!!
위의 코드를 작성하면서 나는 IoC에 대한 설명을 따로 하지 않았다. 그 이유는
바로 @Service나 @Repository처럼 Sprign bean으로 등록하는 행위가 객체의 관리를 Spring에게 전가? 한다는 의미이다.
기존의 순수 자바 코드에서는 AppConfig를 만들고 거기서 객체를 사용자가 갈아끼워줬는데 이제는 더 간편하게 애노테이션 하나로 Spring bean으로 등록하는 것이다.
이 과정에 대해서는 조금 더 자세하게 알아보자
그 이유는 Spring Container에 있다.
MemberServiceImpl은 확실하게 MemberRepository에 의존한다. 그렇다면 MemberRepository의 구현체는 도대체 어디서 가져오는 걸까?
그건 바로 Spring Container다. @Repository, @Service가 붙은 것들은 Spring Container로 향하게 된다. 이때 @Repository애노테이션을 갖는 MemoryMemberRepository는 MemberRepository를 상속받았기 때문에 스프링은 MemberRepository를 의존하게 되면 이를 상속하는 객체를 꺼내와서 주입해준다.
그래서 제어의 역전이라고 부르는 것이다.
순수 자바 코드에서 AppConfig를 생각해보자
//appliction환경 구성은 여기서 다 하는 거임
@Configuration //Spring 옵션
public class Appconfig {
//역할들이 나오고 역할들이 어떤 구현체를 갖는 지 확인할 수 있다.
//역할과 구현 클래스가 한눈에 들어온다.
//bean을 쓰면 Spring Container에 저장이 된다.
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
이렇게 구현되어 있고 구현체에서는
AppConfig app = new AppConfig();
MemberService memberRe = app.memberService();
의 형태로 가져오게 된다.
DIP, OCP를 지키기는 하지만 그래도 개발자들이 AppConfig에서 객체를 갈아 끼워줘야하는 상황이 발생한다.
Spring Container를 사용하면 그럴 필요가 없다는 의미이다.
추상화를 만들고 그 추상화를 상속받는 친구들만 만들고 @Bean으로 등록만 해주면 Spring이 알아서 객체를 관리해주는 것이다.
그리고 또한!... IoC는 조금 더 넓은 의미를 가지고 있는데
IoC는 객체의 생성, 생명주기, 의존성 관리(여기에 DI가 속함)을 관리해준다.
직역하면 외부에서 주입해준다는 뜻이다.
그럼 Spring에서 DI는 어떻게 이루어 지는지 보자
SOLID 원칙을 지키기위해서는 다형성을 지키기위해 추상화에 의존해야한다고 했다.
이제 추상화에 의존하는 건 알겠는데... 문제는 추상화에 의존받으면 객체를 주입해주는 과정이 필요하다.
그러기 위해서 순수 자바코드에서는 AppConfig를 사용했다. 그렇다면 Spring에서는 어떻게 사용할까?
@Autowired
final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
ServiceImpl의 일부분이다.
@Autowired를 통해서 스프링이 생성자 주입 방식으로 MemberRepository를 스프링 컨테이너에서 찾아서 주입해준다.
스프링은 인터페이스를 확인하고, 그 인터페이스를 구현한 구현체를 찾아서 주입하게된다. 이 과정을 DI라고 한다.
스프링 컨테이너가 애플리케이션에서 필요한 객체를 자동으로 생성하고 관리한다.
@Service, @Repository, @controller, @Compnent등의 애노테이션이 이런 작업을 수행한다.
어떤 식으로 제공하는지는 위에 그림으로 그려놓았다.
IoC보다 조금 좁은 개념
Spring Container에 등록된 빈 중 추상화에 알맞는 구현체를 찾아 객체를 주입해준다. 자세한 개념은 앞쪽에서 설명이 되었으니 pass
김영한 강사님 강의 보고 공부했읍니다. 짱짱맨