본문 바로가기
Spring

2022-11-24 스프링 - IoC(제어의 역전)컨테이너를 이용해 결합도 낮은 코드만들기

by HTT 2022. 11. 24.
IoC(Inversion of Control, 제어의 역전)

 


 

- 사전적 정의

: 제어 반전, 제어의 반전, 역제어는 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다. 줄여서 IoC이라고 부른다. 전통적인 프로그래밍에서 흐름은 프로그래머가 작성한 프로그램이 외부 라이브러리의 코드를 호출해 이용한다. 위키백과

 

 

  • 작업을 구현하는 방식과 작업 수행 자체를 분리한다.
  • 모듈을 제작할 때, 모듈과 외부 프로그램의 결합에 대해 고민할 필요 없이 모듈의 목적에 집중할 수 있다.
  • 다른 시스템이 어떻게 동작할지에 대해 고민할 필요 없이, 미리 정해진 협약대로만 동작하게 하면 된다.
  • 모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.

 

- 쉽게 말하자면 이전에는 개발자가 내부에서 사용되는 구현클래스를 직접 코드로 구현했던 일을 이제는 스프링 프레임워크가 대신 해주는 것이다. 그렇다면 결합도는 낮아지고 수정해야 할 코드의 수도 줄어든다. 여기서 결합도가 높다는 말은 커플링이 높다는 말과 같다. 개발자는 다른 핵심 업무에 시간을 더 쏟을 수 있다는 장점이 있다(나의 생각...).

 

 

Spring Framework

 

종속성 주입, 트랜잭션 관리, 웹 앱, 데이터 엑세스, 메시징 등에 대한 핵심 지원을 제공하는 스프링의 프로젝트 중 하나.

 

 

- 결합도가 높은 코드의 예시를 보자.

CustomerDAO dao = new MybatisCustomerDAO();

=> 만약 CustomerDAO클래스를 상속하는 MybatisCustomerDAO클래스가 있고 이 클래스가 Mybatis의 역할을 한다고 가정해보자. 만약 Mybatis가 아니라 JPA, SpringJDBD... 등으로 변경하여 작업하고 싶다면? 이 객체를 생성하고 있는 클래스에 가서 각각 new JPACustomerDAO, new SpringJDBDCustomerDAO 등으로 바꿔주고, 프레임워크를 변경할 때마다 이 작업을 매번 반복해야 할 것이다.

이렇게 되면 기존에 잘 실행되던 소스가 변경되고 에러가 발생할 확률이 높아진다. OCP원칙이 지켜지지 않는 것이다.

 

위의 코드는 내부에서 상위 인터페이스인 CustomerDAO에만 의존하고 있지 않고, 자식객체(상위 인터페이스를 구현한 클래스) SpringJDBDCustomerDAO에도 의존하고 있다. 이렇게 상위 인터페이스와 하위클래스에 모두 의존하게 되면 코드가 변경될 수 밖에 없다. DIF의 원칙이 지켜지지 못하는 것이다.

 

 

이것을 해결하기 위해 사용하는 것이 IoC컨테이너이다.

5. 스프링컨테이너의 종류(대표컨테이너 - 상위인터페이스)

	BeanFactory			:getBean이 호출되면 객체를 생성하고 작업한다.
		 ^				:getBean이 호출될 때 설정파일에 id로 등록한 객체만 생성된다.
		 |
		 |
	ApplicationContext
		 ^
 		 |
  		 |
	WebApplicationContext

 

* OCP(Open Close Principle - 개방폐쇄의 원칙)

소프트웨어는 확장(기능추가)에는 열려있어야하고 변경(기존에 실행이 잘 되는 코드에는)에는 닫혀있어야 한다.
=> 다형성을 이용해서 인터페이스와 구현을 분리

 

* DIF(Dependency Inversion Principle - 의존관계 역전의 원칙)
- 개발을 할 때는 추상에 의존해야 한다. 즉 상위 인터페이스를 이용해서 개발하도록 작성해야 한다.
- 구현된 클래스(하위클래스)에 의존하면(사용하면) 안 된다 (추상화에 의존해야 하지 하위클래스를 직접 쓰지 말아야 한다).
ex) CustomerDAO dao = new MybatisCustomerDAO(); XX
      CustomerDAO dao; 여기까지만 써야 함

 


 

 

 

- IoC컨테이너를 이용해 코드바꾸기

 

상위 인터페이스(TV)를 상속하는 SamsungTV, LgTV클래스가 있고, main메소드에서 다형성을 이용해 두 클래스를 호출하고 있다.

TV tv = new SamsungTV();
TV lgtv = new LgTV();

===========변경하기===============>

 

 

우선 객체를 제어하기 위한 빈을 생성해야 한다(스프링에서는 객체를 Bean(빈)이라 부른다). 

New - Spring Bean Configuration File - beans(기본 작업은 여기서)

<?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 id="mylg" class="exam01.LgTV"/>
	<bean id="mysamsung" class="exam01.SamsungTV"/>
</beans>

=> 어떤 이름으로 호출한 건지 id를 정의해주고 class에 경로를 적어준다. 그리고 메인으로 돌아가서 IoC컨테이너를 생성하고 빈을 호출해준다.

 

 

public class TVUser {
	public static void main(String[] args) {
		//spring이 제공하는 IoC컨테이너를 통해서 객체를 전달받아 사용하기
		ApplicationContext factory = new ClassPathXmlApplicationContext("/config/exam.xml");
		
		TV tv = (TV)factory.getBean("mysamsung");
//		TV tv = factory.getBean("mysamsung", TV.calss);    <= 캐스팅대신 이렇게 작성해도 됨!
		tv.powerOn();
		tv.soundUp();
		
		TV lgtv = (TV)factory.getBean("mylg");
		lgtv.powerOn();
		lgtv.soundUp();
	}

}

 

 

1) xml설정 파일을 classpath로부터 찾아서 읽은 후 객체를 관리하는 IoC컨테이너를 생성해준다(ApplicationContext의 하위 객체 생성). ClassPathXmlApplicationContext메소드는 xml파싱하고, <bean>엘리먼트로 등록된 모든 클래스들을 객체로 만들어서 컨테이너에 저장한다. 

ApplicationContext factory = new ClassPathXmlApplicationContext("/config/bean.xml");

 

2) 이제 원하는 객체를 찾아오자. 객체의 정보를 담고 있는 factory를 이용해서 getBean("불러올 객체의 빈 id") 메소드를 lookup해준다. 

TV tv = (TV)factory.getBean("mysamsung");
TV lgtv = (TV)factory.getBean("mylg");

다형성을 이용해 상위 인터페이스 타입의 참조변수로 받아준다. 이렇게 하면 이제 new ~~~(); 하며 객체를 생성했던 클래스를 일일이 찾아가며 수정하는 작업을 하지 않아도 된다!!

 

 

** 빈을 생성할 때 scope를 따로 지정해주지 않으면 디폴트는 singleton이기 때문에 getBean() 메소드를 이용할 때마다 만든다고 하더라도 같은 객체는 동일 객체로 간주한다.

~~~~~~~~~~~~~~같은 객체로 간주~~~~~~~~~~~~~~
MyBean obj = (MyBean)factory.getBean("mybeanB");
MyBean obj2 = (MyBean)factory.getBean("mybeanB");

 

만일 다른 객체로 다루고 싶다면 scope를 "prototype"타입으로 지정해주면 된다.

<bean id="mybeanB" class="app3.MyBeanStyleB" scope="prototype"/>

 

여기서 주의할 점은 ApplicationContext의 디폴트타입은 모든 빈을 컨테이너가 만들어지는 시점에 생성하여 메모리에 갖고 있기 때문에 컨테이너가 생성될 때 생성된다. 반대로 "prototype"는 getBean이 호출될 때 객체가 생성된다.

 

 

~~~~~~~~~끝~~~~~~~~~

 

 

 

+++++ 수업시간에 "prototype"만 배웠는데 다른 타입은 어떤 역할을 하는지 궁금해져 검색해 보았다.

(우선 이클립스에 있는 네 가지의 타입만 보도록 하겠다 ..)

 

 

Bean scopes

Scope       | Description
singleton (Default) Scopes a single bean definition to a single object instance per Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
global session Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.

 

- singleton : 기본값으로 Spring IoC 컨테이너당 단일 객체 인스턴스에 대한 단일 bean 정의의 범위를 지정한다.

bean이름이 같으면 동일 객체로 취급한다.

 

- prototype : 단일 bean 정의를 여러 객체 인스턴스로 범위 지정한다.

같은 이름의 bean도 다른 객체로 취급한다(setter, getter메소드를 프로티라고 부른다).

 

- request : 단일 HTTP 요청의 라이프사이클에 대한 단일 빈 정의의 범위를 지정한다. 즉, 각 HTTP 요청에는 단일 bean 정의 뒤에서 생성된 자체 bean 인스턴스가 있는데, 웹 인식 Spring 의 컨텍스트에서만 유효하다. - ApplicationContext

 

- session : 단일 bean 정의를 HTTP의 수명 주기로 범위 지정한다. - Session. 

웹 인식 Spring 의 컨텍스트에서만 유효하다. - ApplicationContext

댓글