본문 바로가기

학원/복기

[Spring] 제어의 역전(Inversion of Control, IoC)

Inversion of Control(IoC) : 제어의 역전

 

IoC는 소프트웨어 개발에서 객체의 생성과 관리를 개발자가 아닌 프레임워크나 컨테이너가 담당하는 디자인 패턴이다.

 


 

xyz.itwill01.old 패키지 생성

 

객체간의 결합도

 

먼저 컨테이너를 이용하지 않고 클래스 내에서 객체를 생성해 메소드를 호출해보자.

 

package xyz.itwill01.old;

//DAO 클래스
public class HelloMessageObject {
	public String getHelloMessage() {
		return "Hello!!!";
	}
}

//서비스 클래스
public class MessagePrint {
	public void helloMessagePrint() {
		HelloMessageObject object=new HelloMessageObject();//객체생성
		String message=object.getHelloMessage();
		System.out.println("message = "+message);
	}
}

public class MessagePrintApp {
	public static void main(String[] args) {
		MessagePrint print=new MessagePrint();
		print.helloMessagePrint();
	}
}

 

main 메소드에서 실행된다.

 

 

하지만 이렇게 만들어주면 객체간의 결합도가 굉장히 높아진다.

기존의 클래스가 바뀌면 해당 클래스와 관계가 있는 다른 클래스도 바꾸어주어야 하기 때문에 유지보수가 어렵다.

 


 

예를 들어 보자.

 

package xyz.itwill01.old;

public class HiMessageObject {
	public String getHiMessage () {
		return "Hi!!!";
	}
}

public class MessagePrint {
	public void helloMessagePrint() {
		HelloMessageObject object=new HelloMessageObject();//객체생성
		String message=object.getHelloMessage();
		System.out.println("message = "+message);
	}
	
	//기존의 메소드 건들지 않고 새로운 메소드 선언
	public void hiMessagePrint() {
		HiMessageObject object=new HiMessageObject();
		String message=object.getHiMessage();
		System.out.println("message = "+message);
	}
}

public class MessagePrintApp {
	public static void main(String[] args) {
		MessagePrint print=new MessagePrint();
		print.helloMessagePrint();//message = Hello!!!
		print.hiMessagePrint();//message = Hi!!!
	}
}

 

 

객체간의 결합도가 높으면 기존의 클래스가 바뀌면 해당 클래스와 관계있는 클래스까지 모두 바꾸어 주어야 하기 때문에 유지보수의 효율성이 떨어진다는 문제점이 발생한다.

 

이를 해결해주기 위한것이 제어의 역행이다.


1) Factory 디자인 패턴

 

오버라이드에 의한 다향성을 통해 객체간의 결합도를 감소시킬 수 있다.

 

먼저 Factory 클래스로 제공될 객체를 생성하는 클래스가 반드시 상속받아야 되는 인터페이스를 선언해준다.

→ Factory 클래스로 제공받은 객체가 변경돼도 메소드는 변경되지 않도록 인터페이스를 상속한다.

 

인터페이스를 상속받은 자식클래스는 반드시 인터페이스에 선언된 모든 추상메소드를 오버라이드 선언해주어야 한다.

이처럼 인터페이스는 인터페이스를 상속받은 클래스의 메소드 작성 규칙을 제공해주는 작업 지시서의 역할을 한다.

 

인터페이스로 참조변수를 생성하면 참조변수에는 인터페이스를 상속받은 모든 자식클래스의 객체를 저장할 수 있다.

→ 인터페이스로 생성된 참조변수로 추상메소드를 호출하면 참조변수에 저장된 자식 객체의 오버라이드 메소드가 호출된다. 이를 오버라이드에 의한 다향성 이라고 하며, 이를 이용해 객체간의 결합도를 감소시킬 수 있다.

 

 

예제)

xyz.itwill02.factory 패키지 생성

 

인터페이스를 선언해주자 

package xyz.itwill02.factory;

//Factory 클래스로 제공될 객체를 생성하는 클래스가 반드시 상속받아야 되는 인터페이스
public interface MessageObject {
	String getMessage();//추상 메소드 선언
}

 

 

Factory 클래스로 제공받은 객체가 변경돼도 메소드는 변경되지 않도록 반드시 인터페이스를 상속받아야 한다.

package xyz.itwill02.factory;

//Factory 클래스로 제공받은 객체가 변경돼도 메소드는 변경되지 않도록 반드시 인터페이스를 상속
public class HelloMessageObject implements MessageObject {
	@Override
	public String getMessage() {
		return "Hello!!!";
	}
	
}

 

Factory 디자인 패턴이 적용된 [MessageObjectFactory] 클래스 선언

 

Factory 디자인 패턴을 이용하여 작성된 클래스를  Factory 클래스(Provider 클래스) 라고 부른다.

Factory 클래스(Provider 클래스)는 프로그램 개발에 필요한 객체를 생성하여 제공하는 기능을 제공하는 클래스이다. 이를 컨테이너(Container)라고 부른다.

 

package xyz.itwill02.factory;

//Factory 디자인 패턴을 이용하여 작성된 클래스 - Factory 클래스(Provider 클래스)
public class MessageObjectFactory {
	//Factory 클래스에 의해 제공될 객체를 구분하기 위한 상수(Constant) 
	public static final int HELLO_MSG=1;
	
	//매개변수에 전달된 값을 비교하여 필요한 객체를 생성하여 반환하는 메소드
	// => 인터페이스를 상속받은 클래스로 객체를 생성하여 반환
	public static MessageObject getMessageObject(int messageObject) {
		MessageObject object=null;
		switch(messageObject) {
		case HELLO_MSG:
			object=new HelloMessageObject();
		}
		return object;
	}
}

 

[MessagePrint] 클래스 선언

 

객체를 직접 생성하여 메소드를 호출하면 객체간의 결합도가 높아 유지보수의 효율성이 감소한다.

public class MessagePrint {
	public void messagePrint() {
		//객체를 직접 생성하여 메소드 호출 - 객체간의 결합도가 높아 유지보수의 효슐성이 감소
		MessageObject object=new HelloMessageObject();
	}
}

 

프로그램 실행에 필요한 객체를 Factory 클래스로부터 제공받아 메소드를 호출할 수 있는데, 이를 IoC(제어의 역전)라고 한다.

제어의 역전을 이용하면 객체간의 결합도를 낮춰 유지보수의 효율성이 증가한다.

package xyz.itwill02.factory;

public class MessagePrint {
	public void messagePrint() {
		//프로그램 실행에 필요한 객체를 Factory 클래스로부터 제공받아 메소드 호출
		// => IoC(Inversion of Control) 
		MessageObject object=MessageObjectFactory.getMessageObject(1);
		
		//인터페이스로 생성된 참조변수로 추상메소드를 호출할 경우 참조변수에 저장된 자식 객체의
		//오버라이드 메소드가 호출된다. - 묵시적 객체 형변환 : 오버라이드에 의한 다향성
		String message=object.getMessage();
		System.out.println("message = "+message);
	}
}

 

[MessagePrintApp] 클래스 선언해 출력해보자

package xyz.itwill02.factory;

public class MessagePrintApp {
	public static void main(String[] args) {
		MessagePrint print=new MessagePrint();
		print.messagePrint();//message = Hello!!!
	}
}

 

Hi!!! 를 출력하고 싶으면,

 

다시 MessageObject 인터페이스를 상속받는 [HiMessageObject] 클래스를 선언해준다.

package xyz.itwill02.factory;

public class HiMessageObject implements MessageObject {
	@Override
	public String getMessage() {
		return "Hi!!!";
	}
}

 

[MessageObjectFactory] 팩토리 클래스 변경

package xyz.itwill02.factory;

//Factory 디자인 패턴을 이용하여 작성된 클래스 - Factory 클래스(Provider 클래스)
public class MessageObjectFactory {
	//Factory 클래스에 의해 제공될 객체를 구분하기 위한 상수(Constant) 
	public static final int HELLO_MSG=1;
	public static final int HI_MSG=2;
	
	//매개변수에 전달된 값을 비교하여 필요한 객체를 생성하여 반환하는 메소드
	// => 인터페이스를 상속받은 클래스로 객체를 생성하여 반환
	public static MessageObject getMessageObject(int messageObject) {
		MessageObject object=null;
		switch(messageObject) {
		case HELLO_MSG:
			object=new HelloMessageObject();
			break;
		case HI_MSG:
			object=new HiMessageObject();
			break;
		}
		return object;
	}
}

 

 

이처럼 인터페이스와 팩토리 클래스를 이용해 결합도를 느슨하게 만들어줄 수 있다.

하지만 여전히 출력되는 값을 바꾸기 위해선 소스코드의 수정이 필요하기 때문에 불편하다.

 


2) 스프링 컨테이너 

 

이번엔 스프링 컨테이너를 이용해 객체의 결합도를 낮추어 보자

 

 

먼저 스프링 컨테이너를 이용하지 않은 예시를 먼저 보자 

 

 

인터페이스 선언

package xyz.itwill03.spring;

public interface MessageObject {
	String getMessage();
}

 

[HelloMessageObject] 클래스 선언 

package xyz.itwill03.spring;

public class HelloMessageObject implements MessageObject {
	@Override
	public String getMessage() {
		return "Hello!!!";
	}
}

 

[MessagePrint] 클래스 선언해 [MessageObject] 인터페이스를 상속받은 자식클래스의 객체를 저장하기 위한 필드를 선언한다.

필드에 객체를 저장하기 위한 생성자 또는 Setter 메소드를 반드시 작성해야하며, 해당 필드에 저장된 객체를 사용하여 메소드 호출이 가능하다.

package xyz.itwill03.spring;

public class MessagePrint {
	//MessageObject 인터페이스를 상속받은 자식클래스의 객체를 저장하기 위한 필드
	private MessageObject object;
	
	public MessageObject getObject() {
		return object;
	}

	public void setObject(MessageObject object) {
		this.object = object;
	}
	
	public void messagePrint() {
		//필드에 객체가 저장되지 않은 경우 메소드 호출시 NullPointerException 발생 
		String message=object.getMessage();
		System.out.println("message = "+message);
	}
}

 

[MessagePrintApp] 클래스 선언

 

객체를 직접 생성해 객체 필드에 객체를 전달(포함관계)하여 저장하여 메소드를 호출할 수 있다.

package xyz.itwill03.spring;

public class MessagePrintApp {
	public static void main(String[] args) {
		HelloMessageObject object=new HelloMessageObject();
		MessagePrint print=new MessagePrint();
		print.setObject(object);//객체 필드에 객체를 전달하여 저장 - 포함관계 완성
		print.messagePrint();//message = Hello!!!
	}
}

 

하지만 직접 객체를 생성하여 만들어주었기 때문에 결합도가 높다.

 


이제 스프링 컨테이너를 이용해보자

 

Spring Framework의 Spring Container 기능을 이용해 객체를 스프링이 대신 만들어줄 수 있다.

이때 Spring Container 역할을 해주는 객체가 ApplicationContext 객체이다.

 

ApplicationContext 객체는 Spring Container 기능을 제공하기 위한 객체이다. Spring Bean Configure File(XML)을 제공받아 객체(SpringBean)를 생성한다.

 

 

XML파일은 src/main/resources 경로에 생성해준다.

 

 

 

생성할 때 <Spring Bean Configuration File> 메뉴를 이용하면 더 간단하게 생성할 수 있다.

 

 

Next

 

xml 파일이 생성된다

 

namespace를 변경하기 위해서는 하단의 [Namespaces]에 들어가서 변경할 수 있다


Spring 컨테이너는 환경설정파일(Spring Bean Configuration File - XML)로부터 클래스를 제공받아 객체를 생성하여 관리한다. - Spring Bean : Spring 컨테이너에 의해 관리되는 객체(클래스)

 

Spring 컨테이너는 리플렉션(Reflection) 기술을 사용하여 객체를 생성한다.

 

bean 엘리먼트를 사용하여 Spring 컨테이너에게 Spring Bean으로 사용될 클래스를 제공해준다.

 

[03_message.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">
	
	<!-- Spring 컨테이너가 class 속성값으로 설정된 클래스를 객체로 만들어준다 -->
	<bean class="xyz.itwill03.spring.HelloMessageObject" id="helloMessageObject"></bean>
	
	<!-- bean 엘리먼트의 하위 엘리먼트를 사용하여 Spring Bean에 대한 포함관계를 설정할 수 있음 - 의존성 주입 -->
	<bean class="xyz.itwill03.spring.MessagePrint" id="messagePrint">
		<property name="object" ref="helloMessageObject"/>
	</bean>
</beans>

 

[MessagePrintApp] 클래스 

package xyz.itwill03.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MessagePrintApp {
	public static void main(String[] args) {
		/*
		HelloMessageObject object=new HelloMessageObject();
		MessagePrint print=new MessagePrint();
		print.setObject(object);//객체 필드에 객체를 전달하여 저장 - 포함관계 완성
		print.messagePrint();//message = Hello!!!
		*/
		
		// ApplicationContext 객체 : Spring Container 기능을 제공하기 위한 객체
		// => Spring Bean Configure File(XML)을 제공받아 객체(SpringBean)를 생성
		ApplicationContext context=new ClassPathXmlApplicationContext("03_message.xml");
		
		//Spring 컨테이너에게 필요한 Spring Bean(객체)를 제공받아 저장
		// => 매개변수에 Spring Bean을 구분하기 위한 식별자(beanName 또는 beanId)를 전달함 
		MessagePrint print=(MessagePrint)context.getBean("messagePrint");
		
		print.messagePrint();//message = Hello!!!
		
		//ApplicationContext 객체 제거 - Spring 컨테이너 소멸
		// => Spring 컨테이너가 관리하는 모든 Spring Bean(객체)이 소멸된다. 
		((ClassPathXmlApplicationContext)context).close();
	}
}

 

 

이게 좋은 이유는, 클래스를 아래의 예시처럼 변경하고 싶을 때 

package xyz.itwill03.spring;

public class HiMessageObject implements MessageObject {
	@Override
	public String getMessage() {
		return "Hi!!!";
	}
}

 

 

[03_message.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">
	
	<!-- Spring 컨테이너가 class 속성값으로 설정된 클래스를 객체로 만들어준다 -->
	<bean class="xyz.itwill03.spring.HelloMessageObject" id="helloMessageObject"></bean>
	<bean class="xyz.itwill03.spring.HiMessageObject" id="hiMessageObject"></bean>

	
	<!-- bean 엘리먼트의 하위 엘리먼트를 사용하여 Spring Bean에 대한 포함관계를 설정할 수 있음 - 의존성 주입 -->
	<bean class="xyz.itwill03.spring.MessagePrint" id="messagePrint">
		<!-- <property name="object" ref="helloMessageObject"/> -->
		<property name="object" ref="hiMessageObject"/>
	</bean>
</beans>

 

스프링 컨테이너가 알아서 객체를 만들어주고 관계를 설정해준다.

 

 

참고:https://agentsmith.tistory.com/12

'학원 > 복기' 카테고리의 다른 글

[Spring] 의존성 주입(Dependency Injection)  (0) 2023.07.30
[Spring] 스프링 빈(Spring Bean)  (0) 2023.07.26
[Spring] 로그 구현체 설정  (0) 2023.07.26
[Spring] 환경설정  (0) 2023.07.24
[Spring] Spring 프레임워크란?  (0) 2023.07.24