의존성 주입 - DI(Dependency injection)
스프링(Spring)

의존성 주입 - DI(Dependency injection)

반응형

스프링을 시작하면, 가장 먼저 배우게 되는 핵심 내용은 바로 DI인데요. Dependency injection으로 의존성 주입이라는 뜻입니다.


바로 뼈대를 만들어놓고, 세부적인 요소가 변경되어도 쉽게 수정할 수 있도록 설계한 프로그래밍 기법이라고 말할 수 있겠습니다.

스프링 컨테이너가 지원하는 핵심 개념 중 하나가 바로 의존성 주입입니다.


예를 들어볼게요.



클래스 A는 안에서 B를 생성해 가져오고, b의 a 메소드를 실행시키고 있습니다.


즉, '클래스 A는 클래스 B에 의존'하고 있다고 말할 수 있겠습니다. ( A has a B )


이는 상당히 번거로운 문제점을 가지고 있는데요.

만약 A에서 새로운 클래스인 C를 의존하고 싶어서 변경해야 한다고 생각해봅시다. 우리는 클래스 A안에서 B에 관련된 instance는 모두 지우고 새로 C로 작성해주어야 할 것입니다. 만약 큰 프로젝트에서 이런 수정사항이 한 두개가 아니면 어떨까요? 생각만 해도 끔찍합니다.


따라서 한 클래스 안에 의존 할 다른 클래스 instance를 생성할 필요없이, 스프링 컨테이너가 만들어 놓은 instance를 이용해 그냥 가져와 사용하도록 만들 것 입니다. ( xml 파일로 구성됩니다. )




실제 코드를 통해 예를 들면서 의존성 주입에 대해 설명하도록 하겠습니다.


1
2
3
4
5
6
7
public class TranspotationWalk {
    
    public void move() {
        System.out.println("도보로 이동 합니다.");
    }
}
 
cs


도보로 이동하겠다는 것을 알릴 클래스 파일을 하나 생성했습니다.


우리가 자바를 배우면서 이를 실행시키기 위해 main 클래스에서는 아래와 같이 구현했을겁니다.


1
2
3
4
5
6
7
8
public class MainClass {
 
    public static void main(String[] args) {
        
        TranspotationWalk transpotationWalk = new TranspotationWalk();
        transpotationWalk.move();
    }
}
cs


도보 이동에 해당하는 클래스를 생성해주고, 여기에 존재하는 move 메소드를 가져와 실행시켜주는 모습을 볼 수 있죠? 돌려보면 실행이 잘 될 겁니다.


그런데 만약, 도보 이동이 아니라 버스 이동이나 자전거 이동과 같은 클래스도 구현해야 한다면 그때마다 main 클래스에서 생성자를 만들고 메소드를 실행하는 과정을 반복해야 합니다. 이는 main 클래스가 메소드를 실행할 여러 클래스에게 의존성이 매우 크다는 걸 뜻하는데요.


이를 줄일 수 있는 방법으로 xml 파일을 통해 의존성을 낮추게 되겠습니다.


resources 안에 xml 파일을 생성하고 아래와 같이 작성합니다.


applicationCTX.xml


1
2
3
4
5
6
7
8
9
<?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="tWalk" class="testPjt01.TranspotationWalk"/>
 
</beans>
 
cs


bean을 활용해 프로젝트의 클래스를 가져와 id 값을 주면서 객체의 의존성을 설정해주고 있습니다.


이제 우리는 main 클래스에서 해당 클래스의 생성자를 만드는 것이 아니라 아래와 같이 구현이 가능합니다.


1
2
3
4
5
6
GenericXmlApplicationContext ctx 
= new GenericXmlApplicationContext("classpath:applicationContext.xml"); // 컨테이너
        
TranspotationWalk transpotationWalk = ctx.getBean("tWalk", TranspotationWalk.class);
transpotationWalk.move();
        
ctx.close();
cs


스프링의 context 모듈을 활용해 이처럼 구현하게 된다면, 개발자는 main 클래스 내에서 사용해야 할 클래스의 instance를 추가적으로 구현할 필요가 없게 됩니다. 왜냐하면 우리가 스프링 컨테이너의 xml 파일을 이용해서 클래스들의 bean을 생성하여 관리하고 있기 때문입니다.


TranspotationWalk transpotationWalk = new TranspotationWalk();


이제 이처럼 생성자를 필요할 때마다 만들어줄 필요가 없어졌습니다!


개발자는 단순히 해당 id를 통해 getBean() 메소드를 통해서 instance를 가져오기만 하면 되는 효율적인 코드로 바꿀 수 있는 것이 바로 DI입니다.




하나의 클래스만 가지고는 DI가 그렇게 효율적인건가? 라는 생각이 들게 됩니다. 하지만 실제 프로젝트 구조를 보면 충분히 느낄 수 있는데요.


만약 학생 정보를 저장하고 있는 시스템이 있다고 가정하겠습니다.

학생 데이터베이스에 해당하는 DAO클래스가 존재하는 상태에서, 학생 정보를 등록, 선택, 삭제하는 기능을 구현한다고 생각해봅시다.


이러한 메소드들에서는 DAO를 가져오기 위한 코드가 필요합니다. 즉, DAO 생성자를 코드 내에서 구현을 해야되는 것이죠.

이를 xml에서 constructor-arg태그를 통해서 만들어줄 수 있습니다.



이와 같은 방식으로 생성자를 이용한 의존성을 주입하는 방법을 구현할 수 있습니다.


이 밖에도 setter, List, Map 또한 객체 주입이 가능한데요. 간단하게만 정리하고 넘어가겠습니다.


setter : bean태그 안에 property를 통해 name과 value 값을 주는 것으로 주입

List : property 태그 안에 list 태그를 만들고 그 안에 해당 list가 가진 value를 주입

Map : property 태그 안에 map 태그를 만들고, entry로 나눠서 key와 value값으로 주입






의존 객체 자동 주입


우리가 객체를 주입하기 위해 xml에서 <constructor-org> 또는 <property> 태그를 명시했습니다.


하지만 자동 주입은, 이렇게 객체를 명시하지 않아도 스프링 컨테이너가 자동으로 필요한 의존 대상 객체를 찾아서 의존 대상 객체가 필요한 객체에 주입해 주는 기능을 말합니다.


크게 두 가지 기능이 있는데요. @Autowired와 @Resource 어노테이션입니다.



@Autowired : 주입하려는 객체의 '타입'이 일치하는 객체를 자동으로 주입


@Resource : 주입하려는 객체의 '이름'이 일치하는 객체를 자동으로 주입



이를 사용하면 우리가 xml상에서 <constructor-org> 또는 <property> 태그로 주입을 시키지 않아도 자동으로 매칭시켜줍니다.

상당히 편리한 기능으로, 실제 실무에서도 많이 사용한다고 하네요.


Autowired를 사용한 xml 파일입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
 
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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 
         http://www.springframework.org/schema/context 
         http://www.springframework.org/schema/context/spring-context.xsd">
 
    <context:annotation-config />
 
    <bean id="wordDao" class="com.word.dao.WordDao" >
        <!-- <qualifier value="usedDao"/> -->
    </bean>
    <bean id="wordDao2" class="com.word.dao.WordDao" />
    <bean id="wordDao3" class="com.word.dao.WordDao" />
    
    <bean id="registerService" class="com.word.service.WordRegisterServiceUseAutowired" />
    
    <bean id="searchService" class="com.word.service.WordSearchServiceUseAutowired" />
    
</beans>
cs


<context:annotation-config />를 통해서 Autowired로 자동 주입이 가능해집니다.


1
2
    @Autowired
    private WordDao wordDao;
cs


이와같이 필요한 메소드에 Dao를 불러오는 코드에 @Autowired를 지정해주면 됩니다.

resource도 마찬가지이구요. 둘의 차이는 Autowired는 스프링 설정 파일을 지원해주는 것이며, resource는 자바 파일을 지원해주는 차이입니다.


의존성 주입에 대한 개념에 대해서 어느정도 알아봤는데요. 마지막으로는 객체가 여러개일 때 발생할 수 있는 문제점에 대한 내용입니다.


만약 xml에 아래와 같이 여러가지 객체가 정의되어있다면 정상적으로 실행될까요?


<bean id="wordDao" class="com.word.dao.WordDao" />

<bean id="wordDao2" class="com.word.dao.WordDao" />

<bean id="wordDao3" class="com.word.dao.WordDao" />


@Autowired를 이용했을 때 결과는 실행 시 에러가 출력됩니다. id가 wordDao인거로 잘 될 것 같지만, 타입을 우선으로 하기에 같은 타입인 3가지가 모두 조건이 같아서 주입할 하나를 고르지 못하게 되는 것입니다.


따라서 Qualifier을 통해 id 값을 지정해주는 방법으로 해결해야 합니다.


1
2
3
@Autowired 
@Qualifier("usedDao"
private WordDao wordDao;
cs


이처럼 작성해두면 wordDao id를 가진 bean을 가져와 자동 주입이 이루어지게 됩니다.



반응형