IoC  (Inversion of Control 제어의 반전) 컨테이너
- IoC란 인스턴스 (객체)의 생성부터 소멸까지 객체 생명주기(new 연산자, 인터페이스 호출등) 관리를 개발자가 하는게 아닌 컨테이너가 대신 해주는 것을 말한다.
- 즉, IoC가 개발자의 코드를 호출하여 그 코드로 생명주기를 제어(관리)하는 것이다.

기존에 자바 기반으로 애플리케이션을 개발할 때 객체를 생성하고 객체들 사이의 의존관계를 처리하는 것에 대한 책임은 전적으로 개발자에게 있었다. 즉 개발자가 어떤 객체를 생성할지 판단하고 객체 간의 의존관계 역시 소스 코드로 표현해야 했다.
하지만 제어의 반전이라는 것은 이런 일련의 작업들을 소스코드로 처리하지 않고 컨테이너로 처리하는 것을 의미한다. 
따라서 제어의 반전을 이용하면 소스에서 객체 생성과 의존관계에 대한 코드가 사라져 결과적으로 낮은 결합도의 컴포넌트를 구현할 수 있게 한다.
더보기

1. 서블릿 컨테이너

스프링 프레임워크를 이해하는데 가장 중요한 개념이 바로 컨테이너이다.

컨테이너의 개념은 스프링에서 가장 먼저 사용된 것은 아니며 기존의 서블릿에서 이미 사용해왔다.

그리고 대부분 컨테이너는 비슷한 구조와 동작 방법을 가지고 있으므로 서블릿 컨테이너를 통해 스프링 컨테이너의 동작 방식을 유추해 볼 수 있다.

 

다음과 같이 간단한 서블릿 클래스를 만들었다고 가정하자.

HelloServlet.class

public class HelloServlet extends HttpServlet {
	private static final long serialVersionUID = 1L; 
	public HelloServlet() {
		System.out.println("=======> HelloServlet 객체 생성");
	}
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("=======> doGet() 메서드 호출");
	}
}

web.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">

	<display-name>springExample</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>  
	<servlet>
		<servlet-name>hello</servlet-name>
		<servlet-class>com.spring.example.HelloServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>hello</servlet-name>
		<url-pattern>/hello.html</url-pattern>
	</servlet-mapping>
  
</web-app>

 

http://localhost:8080/example/hello.html라는 URL 요청을 전송하면,
hello라는 이름으로 등록된 com.site.example.HelloServlet 클래스를 찾아 객체를 생성하고 실행한다는 설정이다

 

 

서블릿은 자바로 만들어진 클래스이다. 따라서 반드시 객체 생성을 해야 하는데 어느 부분에도 명시되어 있지 않다.

그렇다면 누가 서블릿 객체를 생성했으며, doGet() 메서드를 호출해줬을까?

정답은 서블릿 컨테이너이다. 

 

다음은 서블릿 컨테이너가 Servlet 클래스 객체를 생성하고 운용하는 과정을 표현한것이다.

서블릿 컨테이너는 다음 순서에 따라 동작한다.

① /WEB-INF/web.xml 파일을 로딩하여 구동

② 브라우저로부터 http://localhost:8080/example/hello.html 요청 수신

③ com.spring.example.HelloServlet 클래스를 찾아 객체를 생성하고 doGet() 메서드 호출

④ doGet() 메서드 실행 결과를 클라이언트 브라우저로 전송

 

이렇듯 컨테이너는 자신이 관리할 클래스들이 등록된 XML 설정파일을 로딩하여 구동한다.

그리고 클라이언트의 요청이 들어오는 순간 XML 설정 파일을 참조하여 객체를 생성하고, 객체의 생명주기를 관리한다.

스프링 컨테이너 역시 서블릿 컨테이너와 유사하게 동작한다.


 

 

🌳 결합도(Coupling)

결합도란 하나의 클래스가 다른 클래스와 얼마나 많이 연결되어 있는지를 나타내는 표현이다.

결합도가 높은 프로그램은 유지보수가 어렵다.

 

🍎  결합도 낮추기


→  LgTV 클래스에도 SamsungTV 클래스와 같은 기능을 수행하는 메서드가 있지만, 둘의 메소드 이름과 다르다.

→  TVUser 프로그램을 구현했지만 SamsungTV와 LgTV는 시그니처(signature)가 다르므로 TVUser 코드 대부분을 수정해야 TV를 교체할 수 있다. 결합도가 높아 유지보수가 힘든 상황.


🍎  (1)  결합도를 낮추기 - 다형성을 이용

더보기
public interface TV {
    public void powerOn();
    public void powerOff();
    public void volumeUp();
    public void volumeDown();
}

 TV 클래스들을 구현하여 사용하기 위해 TV 인터페이스를 추가하고,

모든 TV가 공통으로 가져야 할 메서드들을 추상 메서드로 선언한다.

 

 TV 인터페이스에 선언된 추상 메서드들을 모두 재정의하여,

인터페이스를 이용하여 모든 TV 클래스가 같은 메서드들을 가질 수밖에 없도록 강제할 수 있게 되었다.

 

public class TVUser {
    public static void main(String[] args) {
        TV tv = new SamsungTV();
        tv.powerOn();
        tv.volumeUp();
        tv.volumeDown();
        tv.powerOff();
    } }

③ 결합도가 낮아지고 객체를 쉽게 교체할 수 있다 (유지보수 용이)


 

🍎  (2)  결합도를 낮추기 - 디자인 패턴을 이용

더보기
public class BeanFactory {
    public Object getBean(String beanName){
        if(beanName.equals("samsung")){
            return new SamsungTV();
        } else if(beanName.equals("lg")){
            return new LgTV();
        }
        return null;
    }}

 TV를 교체할 때, 클라이언트 소스를 수정하지 않고 TV를 교체할 수만 있다면 유지보수는 더욱 편리해질 것이다.

이를 위해서 Factory 패턴을 적용 (Factory 패턴은 클라이언트에서 사용할 객체 생성을 캡슐화하여 TVUser와 TV 사이를 느슨한 결합 상태로 만들어준다.)

BeanFactory 클래스의 getBean() 메서드는 매개변수로 받은 beanName에 해당하는 객체를 생성하여 리턴한다.

 

public class TVUser {
    public static void main(String[] args) {
        BeanFactory factory = new BeanFactory();
        TV tv = (TV)factory.getBean(args[0]);
        tv.powerOn();
        tv.volumeUp();
        tv.volumeDown();
        tv.powerOff();
    }}

  이제 이 BeanFactory 클래스를 이용하여 사용할 TV 객체를 획득하도록 TVUser 클래스를 수정했다.


 

 

🍎  (3) 결합도를 낮추기 - 스프링 IoC(Inversion of Control) 시작하기

../src/main/resources/applicationContext.xml 파일을 생성

 

 이때 기본으로 <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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">
     // 클래스 1개당 1개의 <bean>을 사용한다.
     // class 속성값은 패키지 경로가 포함된 전체 클래스 명으로 지정해야 한다.
     <bean id="tv" class="com.spring.polymorphism.SamsungTV" />	
</beans>

 

 구동된 컨테이너로부터 SamsungTV 객체를 얻어내 보자.

(SamsungTV 객체가 언제 생성되는지 확인하기 위해서 기본 생성자를 추가

public class SamsungTV implements TV {
     public SamsungTV() {
          System.out.println("===> SamsungTV(1) 객체 생성"); }
     ...          
}

 

 TVUser를 수정한다. (이름이 tv인 객체를 getBean() 메서드를 이용하여 요청하도록 )

public class TVUser {
    public static void main(String[] args) {   
    
        // 1. Spring 컨테이너를 구동한다.
        AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");       
       
        // 2. Spring 컨테이너로부터 필요한 객체를 요청(Lookup)한다.
        TV tv = (TV)factory.getBean("tv");
        tv.powerOn();
        tv.volumeUp();
        tv.volumeDown();
        tv.powerOff();        
       
        // 3. Spring 컨테이너를 종료한다.
        factory.close();
    }}
applicationContext.xml   ( TVUser 소스를 수정할 필요가 없이 applicationContext.xml 파일만 수정하면 된다 )
<bean id="tv" class="com.spring.polymorphism.SamsungTV" />
<bean id="tv" class="com.spring.polymorphism.LgTV" />
TVUser.java 실행창(console)
===> SamsungTV(1) 객체 생성
SamsungTV---전원을 켠다.
SamsungTV---소리를 올린다
SamsungTV---소리를 내린다.
SamsungTV---전원을 끈다.
===> LgTV(1) 객체 생성
LgTV---전원 켠다.
LgTV---소리를 올린다
LgTV---소리를 내린다.
LgTV---전원 끈다.

① TVUser 클라이언트가 스프링 설정 파일을 로딩하여 컨테이너 구동

② 스프링 설정 파일에 <bean> 등록된 SamsungTV or LgTV 객체 생성

③ getBean() 메서드로 이름이 ‘tv’인 객체를 요청

④ SamsungTV 객체 반환.

'Language > Java' 카테고리의 다른 글

객체 지향  (0) 2022.11.04
필드 - 제어자  (0) 2022.10.12
Spring  (0) 2022.09.11
스레드  (0) 2022.03.10
입출력 (Stream)  (0) 2022.03.03