[자바와 객체 지향 그리고 스프링] 07. IoC/DI

코린이서현이·2024년 1월 23일
0

😪들어가면서😪

더 열심히 해!! 

IoC / DI

📌 프로그래밍에서의 의존성이란?

  • 의존한다는 것은 어떤 객체에서 의존 객체를 알고 있다는 것이다.
  • 전체가 부분에 의존한다.
class A { 
	B b = new b();
}
  • A 클래스가 B 클래스에 의존하고있다.
    클래스 A는 B라는 클래스의 new키워드를 통해 인스턴스를 생성하고 있습니다.

  • B 타입 변수 b는 B 클래스의 인스턴스를 가리키고 있습니다. 즉, 의존 객체는 A 클래스 내부의 b 변수가 참조하는 객체인 new B()입니다.

  • 전체는 A, 부분은 b로 A는 b에 의존한다.

즉, A클래스는 의존 객체인 new B()를 알고있고 의존한다.

이때, DIP를 만족하려면 구체적인 것이 추상화된 것에 의존해야한다.
따라서 B가 A보다 덜 변경되어야하고, 클래스가 아니라 추상화된 것이어야한다.

주입없이 코드 짜기

전략 객체 : 인터페이스와 실제 구현 객체

package expert001_01;

public interface Tire {
  String getBrand();
}
package expert001_01;

public class KoreaTire implements Tire{
  @Override
  public String getBrand() {
    return "코리안 타이어";
  }
}
package expert001_01;

public class AmericaTire implements Tire{
  @Override
  public String getBrand() {
    return "미국 타이어";
  }
}

컨텍스트 : 전략 객체를 사용하는 부분

전략객체생성을 컨텍스트의 생성자내에서 실제 구현 객체에 new를 사용해 의존하고 있음

package expert001_01;

public class Car {
  Tire tire;

  public Car(){
    tire = new KoreaTire();		# 의존관계
//    tire = new AmericaTire();
  }

  public String getTierBrand(){
    return "장착된 타이어:" + tire.getBrand();
  }
}

클라이이언트

package expert001_01;

public class Driver {
  public static void main(String[] args) {
    Car car = new Car();
    System.out.println(car.getTierBrand());	#장착된 타이어:코리안 타이어
  }
}

실행화면

장착된 타이어:코리안 타이어

테스트 코드

테스트를 하는 방법은 따로 포스팅 했다. 보러가기

package expert001_01;

import org.junit.jupiter.api.Test;

import static org.junit.Assert.*;

class CarTest {
  @Test
  public void 자동차_장착_타이어브랜드_테스트(){
    Car car = new Car();

    assertEquals("장착된 타이어:코리안 타이어",car.getTierBrand());

  }

}

다이어그램 확인하기

  • DriverCar에 의존한다.
  • Car이 실제 구현 객체 KoreaTier에 의존한다.

Car이 자주 변경될 수 있는 구현 객체 부분, 즉 구체적인 것에 의존하고 있어서 DIP를 따르지 않는다.
또한 전략객체를 변경할 떄마다 Car의 내부코드를 변경해야해 OCP를 위반한다.

의존성을 주입하는 여러방법

🤔 주입이란?

주입은 외부에서라는 뜻을 내포하고 있는 단어이다.
즉, 외부에서 객체를 장착하는 작업을 말한다.

스프링 없이 의존성 주입하기 1. 생성자를 통한 의존성 주입

  • 전략 객체를 클라이언트가 생성자를 통해서 주입한다.
  • 컨텍스트는 생성자의 매개변수를 가지고 전략객체를 주입받는다.

컨텍스트 코드

package expert001_02;

public class Car {

  Tire tire;

  Car(Tire tire){
    this.tire = tire;     //셍성자를 통해서 전략객체를 주입받는다.
  }
  public String getTierBrand(){
    return "장착된 타이어:" + tire.getBrand();
  }
}

클라이언트 코드

package expert001_02;

public class Drive {
  public static void main(String[] args) {
    Tire koreaTire = new KoreaTire();
    Car koreaCar = new Car(koreaTire);

    System.out.println(koreaCar.getTierBrand());

    Tire americaTier = new AmericaTire();
    Car americaCar = new Car(americaTier);

    System.out.println(americaCar.getTierBrand());


  }
}

실행화면

장착된 타이어:코리안 타이어
장착된 타이어:미국 타이어

테스트 하기

package expert001_02;

import org.junit.jupiter.api.Test;
import static org.junit.Assert.*;

class CarTest {
  @Test
  public void 생성자주입_코리아타이어_장착_자동차_테스트() {
    Tire tire = new KoreaTire();

    Car car = new Car(tire);

    assertEquals("장착된 타이어:코리안 타이어",car.getTierBrand());

  }
  @Test
  public void 생성자주입_미국타이어_장착_자동차_테스트() {
    Tire tire = new AmericaTire();

    Car car = new Car(tire);

    assertEquals("장착된 타이어:미국 타이어",car.getTierBrand());

  }

}

스프링 없이 의존성 주입하기 2. 속성을 통한 의존성 주입

Getter을 이용해서 객체를 주입해보자.

의사코드

운전자가 자동차를 생산한다.
운전자가 타이어를 생산한다.
운전자가 자동차에 타이어를 장착한다.

속성으로 받는 컨텍스트

package expert001_03;


public class Car {

  Tier tier;

  public Tier getTire() {
    return tier;
  }

  public void setTire(Tier tier) {
    this.tier = tier;
  }

  public String getTierBrand(){
    return "장착된 타이어:" + tier.getBrand();
  }
}

클라이언트

package expert001_03;

public class Drive {
  public static void main(String[] args) {
    System.out.println("=============");
    Tier koreaTier = new KoreaTier();
    Car koreaCar = new Car();
    koreaCar.setTire(koreaTier);


    System.out.println(koreaCar.getTierBrand());

    Tier americaTier = new AmericaTier();
    Car americaCar = new Car();
    americaCar.setTire(americaTier);

    System.out.println(americaCar.getTierBrand());


  }
}

실행화면

=============
장착된 타이어:코리안 타이어
장착된 타이어:미국 타이어

🤔 생성자 vs 속성

속성 주입은 중간에 변경이 가능하고, 생성자는 변경할 수 없다.

💡 의존성 주입을 사용해야하는 이유

1. 컨텍스트는 어떤 구현 객체인지 몰라도 된다.

컨텍스트는 추상화된 인터페이스만 알면 된다.
구현 객체가 인터페이스만 잘 구현했다면 정상적으로 작동한다.

2. 확장성이 좋아진다.

어떤 새로운 구현 객체를 만들더라도, 인터페이스만 따른다면, 컨텍스트의 코드를 변경하지 않고 사용할 수 있다. - IOP

3. 새로운 구현 객체만 컴파일하면 된다.

새롭게 작성한 코드만 재컴파일하고, 다른 코드는 변경하지 않기 때문에 재컴파일하지 않아도 된다.
즉, 재배포가 간단해진다.

스프링 사용해서 의존성 주입

두구두구 드디어 스프링..?! 대단

스프링을 통한 의존성 주입 - XML 파일

의사코드

운전자가 종합 쇼핑몰에서 타이어를 구매한다.
운전자가 종합 쇼핑몰에서 자동차를 구매한다.
운전자가 자동차에 타이어를 장착한다.

🤔 뭐가 달라졌을까...??
운전자가 직접 생산하는 것이 아닌 종합 쇼핑몰에서 구매하는 형식으로 바뀜

Driver의 코드 변경

종합쇼핑몰을 이용하기 위해서, 즉 스프링 프레임 워크의 정보를 가져와야한다.

package expert002;


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

public class Driver {
  public static void main(String[] args) {
    System.out.println("스프링 의존성 주입 - xml파일을 이용");

    //왜 컨텍스트일까?
    /* # 컨텍스트
        전략 객체를 클라이언트로 주입받아 전략객체의 전략 메서드를 사용하는 컨택스트
        전략 객체의 사용자, 소비자이다.
         컨텍스트는 어떤 전략 객체가 들어올지 모른다.
     */
    ApplicationContext context = new ClassPathXmlApplicationContext("expert002.xml");


    //스프링 컨테이너에 등록된 빈을 사용
    Car car = context.getBean("car", Car.class);
    Tire tire = context.getBean("tire", Tire.class);

    car.setTire(tire);

    System.out.println(car.getTireBrand()); //장착된 타이어:코리안 타이어
  }
}

xml 파일 (resources)

<?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="tire" class="expert002.KoreaTire"></bean>
    <bean id="americaTire" class="expert002.AmericaTire"></bean>
    <bean id="car" class="expert002.Car"></bean>
</beans>

⚒️ ApplicationContext란?
스프링 컨테이너 : 자바 객체,빈을 등록하고 생명주기를 관리한다.
스프링 컨테이너의 종류 : BeanFactory , ApplicationContext
일반적으로 ApplicationContext를 스프링 컨테이너라 한다.

1. xml파일만 변경해도, 프로그램의 실행 결과를 바꿀 수 있다.

자바코드에는 KoreaTire인지, AmericaTire인지 지칭하지 않는다. 즉 의존하지 않는다.

🤔 만약 AmericaTire로 변경하고 싶으면, xml파일의 id만 변경하면 된다.

스프링을 통한 의존성 주입 - 스프링 설정 파일(XML)에서 속성주입

💡 이번에는 속성주입까지 스프링 설정 파일을 통해서 사용하자!

의사코드

운전자가 종합 쇼핑몰에서 자동차를 구매요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합쇼핑몰은 자동차에 타이어를 장착한다.
종합쇼핑몰은 운전자에게 자동차를 전달한다.

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 id="koreaTire" class="expert003.KoreaTire"/>
    <bean id="americaTire" class="expert003.AmericaTire"/>
    <bean id="car" class="expert003.Car">
        <property name="tire" ref="koreaTire"/>
    </bean>
</beans>

💡 property 태그를 사용해 속성을 주입한다.

    <bean id="car" class="expert003.Car">
        <property name="tire" ref="koreaTire"/>
    </bean>

Driver

package expert003;

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

public class Driver {
  public static void main(String[] args) {
    System.out.println("스프링 의존성 주입 - 스프링 설정 파일에서 속성을 주입");
    
    ApplicationContext context = new ClassPathXmlApplicationContext("expert003/expert003.xml");

    //속성까지 모두 주입한 객체를 전달 받는다.
    Car car = context.getBean("car", Car.class);
    System.out.println(car.getTireBrand()); //장착된 타이어:코리안 타이어
  }
}

테스트 코드 돌리러가기

스프링을 통한 의존성 주입 - @Autowired를 통한 속성주입

xml파일쓰기 귀찮네...? 속성도 자동 주입되면 편할텐데?

의사코드

운전자가 종합 쇼핑몰에서 자동차를 구매요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합쇼핑몰은 자동차에 타이어를 장착한다.
종합쇼핑몰은 운전자에게 자동차를 전달한다.

🤔 @Autowired는?

의존관계를 자동으로 주입해준다.

객체를 주입받을 Car에서@Autowired

package expert004;

import org.springframework.beans.factory.annotation.Autowired;

public class Car {

  @Autowired
  Tire tire;

  public String getTireBrand(){
    return "장착된 타이어:" + tire.getBrand();
  }
}

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"
       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-3.1.xsd">
    <context:annotation-config />

    <bean id="tire" class="expert004.KoreaTire"></bean>
    <bean id="tire2" class="expert004.AmericaTire"></bean>
    <bean id="car" class="expert004.Car"></bean>
</beans>

실행화면

스프링 의존성 주입 - @Autowired를 통한 속성주입
장착된 타이어:미국 타이어

더 알아보기 : 속성에 쓰일 빈이 유일하고, id가 명확하지 않을 때

xml파일 수정

    <bean class="expert004.AmericaTire"></bean>
    <bean id="car" class="expert004.Car"></bean>
</beans>

실행결과

장착된 타이어:미국 타이어

👉 해당 type을 구현한 빈이 있고, 빈이 하나만 있을 때는 그 빈을 자동으로 할당해준다.
그러나 명확하게 id를 써주는 것이 좋다!
추가로, 빈을 등록할때는 id보다는 type을 먼저 검사한다.

스프링을 통한 의존성 주입 - @Resource를 통한 속성주입

@Resource란?

  • @Autowired와 동일하게 작동한다. 그러나 @Resource는 자바의 표준 어노테이션이다.
  • @Resource는 id와 type중에서 id가 더 우선순위가 높다.

필자가 추천하는 방식

xml파일을 쓰고, 사용고려 객체를 모두 빈으로 등록하고, id값을 바꿔가면서 사용한다.
(그런데 아마도 요즘은 yml파일을 더 쓸수도...?)

profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글