DI, 의존성 주입

706__·2021년 2월 25일
1

Spring

목록 보기
3/3
post-thumbnail

Dependency Injection

DI 의존성 주입하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다.

의존성은 서비스로 사용할 수 있는 객체이다. Client가 어떤 서비스를 사용할 것인지 지정하는 대신, Client에게 무슨. 서비스를 제공할 것인지 말해주는 것이다.

주입은 의존성 Service 를 사용하려는 객체 Client로 전달하는 것이다.

정리하면, Client가 서비스를 구축하거나 찾는 것을 허용하는 대신, Client에게 서비스를 전달하는 것이 패턴의 기본 요건이다.

위의 예시를 이해하였다면, 이전에 정리했던 IoC 의존성 역전과 매우 비슷한 개념이라고 느껴질 것이다.

IoC 의존성 역전 : 실행에 필요한 객체의 생성 / 사용 등 제어 권한을 Framework에게 위임함으로써, Client가 신경써야할 부분을 줄이는 전략

DI 의존성 주입 : 객체가 필요로 하는 어떠한 객체를 주입해줌으로써, 하나의 객체가 다른 객체의 의존성을 제공하는 전략

의존성을 주입하는 방식은 다음과 같다.

  • xml 설정 파일
  • Class 내 Setter / Constructor
  • Annotation
    • @Autowired : Spring아, 이 Type에 맞는 것을 찾아서 이 친구와 그 친구를 연결시켜줘!
    • @Component : Spring아, 이건 네가 관리해야 하는 Bean이야!

다시 말해서, 의존성 주입은 객체가 필요로 하는 어떤 객체를 Constructor / Setter 를 통해서 외부에서 주입함으로써, 의존성을 제공하는 것이다.

코드 상에서 의존 관계는 new 키워드를 통해서 생성된다.

A = new B();

위와 같은 경우, 객체 생성자 AB 를 의존한다.

코드를 작성하는 과정에서, 이러한 강결합 Tightly Coupled 을 일으키는 요소를 무분별하게 사용하게 되면 코드의 유연성 / 가독성이 떨어질 뿐만 아니라, 나중에 유지 보수에 큰 어려움을 겪게 된다.

A 객체에서 B 를 생성하는 것이 아니라 외부에서 생성된 BA 에 주입함으로써 의존관계를 없앨 수 있다.

class Potion implements Item {

    public void useItem() {
    	System.out.println("Use Potion");
    }
    
}

class Shoes implements Item {

    public void useItem() {
    	System.out.println("Use Shoes");
    }

}

class Gun implements Item {

    public void useItem() {
    	System.out.println("Use Gun");
    }
}

interface Item {
	
    void useItem();
    
}

public class Users {

    private Item item;
    
    Users() {};
    
    public setItem(Item item) {
    	this.item = item;
    }
    
    public useUsersItem() {
    	item.useItem();
    }
    
}

public class Main {

	public static void main(String[] args) {
    	
        // 의존성 주입을 통해 의존 요소들을 쉽게 갈아끼울 수 있다
    	
        Users user = new Users();
        
        user.setItem(new Potion());
        
        user.useUsersItem();
        
        user.setItem(new Shoes());
        
        user.useUsersItem();
        
        user.setItem(new Gun());
        
        user.useUsersItem();
  }
  
}   

위의 코드를 도식화하면 다음과 같다.

Users 객체에 있는 Item이라는 의존성Setter를 통해 주입하였고, 사용하고자 하는 Item을 바꾸고 싶다면 그 Item 객체를 생성하고 단순하게 주입하기만 하면 된다.

즉, Users Instance에 대한 Users의존성을 약화하는 방식으로, Potion / Shoes / Gun 객체를 통해 의존성을 주입하는 것이 취지이다.

의존성 주입IoC 의존성 역전 원칙 하에, 객체 간의 결합도를 느슨하게 하며, 유지 보수에 편리한 코드를 만들어준다.

Spring을 통한 의존성 주입

Spring에서 의존성을 주입하는 방식은 다음과 같다.

  • xml 혹은 Java Config 설정 파일
  • Annotation

위의 방식을 통해 컨테이너의 설정 정보 / Bean 객체 / 의존성 관계 정보를 입력한다.

Bean : Spring에서 선언되어 관리되고 있는 Instance

Container : Spring에서 IoC 원칙 내에서, Application 운용에 필요한 객체를 생성하고, 객체간의 의존성을 관리하기 위해 만든 요소

xml을 통한 의존성 주입

<-- appContext.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="potion" class="com.tutorial.spring.Potion"/>
    <bean id="shoes" class="com.tutorial.spring.Shoes"/>
    <bean id="gun" class="com.tutorial.spring.Gun"/>

    <bean id="gunUser" class="com.tutorial.spring.Player">
        <constructor-arg ref="gun"/>
    </bean>
    <bean id="shoesPlayer" class="com.tutorial.spring.Player">
        <property name="item" ref="shoes"/>
    </bean>
    <bean id="potionPlayer" class="com.tutorial.spring.Player">
        <property name="item" ref="potion"/>
    </bean>

</beans>

Spring은 위의 xml 설정 파일을 통해서 컨테이너 정보를 입력하고, 이 컨테이너가 관리할 Bean 객체들을 등록하도록 한다.

Bean을 생성할 때, IdClass 정보를 입력해주어야 한다.

  • Id : Bean의 식별자
  • Class : Spring 객체인 Bean의 클래스를 지정하는 속성

속성을 주입하는 방식에는 두 가지가 존재한다.

  • Constructor
  • Setter
<bean id="gunUser" class="com.tutorial.spring.Player">
        <constructor-arg ref="gun"/>
    </bean>

위 방식은 Constructor 를 통해 속성을 주입하는 방식으로, <constructor-arg> tag를 사용한다.

주입할 Bean의 객체 정보는 ref="[ID명]" 을 속성에 입력한다.

<bean id="potionPlayer" class="com.tutorial.spring.Player">
        <property name="item" ref="potion"/>
    </bean>

위 방식은 Setter 를 통해 속성을 주입하는 방식으로, <property> tag를 사용해야 하며, name 속성값은 호출하고자 하는 Method의 이름이어야 한다.

name 에는 변수명을 적어주면, Spring에서 name의 첫 글자를 대문자로 바꾸고, 앞에 set 키워드를 붙여 Set Method을 실행한다.

class Potion implements Item {

    public void useItem() {
    	System.out.println("Use Potion");
    }
    
}

class Shoes implements Item {

    public void useItem() {
    	System.out.println("Use Shoes");
    }

}

class Gun implements Item {

    public void useItem() {
    	System.out.println("Use Gun");
    }
}

interface Item {
	
    void useItem();
    
}

public class Users {

    private Item item;
    
    Users() {};
    
    public Users(Item item) {
    	super();
        this.item = item;
    }
    
    public setItem(Item item) {
    	this.item = item;
    }
    
    public Item getItem() {
    	return item;
    }
    
    public useUsersItem() {
    	item.useItem();
    }
    
}

import org.springframework.context.support.GenericXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:appContext.xml");

        User gunUser = ctx.getBean("gunUser", User.class);
        gunUser.useUsersItem();

        User shoesUser = ctx.getBean("shoesUser", User.class);
        shoesUser.useUsersItem();

        User potionUser = ctx.getBean("potionUser", User.class);
        potionUser.useUsersItem();
    }
    
}

GenericXmlApplicstionContext 는 xml 설정 파일을 통해 Spring 컨테이너를 생성해주는 객체이다. 이 객체를 통해서 Spring 컨테이너에 접근할 수 있다.

Spring 컨테이너의 Bean 객체에 접근하고 싶다면, getBean()을 사용한다. 이 때, getBean에 Class 정보를 입력하면 번거롭고 불필요한 Type-casting 을 하지 않아도 된다.

Java Config를 통한 의존성 주입

package hello.hellospring;

import hello.hellospring.repository.MemberRepository; 
import hello.hellospring.repository.MemoryMemberRepository; 
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

    @Configuration
    public class SpringConfig {
    
        @Bean
        public MemberService memberService() {
        
            return new MemberService(memberRepository());
            
        }
        
        @Bean
        public MemberRepository memberRepository() {
        
          return new MemoryMemberRepository();
          
        }
}

@Service, @Repository, @Autowired Annotation을 제거하고, @Bean Annotation을 통해 Config에 직접 Spring Bean을 등록한다.

@Configuration 은 Spring에서 설정한 Class라는 의미를 가진다. 위와 같이, Java Config로 설정을 하는 Class의 경우, @Configuration 가 붙어있어야 한다.

ApplicationContext 중에서 AnnotationConfigApplicationContext는 Java Config를 읽어들여 IoCDI 를 적용시킨다.

AnnotationConfigApplicationContext 는 설정 파일 중 @Bean 이 붙어 있는 Method들을 자동으로 실행하여 return되는 객체들을 기본적으로 Singleton으로 관리하게 된다.

@Autowired를 통한 의존성 주입

package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Controller;

  @Controller
  public class MemberController {

      private final MemberService memberService;
      
      @Autowired
      public MemberController(MemberService memberService) {
      
      	this.memberService = memberService; 
        
      }
}

Constructor@Autowired추가하면, Spring이 연관된 객체를 Spring Container에서 찾아서 넣어준다. 즉, 객체가 생성되는 시점에 해당 Spring Bean을 찾아서 주입한다.

생성자가 1개만 있을 경우, @Autowired 는 생략할 수 있다.

주의 : @Autowired를 통한 의존성 주입은 Spring이 관리하는 객체에서만 동작한다. Spring Bean으로 등록하지 않고, 사용자가 직접 생성한 객체에서는 동작하지 않는다.

컴포넌트 스캔 원리

@Component Annotation이 있으면, Spring Bean으로 자동 등록된다.

@Component포함하는 Annotation 역시 Spring Bean으로 자동 등록된다.

  • @Controller
  • @Service
  • @Repository

이와 같이 @Component Annotation으로 인해, Spring Bean이 자동 등록되는 것을 컴포넌트 스캔 원리라고 한다.

실무에서는 주로 정형화된 Controller, Service, Repository 같은 코드의 경우, 컴포넌트 스캔을 사용한다. 그리고 정형화되지 않거나, 상황에 따라 구현 Class를 변경해야 하는 경우, 설정을 통해 Spring Bean으로 등록한다.

0개의 댓글