SpringFramework/BASIC

DI 의존성 주입 - 생성자

유혁스쿨 2020. 8. 29. 00:08
728x90
반응형

 

[DI와 IOC]

JAVA 의 Class 상속 / Interface 를 이용한 추상화를 기반으로 하는 개발 방법

Spring은 아래 DI/IoC 를 강력하게 지원하는 프레임워크

 

쉽게말해서 Ioc는 제어의 역전입니다.

기존에 우리가 필요할 때 마다 마트에가서 장을봐오는게 아니라 필요한게 있다고 미리 얘기해두면 따로 장을봐오는 사람이 있으며 장봐온것을 계속 뽑아서 쓰는것 이것을 바로 제어의 역전이라고 할수 있습니다.

 

DI는 IoC패턴에 구현방법중 하나입니다.

주입이라는 것은 미리 준비된 객체를 넣어주는것 입니다.

스프링컨테이너에 미리 등록해놓은 bean들 하나하나의 객체들은 보통 의존적인객체를 등록합니다.

의존적인객체 즉, A객체가 B객체 없이 동작이 불가능한 상황을 의존성이있다, 의존관계이다, 두 클래스가 의존관계를 가지고있다 라고 말할 수 있습니다.

스프링의 많은 모듈, 라이브러리들은 DI를 기반으로 만들어져 있습니다.

기본적으로 다 등록해 두고 꺼내쓴다 라는것을 잊지 말아야합니다.

 

DI

(Dependency Injection)

스프링 컨테이너에 만들어 둔 각종 클래스(Bean)들은 서로 의존적이다

A객체가 B객체없이 동작이 불가능한 상황이다

 

DI는 IOC패턴의 구현 방법중 하나이다.

 

주입이라는것은 넣어주는것이다 미리 준비된객체를 주입해준다. 

IoC

(Inversion

Of

Control)

프로그램을 제어하는 패턴 중 하나

DI 는 IoC패턴의 구현방법 중 하나

DI에 따라 프로그램의 흐름이 완전히 변경될 수 있다.

객체를 미리 정의해두고 필요할때마다 꺼내서 주입해준다

A객체가 B객체 없이 동작이 불가능 한 상황이다.

의존성이 있다, 의존관계이다,

두 클래스가 의존관계를 가지고 있다.

스프링은 DI를 기준으로 많은 프레임워크모듈 들이 만들어짐

Spring 은 DI Framework 혹은 IoC Framework 라고 부른다.


 

[의존적 관계]

 

스프링이라는 자체가 컨테이너 박스 안에 객체들을 미리 저장시켜 두고 필요할 때 꺼내서 씁니다.← 제어의 역전

그 객체들간에 의존관계들이 있다면 설정안에서 bean을 설정할때 함께 의존관계를 미리 등록을 해놓습니다.

의존성 관계를 표현 할 때에는 클래스간에 의존관계가 있다. 라고 하고있고 해당 클래스는 의존된 클래스가 없으면 실행되지 않습니다 - 의존성구조

생성자,setter() 등으로 의존 객체를 주입시켜주면 정상적으로 작동하게 됩니다.

스프링 객체 관리 법 : 컨테이너 내부에 설정 후 사용합니다. xml에 bean으로 설정한 후 컨테이너에 해당 설정이 등록됩니다.

 

bean을 등록할 때 내부에 내부태그, 소속된 종속태그로 constructure-arg라는 태그를 사용하여 ref속성에 내가 주입할 bean아이디값을 넣어주면 컨테이너에 등록할 때 주입한 상태로 등록을 하게된다.

 

장난감으로 예를 들어 보겠습니다.

일체형 장난감자동차 와 배터리 분리형 장난감 자동차가 있습니다

 

일체형 장난감 자동차

예를들어 CarToy라는 클래스가있고 이 안에 장난감에 대한 Field정보가 있습니다.

장난감의 일련번호, 이름, 가격 같은 정보들이 클래스의 구성요소로 들어가있습니다.

장난감의 기능이라하면 메서드로 run, break, remotecontroller 등의 기능들이 들어가있습니다.

배터리 일체형 이라는 것은 배터리도 하나의 객체입니다

배터리회사명, 수명, 사이즈 등의 정보들이 따로 존재합니다.

자동차가 만들어진 시점에 배터리 객체가 이미 들어와 있어야 합니다.

장난감 자동차와 배터리는 의존관계입니다. 장난감 자동차는 배터리가 없으면 동작을 할 수 없습니다.

 

배터리 분리형 장난감 자동차

장난감 과 배터리는 기본적으로 의존관계에 있으며 분리형일때에는 원할때마다 갈아 끼울 수 있습니다.

 

배터리가 일체형일때는 배터리가 다 닳았을 때 버리고 새로만들어야 합니다 (현실적으로는 충전해서 사용)

배터리 분리형일경우에는 배터리만 새로운 배터리로 갈아끼우면 됩니다.

이 두가지 방식의 의존관계형태를 코드로 작성해 보겠습니다.

 

public class CarToy {
	
	private Battery battery;

	public CarToy(){
		this.battery = new Battery(); 
	}	
}

 

위의 코드에서 CarToy클래스의 생성자를 봅시다.

생성자는 객체를 생성해주는 역할을 해줍니다.

생성할 때 battery타입의 객체를 넣어두고 있습니다.

생성할때에만 battery객체를 만듭니다. 즉, 자동차를 처음만드는 그 시점에만 배터리객체가 저장이됩니다.

이 코드안에서 배터리객체가 다 소모되었을 때 바꿔낄 방법이 없습니다

새로운 battery를 주입할 set() 메서드가 없으며 객체는 현재 private입니다.

접근이 불가능 합니다. 자동차를 버려야합니다 ㅋㅋㅋ

 

두번째 코드를 봅시다

 

public class CarToy {
	
	private Battery battery;
	public CarToy(){
		this.battery = new Battery(); 
	}
	
	public void setBattery(Battery battery) {
//		battery = new Battery();
		this.battery = battery;
	}
	
	public Battery getBattery() {
		return battery;
	}
	
}

 

무엇을 통해서 저장하고있습니까? 생성자를 통해서 저장하고 있습니다.

똑같이 CarToy가 생성되는 시점에서 배터리를 넣어줍니다.

배터리가 다 떨어지면 setBattery() 를통해서 새로운 배터리를 다시 넣어줄 수 있게 됩니다.

Battery객체를 생성자를통한 주입, Set메서드를 통한 주입 두가지의 주입방법을 사용하고있습니다

 

이런 형태가 객체 주입에대한 코드입니다. 생성자, set()

 

그렇다면 의존성 주입이란 무엇일까요

의존성 주입이라는 것은 결국 객체를 넣어주는데 의존관계가 있는 객체를 넣어준다는 개념이다.

장난감은 배터리가 없으면 안됩니다. 배터리가 반드시 필요합니다. 어떻게든 배터리를 주입할 수 있는 방법을 만들어야 하고 주입을 꼭 해야한다는 것입니다.

의존적 관계에서 객체들을 넣어주는 행태들을 의존성 주입이라고 합니다.

 

스프링을 사용하지 않고 원칙적으로 Low한 자바코드를 통해서 주입하는 예시를 들어보겠습니다.

 

Hotel,Restaurant,Chef 각각 세가지 클래스를 만들어 봅니다.

 

클래스 : Hotel , Restaurant , Chef

 

public class Hotel {
	private Restaurant res;
}

호텔에는 레스토랑이 있어야합니다. 레스토랑의 객체 res를 선언하겠습니다.

public class Restaurant {
	private Chef chef;
}

레스토랑은 세프가 있어야합니다. 필드멤버에 Chef 클래스의 객체인 chef를 선언하겠습니다.

public class Chef {
	public Chef() {
		System.out.println("요리사가 생성됨");
	}
	public void cook() {//세프의 기능인 요리
		System.out.println("요리사가 요리를 합니다.");
	}
}

 

chef는 요리를할수있는 기능 cook()을 선언하겠습니다.

 

 

 

다시 레스토랑으로 돌아와 기능을 추가해보겠습니다.

 

public class Restaurant {
	private Chef chef;
    public void orderDinner() {
		System.out.println("저녁식사를 주문합니다.");
		chef.cook();
	}
}

 

이곳에서는 저녁식사를 주문할 수 있는 기능을 선언했습니다.

저녁식사를 주문하면 세프가 요리를 하도록 추가 기능을 함께 구현합니다.

 

 

호텔에도 기능을 만들어 보겠습니다

 

public class Hotel {
	private Restaurant res;
	public void reserveRestaurant() { 
		System.out.println("레스토랑을 예약합니다");
		res.orderDinner();
	}
}

 

호텔에는 레스토랑을 예약할 수 있는 기능이 있습니다.

레스토랑을 예약하면 저녁식사를 주문받는 추가 기능을 함께 구현합니다

 

 

# Hotel과 Restaurant의 관계

Hotel은 Restaurant없이 운영이 될 수 없습니다

호텔에 레스토랑인 res객체가 없다면 reserveRestaurant()기능을 구현할 수 없습니다.

reserveRestaurant()메서드 내부에서 res객체를 참조하고 있기 때문입니다.

Hotel에 res객체가 없다면 res.orderDinner()메서드에서 NullPointerException에러가 발생할 것입니다.

따라서 Hotel은 Restaurant에 의존적이다. Dependency를 가지고있습니다.

즉, res는 Hotel이 의존하는 의존적 객체 입니다

 

# Restaurant과 Chef의 관계

마찬가지로 Restaurant는 chef없이 운영이 될 수 없습니다.

레스토랑에 세프인 chef객체가 없다면 orderDinner()메서드가 마비가됩니다.

orderDinner()메서드 내부에서 chef객체를 참조하고 있기 때문입니다.

Restaurant에 chef객체가 없다면 마찬가지로 chef.cook()메서드에서 NullPointerException에러가 발생합니다.

따라서 Restaurant은 Chef에 의존적이며 Dependency를 가지고 있습니다.

 

결국에는 Hotel은 Restaurant가 없으면 안되고 Restaurantd은 Chef가 없으면 안됩니다 이것들은 서로 의존관계에 있기 때문입니다.

 

그렇다면 단순히 호텔만 세우면 해결이되느냐,? 호텔을 설립하고 예약을 받아보겠습니다.

 

public class MainClass {

	public static void main(String[] args) {
		Hotel hotel = new Hotel(); 
		hotel.reserveRestaurant(); 
	}
}

 

 

메인클래스에서 실행 후 오류

 

의존성을 가지고있는 호텔이 운영이 되지 않는 전형적인 예시입니다.

어떠한 설정도 하지 않았기 때문에 오류가 발생합니다.

정상적으로 작동하기 위해서는 주입에관련된 코드를 반드시 기입해야합니다.

호텔을 설립할 때 부터 내부적으로는 레스토랑을 같이 지어줘야하고

레스토랑이 지어질 때 부터 세프를 바로 고용해야합니다

 

주입에서의 첫번째 방법으로는 생성자를 통한 주입을 실습을 통해 확인해 보겠습니다.

 

public class Hotel {
	private Restaurant res;

//생성자 - 호텔이 설립되면서 레스토랑을 주입한다.
	public Hotel(Restaurant res) {
		System.out.println("호텔이 생성됨!");
		this.res=res;
	}

	public void reserveRestaurant() { 
		System.out.println("레스토랑을 예약합니다");
		res.orderDinner();
	}
}

호텔이 생성 되자마자 생성자를 통해 레스토랑을 함께 지어 주입해줍니다

public class Restaurant {
	private Chef chef;

	public Restaurant(Chef chef) {//레스토랑을 만들 때 세프를 주입
		System.out.println("레스토랑이 생성됨!");
		this.chef=chef;
	}

    public void orderDinner() {
		System.out.println("저녁식사를 주문합니다.");
		chef.cook();
	}
}

 

레스토랑이 만들어 지자 마자 원하는 세프 한명을 고용할 수 있도록 생성자를 만들어둡니다.

 

다시 메인클레스로 갑니다.

호텔 건설과 동시에 레스토랑을 생성하고 레스토랑이 생성됨과 동시에 Chef를 고용하는 코드를 구현합니다.

단순히 필요할때 생성하여 주입하는 Plain한 자바코드로 객체를 생성해 주입해 보도록 하겠습니다

public class MainClass {

	public static void main(String[] args) {
		Hotel hotel = new Hotel(new Restaurant(new Chef())); 
		hotel.reserveRestaurant(); 
	}
}

 

한줄로 쓴 코드를 가독성 좋게 풀어서 다시 구현해봤습니다.

public class MainClass {

	public static void main(String[] args) {
        Chef chef = new Chef(); 
		Restaurant res = new Restaurant(chef);
		Hotel hotel = new Hotel(res);
		hotel.reserveRestaurant(); 
	}
}

 

 

 

의존성객체를 주입 후 정상적으로 작동함

 

여기서 우리가 캐치해야 할 것은 의존성 관계가 무엇인가, 그리고 그 의존성 관계에서 문제를 일으키지 않기 위해서는 객체를 주입하기 위한 코드가 들어있어야 된다는 것이다. (생성자)

 

하지만 이것은 수동주입이다 스프링에서 말하는 DI는 제어의역전, IoC 입니다.

미리 집사(Container)한테 말해놓으면 안되? 야, 호텔 지을때는 레스토랑이 꼭 있어야한다? 레스토랑만들때는 세프가 꼭 있어야한다? 미리 말 해놓는 것입니다.

 

test-config.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">


	<!--Hotel객체와 의존객체들의 빈 등록 및 의존성 주입 설정 -->
	<bean id="chef" class="com.spring.basic.ex01.Chef"/>
	<bean id="res" class="com.spring.basic.ex01.Restaurant">
       <constructor-arg ref="chef"/>   
	</bean>
	<bean id="hotel" class="com.spring.basic.ex01.Hotel">
		<constructor-arg ref="res" />
	</bean>
</beans>

 

호텔과 레스토랑, 세프 총 세개의 클래스를 bean객체로 등록해 놓는다

그리고 해당 bean객체 태그 사이에 <constructor-arg>태그를 사용하여 생성자를통해 객체를 주입시켜준다

 

# constructor-arg : 생성자가 필요한 매개값

# ref : reference객체를 뜻하며 bean id를 가리킴

→ bean id가 chef인 Chef 클래스의 객체

→ bean id가 res인 Restaurant 클래스의 객체

 

constructor-arg는 생성자가 필요한 매개값 즉, 생성자의 기능 / ref는 레퍼런스 즉 참조할이름 bean id를 참조하게됩니다.

 

constructor-arg라는 태그를 사용하여 생성자를통해 chef를 res에 주입

bean id가 chef인 레퍼런스 chef를 consutructor-arg 태그를 사용하여 생성자를 통해 res에 주입

Hotel에는 Restaurant를, Restaurant는 Chef를 각각 주입설정을 해줍니다.

 

그렇다면 이 때에 왜 생성자를 통해 주입할까요?

클래스 설계할때 객체를 생성자를 통해 받아서 넣기로 했습니다.

우리가 setter를 만들지는 않았습니다.

 

또 다시 메인클래스로 돌아갑니다.

우리는 방금 xml을 통해 설정을통해 스프링한테 미리 선언해놨습니다.

객체를 직접 생성하여 주입하는것이 아닌 스프링을통한 객체생성 및 주입할수 있는 코드를 구현해보겠습니다.

 

public class MainClass {

	public static void main(String[] args) {
        GenericXmlApplicationContext ct = new GenericXmlApplicationContext("classpath:test-config.xml");		
		Hotel hotel = ct.getBean("hotel",Hotel.class);
		hotel.reserveRestaurant();
	}
}

 

test-config.xml 설정파일을 정보를 갖고있는 객체가 ct에 저장되고 저장된 ct로부터 getBean()으로 컨테이너로부터 꺼내옵니다.

"hotel"이라는 아이디값을 가진 bean을 Hotel.class형태로 꺼내옵니다.

실행 해 보겠습니다

 

 

 

 

우리가 지금 호텔객체에 레스토랑객체를 직접 넣어주지 않았습니다.

레스토랑객체또한 직접 셰프객체를 넣어준적이 없습니다.

요리사가 생성되고 레스토랑이 생성되고 호텔이 생성되면서 일련의 행동들이 처리가 됩니다.

앞서 구현한 객체를 직접 주입해주는 객체생성 코드가 없는데 정상적으로 동작을 합니다.

이것이 바로 스프링 Bean 의존성 주입입니다.

설정을 통한 객체를 주입, 자바단 그 어디서도 호텔에 레스토랑을, 레스토랑에 세프를 넣어준 적이 없는데 정상적으로 구동되고있습니다.

NullPointerException 에러가 뜨지 않고 정상적으로 모든 메서드가 다 수행이 되었습니다.

 

이것이 바로 스프링 DI입니다.

나중에는 심지어 이런 컨테이너 코드조차 작성하지 않을것입니다.

이것이 바로 스프링의 강점인 객체에 대한 관리, 라이프사이클에 대한 관리를 스프링이 대신해주는것입니다.

만들고, 소멸시키고. 싱글톤으로처리하고 이런것들을 스프링이 알아서 해줍니다.

 

다음 시간에는 같은 코드를 Setter()메서드를 통한 의존성주입에 대해 정리해보도록 하겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형