뉴렉쳐 스프링 프레임워크 정리/Part1. DI

[Spring 개념정리] @Autowired 어노테이션을 이용한 DI

째로스 2023. 7. 14. 12:53

목표

xml 대신 @Autowired 어노테이션을 사용하여 DI 하는 방법을 알아본다

 

기존 xml DI 방식

<!-- setting.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
						http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
						http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
                        
    <bean id="exam" class="spring.di.entity.NewLecExam"/>
    <bean id="console" class="spring.di.ui.GridExamConsole">
    	<property name="exam" ref="exam"/>
    </bean>    
<beans>
public class Program {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml");
        
        ExamConsole console = (ExamConsole) context.getBean("console");
        console.print();
    }
}

exam과 console이라는 이름을 가진 객체를 DI하고,

context 라는 IoC 컨테이너를 통해 해당 객체들을 가져와 사용한 모습이다.

 

어노테이션을 사용한 DI 방식

public class GridExamConsole implements ExamConsole {
	
    private Exam exam;
	
    ...(생략)...

    @Autowired
    @Override
    public void setExam(Exam exam) {
        this.exam=exam;
    }
}
<!-- setting.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/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
						http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
                        
    <context:annotation-config/>
    <bean id="exam" class="spring.di.entity.NewLecExam"/>
    <bean id="console" class="spring.di.ui.GridExamConsole"/>
<beans>
public class Program {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml");
        
        ExamConsole console = (ExamConsole) context.getBean("console");
        console.print();
    }
}

먼저 xml 파일에 context 네임스페이스를 추가한 뒤 <context:annotation-config> 태그를 추가한다.

그래야만 xml이 프로그램에서 bean 객체에 필요한 Dependency가 있을 경우, @Autowired 어노테이션들을 찾아본 뒤 알맞은 객체를 DI를 해주기 때문이다.

 

해당 태그를 추가하면 console 객체를 생성할 때, 내부 필드로 Exam 클래스가 있어 객체 정의가 필요하므로

추적 후 GridExamConsole 클래스의 @Autowired가 붙은 setter를 동작시키면서 먼저 만들어놓은 exam이라는 id의 Dependency를 Injection한다.

 

그런데 여기서 아래와 같이 같은 Exam 클래스 객체를 2개 이상 IoC 컨테이너에 생성했을 때는 어떤 일이 발생할까?

<!-- setting.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:p="http://www.springframework.org/schema/p"
    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/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
						http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
                        
    <context:annotation-config/>
    <bean id="exam1" class="spring.di.entity.NewLecExam" p:kor="10" p:eng="20" p:math="30" p:com="40"/>
    <bean id="exam2" class="spring.di.entity.NewLecExam" p:kor="15" p:eng="25" p:math="35" p:com="45"/>
    <bean id="console" class="spring.di.ui.GridExamConsole"/>
<beans>
public class Program {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml");
        
        ExamConsole console = (ExamConsole) context.getBean("console");
        console.print();
    }
}

console이라는 id를 가진 객체가 생성될 때 exam이라는 dependency를 setter에 주입해줘야 하는데,

같은 클래스 객체가 dependency로 2개가 생성되어 있어 어떤 것을 주입해야할지 몰라 에러가 발생한다.

 

이런 문제를 해결하기 위해 나온 어노테이션이 @Qualifier다.

(exam의 id와는 상관없이 Qualifier해주지 않은 상태라면, 스프링은 클래스를 기준으로 DI를 시도한다.)

public class GridExamConsole implements ExamConsole {
	
    private Exam exam;
	
    ...(생략)...

    @Autowired
    @Qualifier("exam1")
    @Override
    public void setExam(Exam exam) {
        this.exam=exam;
    }
}

위와 같은 방법으로 setter에 Qualifier를 통해 특정 id를 명시하면, 해당 Dependency를 주입하면서 같은 클래스의 Dependency가 여러개 있어도 에러가 발생하지 않는다.

 

@Autowired가 위치할 수 있는 자리

1) setter 앞

 위에서 사용했으니 예시 생략

 

2) 생성자 매개변수 앞

public class GridExamConsole implements ExamConsole {
	
    private Exam exam;
    private Exam exam2;
    
    @Autowired
    public GridExamConsole(@Qualifier("exam1")Exam exam,@Qualifier("exam2")Exam exam2){
    	this.exam=exam;
        this.exam2=exam2;
    }
	
    ...(생략)...

    @Autowired
    @Qualifier("exam1")
    @Override
    public void setExam(Exam exam) {
        this.exam=exam;
    }
}

생성자에 @Autowired를 사용할 때는 setter와 달리 매개변수 앞에 사용한다.

이유는 위 코드에서처럼 매개변수에 2개 이상의 같은 Dependency가 주입되어야 할 수 있기 떄문이다.

이런 혼동을 사전에 방지하기 위해 매개변수 앞에만 사용할 수 있도록 설계되었다.

 

3) Dependency로 사용할 필드 앞

public class GridExamConsole implements ExamConsole {
	
    @Autowired
    @Qualifier("exam1")
    private Exam exam;
	
    ...(생략)...

    @Override
    public void setExam(Exam exam) {
        this.exam=exam;
    }
}

필드 앞에 @Autowired를 사용하면, 기본 생성자를 통해 Dependency가 생성된다.

 

@Autowired(required=false)

public class GridExamConsole implements ExamConsole {
	
    @Autowired(required=false)
    @Qualifier("exam1")
    private Exam exam;
	
    ...(생략)...
    
    @Override
    public void print(){
        if(exam==null)
            System.out.println("Dependency 생성 실패해서 null임");
        else
            System.out.println("Dependency 생성 성공하였음");
        }
}

required=false 옵션을 사용하면, 만약 Dependency 생성에 실패한 경우 null 값을 반환한다.

이를 위처럼 활용할 수도 있게 된다.