스프링 IoC 컨테이너 따라해보기

강상우·2021년 7월 15일
2

Spring

목록 보기
2/3
post-thumbnail

개요

스프링은 빈 팩토리 혹은 애플리케이션 컨텍스트라고 부르는 IoC 컨테이너를 통해 객체의 생성, 관계 설정, 의존성 주입등의 제어권을 가지고 있습니다.
개발자가 직접 객체간의 관계 설정을 하지 않고, 제어의 역전 즉 컨테이너가 이러한 제어권을 가지고 있다면 어떤 이점이 존재할지 자바를 통해 간단하게 따라해보았습니다.

물론, IoC 컨테이너의 역할을 DI 뿐만이 아닌 더 많은 역할을 하지만, 이번에는 IoC 컨테이너 중 빈 팩토리라고 불리는 DI 관점에서 작성해보았습니다.

의존 관계 직접 설정

간단한 자바 코드를 통해 먼저 작성을 해보았습니다.
main 메소드에서 id가 1인 member를 조회하는 코드로 작성해보았습니다.

public class Application {
    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = memberService.findMember(1);
        System.out.println("member = " + member.getName());
    }
}
// 서비스 구현체
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository = new MapMemberRepository();

    @Override
    public Member findMember(int id) {
        return memberRepository.findById(id);
    }
}
// 레포지토리 구현체
public class MapMemberRepository implements MemberRepository {
    private Map<Integer, Member> map = new HashMap<>() {
        {
            put(1, new Member(1,"sangwoo kang", 20));
            put(2, new Member(2,"james kang", 30));
            put(3, new Member(3, "stef you", 10));
        }
    };

    @Override
    public Member findById(int id) {
        return map.get(id);
    }
}

현재 직접 필요한 의존관계를 설정하고 있습니다.
이 경우 어떤 문제가 발생하게 될까요? 만약, 레포지토리의 구현체로 JdbcMemberRepository.class를 사용하게 되었다고 생각해볼까요.
그러면, 레포지토리 인터페이스를 구현한 JdbcMemberRepository를 만드는 것으로 끝나는 것이 아닌, MemberServiceImpl 에서도 구현체를 갈아끼워줘야합니다.

즉, MemberServiceImplMemberRepository 인터페이스뿐만 아닌 구현체인 MapMemberRepsitory까지 의존하고 있는 상황입니다.

구현체를 JdbcMemberRepository로 변경을 하게되면, 인터페이스와 구현 클래스 모두를 의존하고 있는 클라이언트인 MemberServiceImpl 코드도 변경이 되어야 하는 것입니다.

객체지향 설계원칙 중 OCP와 DIP를 위반하고 있다는 것이 느껴지시나요?
구체화인 구현체에까지 의존하고 있는 MemberServiceImpl입니다.
또한, 확장에는 열려있고 변경에는 닫혀있어야 하지만, MemberServiceImpl 코드가 변경이 되는 일이 발생합니다.

// MemberServiceImpl의 코드가 아래와 같이 변경
// private final MemberRepository memberRepository = new MapMemberRepository();
private final MemberRepository memberRepository = new JdbcMemberRepository();

이런 문제를 해결하기 위해 IoC 컨테이너와 같은 역할을 해줄 수 있는 JamesContainer를 만들어보도록 하겠습니다.😅

컨테이너 생성

JamesContainer는 애플리케이션을 대신해서 구현 객체 생성과 연결, 주입의 책임을 가지는 설정 파일입니다.
즉, 관심사를 분리해내게 되는 것입니다.
애플리케이션의 각각의 객체들은 각자의 역할만 수행하면 되는 것이고, 컨테이너는 어떤 관계를 가지는지에 대한 것에만 관심을 가지고 있는 것이라고 생각하면 되겠네요.

public class JamesContainer {
    private static JamesContainer jamesContainer;

    public static JamesContainer getInstance() {
        if (jamesContainer == null) {
            jamesContainer = new JamesContainer();
        }
        return jamesContainer;
    }

    public MemberService getMemberServiceBean() {
        return new MemberServiceImpl(getMemberRepositoryBean());
    }

    public MemberRepository getMemberRepositoryBean() {
        return new MapMemberRepository();
    }
}

간단한 컨테이너를 만들어봤습니다!
애플리케이션 전체에 컨테이너를 1개만 두기 위해 싱글톤으로 개발하였고, 각각의 오브젝트가 필요한 의존관계를 컨테이너 안에서 구성하였습니다.

public class Application {
    public static void main(String[] args) {
        JamesContainer jamesContainer = JamesContainer.getInstance();
        MemberService memberService = jamesContainer.getMemberServiceBean();

        Member member = memberService.findMember(1);
        System.out.println("member = " + member.getName());
    }
}

메인 메소드에서도 단순히 컨테이너에게 MemberSerivce를 달라고 요청을 하는 부분으로 수정이 되었습니다.

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public Member findMember(int id) {
        return memberRepository.findById(id);
    }
}

MemberServiceImpl 역시 MemberRepository의 추상화에만 의존하고, 어떤 구현체가 들어오는지에는 관심이 없는 코드로 변경되었습니다.

즉, JdbcMemberRepository로 구현체를 갈아끼우려해도, 클라이언트인 MemberServiceImpl은 아무런 관심이 없이 자기 할일만 할 수 있는 것입니다. 구현체 변경은 설정파일인 JamesContainer에서 해주면 됩니다.

설정파일 변경은 애플리케이션 로직과는 별개이기 때문에 OCP 원칙을 위반한다고 볼 수 없습니다!

마무리

간단하게 저만의 Container를 만들어 스프링을 따라해보았는데요, 물론 스프링의 IoC 컨테이너는 훨씬 많은 일을 하지만 이를 통해 왜 스프링은 IoC와 DI를 중요시하는지 느껴볼 수 있었습니다.

하지만 시간이 지나고 자바의 기술이 복잡해져 가면서 자바의 본질인 객체지향 언어라는 특징을 점점 잃어버렸다. 스프링은 이 잃어버린 객체지향 언어의 장점을 다시 개발자들이 살릴 수 있도록 도와주는 도구다.
-토비의 스프링

profile
https://sangwoo0727.github.io/ 기존 블로그 이전 중입니다.

0개의 댓글