Spring - IoC, DI

제훈·2024년 8월 9일

Spring

목록 보기
3/18
post-thumbnail

IoC

IoC : 제어의 역전(IoC, Inversion of Control)은 일반적인 프로그래밍에서, 프로그램의 제어 흐름 구조가 뒤바뀌는 것을 의미한다.

=> 객체의 생성 및 관리, 객체 간의 의존성 처리 등을 프레임워크에서 대신 처리해주는 것이 대표적인 예시

개발자가 소스코드를 작성해서 실행하면 컨테이너가 주도권을 가지고 돌린다.

ex) 이전 글의 ApplicationContext
사실 이전 글에서 했던 예제로 다루면서 IoC에 대해서 이야기 한 것과 같다.

IoC 예제보러 가기

  • Application Context란?

    BeanFactory를 확장한 IoC 컨테이너로 Bean을 등록하고 관리하는 기능은 BeanFactory와 동일하지만 스프링이 제공하는 각종 부가 기능을 추가로 제공한다.

아래의 Bean에 대해서 읽어보고 다시 보자.


Sprint IoC Container

Bean

Bean : Spring IoC Container에서 관리되는 객체

⇒ 스프링은 Bean을 생성하고, 초기화하고, 의존성 주입하고, 제거하는 등의 일을 IoC Container를 통해 자동으로 처리할 수 있다.

Bean Factory

BeanFactory : Spring IoC Container의 가장 기본적인 형태로, Bean의 생성, 초기화, 연결, 제거 등의 라이프사이클을 관리한다.

⇒ 이를 위해 Configuration Metadata를 사용한다.

  • Configuration Metadata : BeanFactory가 IoC를 적용하기 위해 사용하는 설정 정보

  • ListableBeanFactory : BeanFactory가 제공하는 모든 기능을 포함한다.
  • ApplicationEventPublisher : 이벤트 처리(Event Handling) 기능을 제공한다.
  • MessageSource : 국제화(i18n) 를 지원하는 메세지를 해결하는 부가 기능을 제공한다.
  • ResourceLoader : 리소스 핸들링(Resource Handling) 기능을 제공한다.
  • GenericXmlApplicationContext : ApplicationContext를 구현한 클래스. XML MetaData Configuration을 읽어 컨테이너 역할을 수행한다.
  • AnnotationConfigApplicationContext : ApplicationContext를 구현한 클래스. Java MetaData Configuration을 읽어 컨테이너 역할을 수행한다.

Dependency Injection - DI

Dependency Injection (의존성 주입, 이하 DI) : 객체 간의 의존 관계를 빈 설정 정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것
-> 객체 간의 결합도를 낮출 수 있으며 이로 인해 유지보수성과 유연성이 증가

활용 예제 1

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="account" class="com.jehun.common.PersonalAccount">
        <constructor-arg index="0" value="20"/>
        <constructor-arg index="1" value="110-234-456789"/>
    </bean>
</beans>

MemberDTO

package com.jehun.common;

import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter @ToString
public class MemberDTO {
    private int sequence;
    private String name;
    private String phone;
    private String email;
    private Account personalAccount = new PersonalAccount(3, "123-2345-6734");
}

Account

package com.jehun.common;

public interface Account {
    // 잔액 조회
    String getBalance();

    // 입금
    String deposit(int money);

    //출금
    String withdraw(int money);
}

PersonalAccount

package com.jehun.common;

public class PersonalAccount implements Account {
    private final int backCode;
    private final String accountNo;
    private int balance;

    public PersonalAccount(int backCode, String accountNo) {
        this.backCode = backCode;
        this.accountNo = accountNo;
    }

    @Override
    public String toString() {
        return "PersonalAccount{" +
                "backCode=" + backCode +
                ", accountNo='" + accountNo + '\'' +
                ", balance=" + balance +
                '}';
    }

    @Override
    public String getBalance() {
        return accountNo + " 계좌의 현재 잔액은 " + balance + "입니다.";
    }

    @Override
    public String deposit(int money) {
        String string = "";

        if (money >= 0) {
            this.balance += money;
            string = money + "원이 입금되었습니다.";
        } else {
            string = "금액을 잘못 입력하셨습니다.";
        }
        return string;
    }

    @Override
    public String withdraw(int money) {
        String string = "";

        if (balance >= money) {
            balance -= money;
            string = money + "원이 출금되었습니다.";
        } else {
            string = "잔액이 부족합니다. 잔액을 확인해주세요.";
        }

        return string;
    }
}

Application

package com.jehun.section01.xmlconfig;

import com.jehun.common.MemberDTO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new GenericXmlApplicationContext("section01/xmlconfig/spring-context.xml");

        MemberDTO member = context.getBean(MemberDTO.class);

        System.out.println(member.getPersonalAccount());
    }
}

사진과 같이 PersonalAccount는 Account에 의존하고 있고, MemberDTO의 Account 필드는 바로 PersonalAccount와 연결돼 있는 것이 아닌, Account를 거쳐서 PersonalAccount의 메소드를 사용할 수 있는 것이다.

이렇게 클래스 간 연결이 돼 있으면 안 되고, DI를 통해 생길 수 있는 문제를 방지한 것이다.

package com.jehun.common;

import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter @ToString
public class MemberDTO {
    private int sequence;
    private String name;
    private String phone;
    private String email;
    private PersonalAccount personalAccount = new PersonalAccount(3, "123-2345-6734");
}

만약에 위의 코드처럼 MemberDTO가 돼 있었다면 Member가 가져야 하는 것을 분리하지 못했기 때문에 DI를 지키지 못한 것이라고 생각하면 된다.

다형성을 이용해서 DI를 지키는 것을 습관화하자!

추가적으로 예제들을 더 알아보자.

활용 예제 2

  • MemberDTO, Account, PersonalAccount 는 그대로

com.jehun.section02.javaconfig 패키지 안에
Application, ContextConfiguration 클래스를 만들고, xml 파일도 수정해보자.

spring-context.xml 내용 추가

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="account" class="com.jehun.common.PersonalAccount">
        <constructor-arg index="0" value="20"/>
        <constructor-arg index="1" value="110-234-456789"/>
    </bean>

    <!-- 1. 생성자 주입 -->
    <bean id="member" class="com.jehun.common.MemberDTO">
        <constructor-arg name="sequence" value="1"/>
        <constructor-arg name="name" value="홍길동"/>
        <constructor-arg name="phone" value="010-1234-5678"/>
        <constructor-arg name="email" value="hong@gmail.com"/>
        <constructor-arg name="personalAccount">
            <ref bean="account"/>
        </constructor-arg>
    </bean>

    <!-- 2. Setter 주입 -->
<!--    <bean id="member" class="com.jehun.common.MemberDTO">-->
<!--        <property name="sequence" value="1"/>-->
<!--        <property name="name" value="홍길동"/>-->
<!--        <property name="phone" value="010-1234-5678"/>-->
<!--        <property name="email" value="hong@gmail.com"/>-->
<!--        <property name="personalAccount" ref="account"/>-->
<!--    </bean>-->
</beans>

Application

package com.jehun.section02.javaconfig;

import com.jehun.common.MemberDTO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

        MemberDTO member = context.getBean("memberGenerator", MemberDTO.class); // 특정 bean을 가져오려면 메소드명을 이름처럼 넣어주면 된다.
        System.out.println("member = " + member);
        System.out.println("member.getPersonalAccount() = " + member.getPersonalAccount());

        System.out.println("입금 : " + member.getPersonalAccount().deposit(100000));
        System.out.println("잔액 확인 : " + member.getPersonalAccount().getBalance());

        System.out.println("출금 : " + member.getPersonalAccount().withdraw(30000));
        System.out.println("잔액 확인 : " + member.getPersonalAccount().getBalance());

    }
}

ContextConfiguration

package com.jehun.section02.javaconfig;

import com.jehun.common.Account;
import com.jehun.common.MemberDTO;
import com.jehun.common.PersonalAccount;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ContextConfiguration {

    @Bean
    public Account accountGenerator() {
        return new PersonalAccount(20, "123-45-678989");
    }

    @Bean
    public MemberDTO memberGenerator() {
        // 생성자 방식
        return new MemberDTO(1, "홍길동", "010-1234-5678", "hong@gmail.com", accountGenerator());

        // setter 방식
//        MemberDTO member = new MemberDTO();
//        member.setSequence(1);
//        member.setName("홍길동");
//        member.setPhone("010-1234-5678");
//        member.setEmail( "hong@gmail.com");
//        member.setPersonalAccount(accountGenerator());
//
//        return member;
    }
}

생성자 방식을 사용하는 것이 더 좋다. 일단은 2개 다 적어놨다.
추가로 인터페이스에서 정의해둔 메소드들에 대해서도 구현해봤다.

실행결과

profile
백엔드 개발자 꿈나무

0개의 댓글