모래성 말고 철옹성

스프링 핵심 개념 IoC, DI, Spring Container 본문

스프링

스프링 핵심 개념 IoC, DI, Spring Container

JDhyeok 2024. 1. 29. 23:16

개요

스프링의 핵심적인 세 가지 개념인 IoC, DI, Spring Container를 알아보기 전에 어떤 페인포인트가 있었길래 이 세 가지 개념들이 스프링에 등장하게 되었는지 간단한 로직을 순수한 Java코드에서 스프링 코드로 발전시켜 나가면서 하나씩 알아보겠다.

 

코드레벨로 진입하기 전 한 가지 상황을 가정해서 코드로 발전시켜 나가보겠다.

1) 비즈니스 요구사항

사내 동아리 활동을 활성화 시키기 위해 동아리 웹사이트를 제작하려고 한다. 사용자는 웹사이트에서 사용자 등록을 하고 동아리인 그룹에 참여할 수 있다.

2) 클래스 도출

  • User Class : 사용자의 속성정보를 나타내는 모델 클래스.
  • UserService Class : 사용자와 관련된 비즈니스 로직을 처리하는 클래스. ex) 사용자 등록/제거
  • GroupService Class : 그룹과 관련된 비즈니스 로직을 처리하는 클래스. ex) 동아리 참여/탈퇴

이제 어느정도 각이 나왔으니 개발로 들어가보자

기존 Pure Java 코드

1) User Class

public class User {
    String userId;
    String name;

    public User(String userId, String name) {
        this.userId = userId;
        this.name = name;
    }

    // Getter, Setter 생략...
}

 

2) UserService Class

public class UserService {

    /*
    * 사용자 등록 메소드
    */
    public void createUser(User user){
        System.out.println("userId: " + user.getUserId() + ", name: " + user.getName()
                           + " 생성 완료");
    }
}

 

3) GroupService Class

public class GroupService {
    /*
    * 그룹 참여 메소드
    */
    void joinGroup(User user){
        System.out.println(user + " is joined");
    }
}

 

4) 이제 도출된 세 가지 클래스로 유저를 등록해 동아리에 참여하는 로직으로 마무리 해보자.

public class Application {
    public static void main(String[] args) {
        User user = new User("1", "user1");
        UserService userService = new UserService();
        GroupService groupService = new GroupService();

        userService.createUser(user);
        groupService.joinGroup(user);
    }
}

 

기존 Pure Java 코드의 문제점

우리는 메인 프로그램에서 모든 객체들을 프로그램 내에서 개발자가 흐름을 직접 제어했다. 즉, 객체들을 직접 생성하고 사용(의존)했다. 이렇게 객체간의 의존도와 결합도가 높고 클라이언트 단에서 직접 제어하게 된다면 코드의 복잡성이 올라가고 유지보수가 어렵게 된다. (의존도와 결합도가 미치는 영향에 대해선 따로 포스팅 해보겠다.)

 

"개발자특"인 것 같은데, 코드는 최대한 짧고! 가독성 좋고! 간결하게 만드는걸 좋아하는 것 같다. 이제 이 문제점들을 하나 씩 해결해보자.

제어의 역전 - IoC (Inversion of Controll)

위의 저 4) 코드가 굉장히 맘에 안든다. 유저를 등록하고, 그룹에 참여시키기 위해 계속해서 저 객체를 생성해야할까? 어짜피 똑같은 로직인데?

 

다시 말하지만 개발자는 "반복"을 극혐한다. 같은 로직을 수행하기 위해 메모리에 같은 객체를 계속 생성해 수십 개 띄워 놓는 것은 굉장히 비합리적이며 비효율적이다. 이렇게 개발자는 객체의 생성/생명주기/소멸 등을 관리하는 책임(제어)을 스프링에게 짬 때리게 된다.

 

이 짬때리는 행위를 "제어의 역전"이라고 한다.

 

이제 제어를 역전해보자

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public GroupService groupService() {
        return new GroupService();
    }
}

 

위 코드는 스프링에게 객체에 대한 책임을 떠넘긴 Inversion of Controll을 한 코드이다. 코드에 대해 자세하게 설명해보자면

  • @Configuration: 프로그램 구동 시 스프링이 Configuration 어노테이션이 달린 클래스부터 읽고 시작한다.
  • @Bean: @Configuration파일 안에 Bean 어노테이션이 객체를 생성해 반환하는 메소드에 있다면 스프링이 내가 책임(제어)해야하는 객체로 인식하고 객체를 생성해 스프링 컨테이너에 담아둔다.

의존성 주입 - DI (Dependency Injection)

이제 객체에 대한 권한을 스프링에게 넘겼으니 스프링이 관리하는 스프링 빈(객체)를 가져와 내가 사용해볼 차례다. 이 것을 우리는 "의존성을 주입한다"라고 한다.

 

이제 의존성을 주입해보자

public class Application {
    public static void main(String[] args) {
        // 1. 스프링 컨테이너를 불러온다.
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        // 2. 스프링 컨테이너에 있는 스프링 빈을 가져와 의존성을 주입해준다.
        UserService userService = ac.getBean("userService", UserService.class);
        GroupService groupService = ac.getBean("groupService", GroupService.class);

        // 3. 주입된 객체를 사용한다.
        User user = new User("1", "user1");
        userService.createUser(user);
        groupService.joinGroup(user);
    }
}

 

스프링에서 ApplicationContext 클래스는 스프링 컨테이너 역할을 하고있는데, 스프링 컨테이너에는 메소드 이름과 스프링 빈이 Hash의 key, value처럼 매핑되어있다. 따라서 스프링 빈을 꺼내와 의존성을 주입해 줄 때 위와 같이 "userService" key값을 가진 빈을 찾아 올 수 있는 것이다.

 

이로서 우리는 하나의 객체로 굉장히 효율적인 코드를 작성할 수 있게 됐다.

// DI주입 시 수백수천번 호출해도 같은 인스턴스이다.
UserService userService = ac.getBean("userService", UserService.class);

// 객체 생성 수백수천번 호출하면 수백수천개의 객체가 메모리에 생성된다.
UserService userService = new UserService();

 

스프링 컨테이너 ( = IoC Container, DI Container)

앞선 IoC와 DI를 설명할 때 스프링 컨테이너라는 생소한 단어가 나와 당황했을 것이다. 앞의 내용을 정확하게 이해하고 컨테이너라는 단어의 의미만 파악했다면 어림짐작 했을 것이다. (스프링 컨테이너, IoC 컨테이너, DI 컨테이너 모두 같은 말이다)

 

스프링 컨테이너는 스프링빈을 담아두는 공간을 뜻한다.

스프링 세 가지 요소에 대한 도식화

 

위 코드들을 스프링 부트를 통해 더 간결하게 만들어 줄 수 있다. 그건 다음 포스팅 때 해보기로 한다.

 

 

⚠️ 본 포스팅은 본인이 이해한 대로 마구잡이로 써서 단어의 선택이 좀 경박할 수 있습니다...ㅎ

 

반응형

'스프링' 카테고리의 다른 글

스프링빈 vs 자바빈 vs POJO  (0) 2024.02.01
Comments