2021 Spring Study

Spring 4. DI 이해하기

Yerim Kim 2021. 1. 1. 18:59

1. 의존 객체 주입받기

다음과 같은 UserDao 인터페이스가 있다.

import java.util.Collection;

public interface UserDao {

    void create(User user);
}

 

그리고 두 종류가 UserDao 가 존재한다.

하나는 HashMap을 가지고 가짜로 DB가 있는 척 하는 HashMapUserDao 이고,

또 하나는 MySQL DB에 CRUD를 하는 MySqlUserDao 이다.

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class HashMapUserDao implements UserDao {

    private Map<Long, User> users = new HashMap<>();

    @Override
    public void create(User user) {
        users.put(user.getId(), user);
    }
}
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MySqlUserDao implements UserDao {

    ...
    
    @Override
    public void create(User user) {
        ...
    }
}

 

UserDao 클래스를 사용하는 UserService 클래스는 아래와 같다. 아래는 UserDao 로 HashMapUserDao 를 사용하는 예시이다.

import java.util.Collection;

public class UserService {

    private UserDao userDao = new HashMapUserDao();

    public void create(UserCreateRequest request) {
        userDao.create(new User(
            request.getEmail(),
            request.getName(),
            request.getPassword()
        ));
    }
}

 

만약 HashMapUserDao 대신 MySqlUserDao 를 사용하고 싶다면 다음과 같이 바꾸면 될 것이다.

import java.util.Collection;

public class UserService {

    private UserDao userDao = new MySqlUserDao();

    public void create(UserCreateRequest request) {
        userDao.create(new User(
            request.getEmail(),
            request.getName(),
            request.getPassword()
        ));
    }
}

 

이 UserService 코드가 엄청나게 길어져서 몇천줄 쯤 되었다고 해보자.

그리고 이 UserService 클래스가 여러 다른 개발자들에 의해 사용되려고 한다.

그런데 어떤 이는 user dao 로 MySqlUserDao 를 사용하고자 하고, 어떤 이는 HashMapUserDao 를 사용하고자 한다.

 

UserService 코드가 위와 같은 경우,

MySqlUserDao 대신 HashMapUserDao 를 사용하고자 한다면

UserService를 사용하는 쪽에서 UserService 코드를 직접 수정해야한다.

 

물론 위 코드상으로는  UserDao userDao = new MySqlUserDao();   달랑 이 부분만 바꾸면 된다.

하지만 UserService 코드를 전혀 모르는 개발자의 입장에서는

코드 한 줄을 건드렸다가 자칫 잘못될수도 있다는 불안감이 있다.

 

그러니 UserService 코드를 전혀 안건드리고 사용할 수 있으면 좋을 것이다.

 

UserService 코드를 다음과 같이 바꿔보자.

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void create(UserCreateRequest request) {
        userDao.create(new User(
            request.getEmail(), request.getName(), request.getPassword()));
    }
}

 

이러면, UserService 를 사용하는 곳에서는

UserService 코드를 고치지 않고서도 다른 UserDao를 사용하게 할 수 있다.

이런식으로 말이다.

public class Test {

    public static void main(String[] args) {
        /**
         * HashMapUserDao 를 사용하고 싶은 경우
         */
        UserService userService = new UserService(new HashMapUserDao());

        UserCreateRequest request = new UserCreateRequest(
            "yelim980103@gmail.com", "김예림", "password");
        userService.create(request);
    }
}
public class Test {

    public static void main(String[] args) {
        /**
         * MySqlUserDao 를 사용하고 싶은 경우
         */
        UserService userService = new UserService(new MySqlUserDao());

        UserCreateRequest request = new UserCreateRequest(
            "yelim980103@gmail.com", "김예림", "password");
        userService.create(request);
    }
}

 

코드가 이렇게 수정되면서 Test 클래스는 얼떨결에

무슨 UserDao 를 사용할지 결정하는 역할을 떠맡게 되었다.

 

각 객체들이 자신의 역할에 충실하도록, UserService 객체를 생성하는 부분을 분리해보겠다.

public class UserServiceFactory {

    public UserService create() {
        return new UserService(new HashMapUserDao());
    }
}
public class Test {

    public static void main(String[] args) {
        UserService userService = new UserServiceFactory().create();

        UserCreateRequest request = new UserCreateRequest(
            "yelim980103@gmail.com", "김예림", "password");
        userService.create(request);
    }
}

 

 

위 코드에서 UserServiceFactory 는 어떤 객체가 어떤 객체를 사용하는지 정해서 객체를 생성하는 일을 맡았다.

 

 

2. 코드 상 의존관계 vs 런타임 의존관계

앞서 작성한 코드의 의존관계는 다음과 같다.

 

코드상으로 UserService 클래스는 UserDao 인터페이스에는 의존하지만 HashMapUserDao 클래스나 MySqlUserDao 클래스에 의존하지 않는다. 이렇게 우리가 보통 말하는 의존관계는 모델이나 코드에서 클래스와 인터페이스를 통해 드러나는 의존관계이다.

 

그래도 어쨌든 앞서 작성한 코드가 실행되면 UserService 객체는 HashMapUserDao 객체나 MySqlUserDao 객체를 사용할 것이다. 이 관계는 런타임 의존관계이다. 런타임 시에 UserService 객체와 HashMapUserDao (또는 MySqlUserDao) 객체가 의존관계를 맺는 것이다.

 

 

3. DI (Dependency Injection)

DI(Dependency Injection, 의존관계 주입) (이)란 다음 세 가지 조건을 충족하는 작업을 말한다.

 

 1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다.

     그러기 위해서는 인터페이스에만 의존하고 있어야 한다.

 2. 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제3의 존재가 결정한다.

 3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

 

의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 객체의 관계를 맺도록 도와주는 제 3의 존재가 있다는 것이다.

위 코드에서 제 3의 존재는 UserServiceFactory 였다.

스프링에는 Application Context 또는 빈팩토리, 또는 스프링 컨테이너라고 불리는 객체가 존재하는데, 이 객체도 제 3의 존재에 해당한다.

 


출처 : 토비의 스프링 3.1 vol 1 (이일민)