[Spring] IoC / DI
IoC (Inversion of Control) : 제어의 역행
Q. 스프링을 사용하기 전에는 객체간의 결합을 누가 관리했을까?
A. 개발자 -> 높은 결합도
스프링을 사용하기 전에는 개발자가 프로그램의 흐름을 제어하는 주체였다면,
스프링에서는 "낮은 결합도" 유지를 위해 객체간의 의존 관계를 컨테이너가 대신 처리하게 된다.
즉, 객체의 생성 및 생명주기 관리를 컨테이너가 처리하게 되고,
프로그램 흐름의 제어권이 개발자에서 컨테이너로 넘어가게 되어 이것을 제어의 역행이라고 한다.
- 개발자가 의존관계를 변경해야할 때, 자바 코드 수정 필요 ex) 이를 해결하기 위해 DAO의 메소드 인자를 VO로 사용
- IoC가 적용되면 컨테이너가 객체를 생성하므로, 자바 코드 변경이 필요하지 않음 -> 낮은 결합도
- 요청 / 응답 흐름)
사용자 --web.xml--> 톰캣 <-frontcontrol-> Dispatcher, Controller(Action) 요청을 분석하여 응답 --포워딩--> View - Controller 코드 변경 시, 함께 수정될 코드를 줄여나가는 것, 즉, 결합도를 낮추는 것이 스프링을 사용하는 목적
- 외부에서 설정파일(pom.xml, applicationContext.xml)로 조작하여 의존성 주입 (DI, Dependency Injection)
-> 객체간의 의존 관계를 관리
※ 높은 응집도 : 클래스를 기능적 연관도에 따라 분류하여 구현하면, 변경 대상과 범위가 명확해져 유지 보수에 용이
※ 낮은 결합도 : 클래스를 분리시켜 결합도를 낮추면, 코드 수정 시 다른 클래스에 영향을 미치지 않아 유지 보수에 용이
낮은 결합도
결합도를 낮추기 위한 방법
예제) 스프링 사용 X
1) 설계를 통한 상속 관계 정의 (인터페이스 사용), 형변환 (다형성) -> 객체를 교체하는데에 용이
// 인터페이스는 결합도를 낮춰준다!
public interface Phone {
void powerOn();
void powerOff();
void volumeUp();
void volumeDown();
}
public class IPhone implements Phone {
public void volumeUp() {
System.out.println("볼륨++");
}
public void volumeDown() {
System.out.println("볼륨--");
}
public void powerOn() {
System.out.println("전원 ON");
}
public void powerOff() {
System.out.println("전원 OFF");
}
}
public class GaPhone implements Phone {
public void volumeUp() {
System.out.println("볼륨 += 10");
}
public void volumeDown() {
System.out.println("볼륨 -= 10");
}
public void powerOn() {
System.out.println("전원 켜짐");
}
public void powerOff() {
System.out.println("전원 꺼짐");
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new IPhone(); // 타입을 인터페이스로 설정하면 결합도가 낮아진다!
phone.powerOn();
phone.volumeUp();
phone.volumeDown();
phone.powerOff();
}
}
2) BeanFactory 사용
디자인 패턴의 일종으로, 클라이언트가 사용할 객체 생성 코드를 캡슐화함
클라이언트는 필요한 객체를 BeanFactory에 요청
실질적으로 BeanFactory가 클라이언트가 사용할 객체를 생성하여 반환
public class BeanFactory {
public Object getBean(String beanName) {
// 인자값에 맞는 객체를 생성하여 건네줌
if (beanName.equals("Ga")) {
return new GaPhone();
} else if (beanName.equals("I")) {
return new IPhone();
}
return null;
}
}
public class Client {
public static void main(String[] args) {
BeanFactory factory = new BeanFactory();
Phone phone = (Phone) factory.getBean(args[0]); // getBean()는 리턴값이 object이므로 캐스팅 필요
phone.powerOn();
phone.volumeUp();
phone.volumeDown();
phone.powerOff();
}
}
스프링 사용 시 IoC 설정 파일 생성하기
스프링 컨테이너가 관리할 bean(객체)을 등록(객체화)할 설정파일(applicationContext.xml) 등록
src/main/resource 내부에 spring bean configuration file로 추가
[applicationContext.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 스프링 컨테이너가 관리할 클래스 등록 == 객체화는 반드시 기본생성자로! -->
<bean class="test.Test" id="test" />
<bean class="test.IPhone" id="Phone" />
<!--
<bean class="패키지명.클래스명" id="객체명"></bean>
class 필수 속성 / id 선택 속성(빈 id를 이용해 빈에 접근) -->
<!-- 객체 변경 시 현재 설정 파일에서 수정해주면 되므로 자바 코드는 수정할 필요가 없다! -->
</beans>
[main]
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class Client {
public static void main(String[] args) {
// 1. 스프링 컨테이너 동작시키기
AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
// 2. 객체 요청(==Look up) 시 해당 객체를 건네줌
Test t = (Test) factory.getBean("test"); // 파라미터 값 == applicationContext.xml에 bean 등록 시 설정한 id 값
t.print();
Phone phone = (Phone) factory.getBean("Phone");
phone.powerOn();
phone.volumeUp();
phone.volumeDown();
phone.powerOff();
// 3. 스프링 컨테이너 종료
factory.close();
// console
// 현재 <bean>으로 등록된 모든 클래스에 대한 객체를 미리 생성해 놓음
// pre-loading (즉시 로딩) <-> lazy-loading (지연 로딩) : bean 등록 시 설정 가능
// 기본 생성자 호출됨!
}
}
DI (Dependency Injection) 의존성 주입
사용할 클래스와 사용될 클래스의 관계를 개발자가 직접 코딩을 통해 컴포넌트(클래스)에 부여하는 것이 아니라,
컨테이너가 클래스 간의 연관 관계를 직접 규정하는 것.
즉, 객체를 직접 생성하는 것이 아니라 외부에서 생성한 후 주입시켜주는 방식
의존성 주입으로 객체간의 의존성(결합) 관리 -> 낮은 결합도 -> 유지보수 용이
1. Setter Injection (setter 주입)
bean(객체) 먼저 생성 후 setter 주입 수행
property : setter를 이용해 값을 주입할 때 사용
☆기본 생성자 반드시 필요
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 세터 주입 (Setter Injection)
흐름 : bean 객체 먼저 생성 후 setter 주입 수행 -->
<!-- property name="멤버변수명" ref="객체" -->
<!-- property name="멤버변수명" value="값" -->
<bean class="test.IPhone" id="phone">
<property name="watch" ref="aw"></property>
<property name="user" value="seong"></property>
</bean>
</beans>
위의 코드 대신 [Namespace] 'p'를 추가하여 다음과 같이 간단하게 작성 가능하다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="test.IPhone" id="phone" p:watch-ref="aw" p:user="seong" />
<bean class="test.GalaxyWatch" id="gw" scope="prototype" />
<bean class="test.AppleWatch" id="aw" scope="prototype" />
<!-- scope=prototype : 사용자가 객체를 만들때마다 매번 객체 생성 -->
</beans>
컬렉션 프레임워크 List, Map 객체 주입
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 컬렉션 프레임워크 사용 -->
<bean class="collectionBean.CBean" id="cb">
<property name="list">
<list>
<value>apple mango</value>
<value>바나나</value>
<value>키위새</value>
</list>
</property>
<property name="map">
<map>
<entry>
<key><value>apple</value></key>
<value>사과</value>
</entry>
<entry>
<key><value>banana</value></key>
<value>바나나</value>
</entry>
<entry>
<key><value>kiwi</value></key>
<value>새</value>
</entry>
</map>
</property>
</bean>
</beans>
2. Constructor Injection (생성자 주입)
constructor-arg : 생성자를 이용해 값을 주입할 때 사용
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 생성자 주입 (Constructor Injection) -->
<!-- constructor-arg ref="객체" : 생성자 호출 시 인자로 넘길 객체 -->
<!-- constructor-arg value="값" : 생성자 호출 시 인자로 넘길 값 -->
<bean class="test.IPhone" id="phone">
<constructor-arg ref="gw"></constructor-arg>
<constructor-arg value="아이폰유저"></constructor-arg>
</bean>
<bean class="test.GalaxyWatch" id="gw" scope="prototype" />
<bean class="test.AppleWatch" id="aw" scope="prototype" />
</beans>
참고) 스프링 컨테이너와 서블릿 컨테이너의 유사점
서블릿은 자바로 구성된 클래스로써 사용 시 객체화가 필요한데 그렇다면
Q. 서블릿 객체를 누가 생성했을까?
A. Servlet Container
Q. doGet() 함수를 어떻게 호출했을까?
A. Servlet Container
즉, 개발자가 서블릿 클래스를 제작하고 web.xml 설정 파일 작성하면 (컨테이너에게 알려주기 위해)
컨테이너는 다음과 같은 과정을 수행한다.
1) web.xml 설정 파일들을 loading(적재)
2) Servlet Container 준비
3) Client --- /*.do GET 요청 ---> 서블릿 컨테이너가 읽음
4) 설정파일 확인 후, 매핑된 서블릿 클래스를 찾아서 객체를 생성
5) doGet() 함수 호출
6) 결과 전송 (response)