IoC : 제어의 역전(IoC, Inversion of Control)은 일반적인 프로그래밍에서, 프로그램의 제어 흐름 구조가 뒤바뀌는 것을 의미한다.
=> 객체의 생성 및 관리, 객체 간의 의존성 처리 등을 프레임워크에서 대신 처리해주는 것이 대표적인 예시
개발자가 소스코드를 작성해서 실행하면 컨테이너가 주도권을 가지고 돌린다.

ex) 이전 글의 ApplicationContext
사실 이전 글에서 했던 예제로 다루면서 IoC에 대해서 이야기 한 것과 같다.
BeanFactory를 확장한 IoC 컨테이너로 Bean을 등록하고 관리하는 기능은 BeanFactory와 동일하지만 스프링이 제공하는 각종 부가 기능을 추가로 제공한다.
아래의 Bean에 대해서 읽어보고 다시 보자.
Bean : Spring IoC Container에서 관리되는 객체
⇒ 스프링은 Bean을 생성하고, 초기화하고, 의존성 주입하고, 제거하는 등의 일을 IoC Container를 통해 자동으로 처리할 수 있다.
BeanFactory : Spring IoC Container의 가장 기본적인 형태로, Bean의 생성, 초기화, 연결, 제거 등의 라이프사이클을 관리한다.
⇒ 이를 위해 Configuration Metadata를 사용한다.

ListableBeanFactory : BeanFactory가 제공하는 모든 기능을 포함한다.ApplicationEventPublisher : 이벤트 처리(Event Handling) 기능을 제공한다.MessageSource : 국제화(i18n) 를 지원하는 메세지를 해결하는 부가 기능을 제공한다.ResourceLoader : 리소스 핸들링(Resource Handling) 기능을 제공한다.GenericXmlApplicationContext : ApplicationContext를 구현한 클래스. XML MetaData Configuration을 읽어 컨테이너 역할을 수행한다.AnnotationConfigApplicationContext : ApplicationContext를 구현한 클래스. Java MetaData Configuration을 읽어 컨테이너 역할을 수행한다.Dependency Injection (의존성 주입, 이하 DI) : 객체 간의 의존 관계를 빈 설정 정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것
-> 객체 간의 결합도를 낮출 수 있으며 이로 인해 유지보수성과 유연성이 증가
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를 지키는 것을 습관화하자!
추가적으로 예제들을 더 알아보자.
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개 다 적어놨다.
추가로 인터페이스에서 정의해둔 메소드들에 대해서도 구현해봤다.
실행결과
