SpringFramework/BASIC

자동 스캔 명령태그 / 자동 의존성 주입 어노테이션 context:annotation-config @Autowired/@Qulifier/@Resource/@Inject

유혁스쿨 2020. 8. 29. 01:33
728x90
반응형

 

의존 객체 자동 주입이란?

 

먼저 재밌고 쉽게 이야기로 비유해보자면 호텔이 레스토랑이 필요하며 의존성관계를 가지고있습니다.

레스토랑은 세프가 필요하고 둘은 또 의존성관계를 가지고 있습니다.

 

실제로 이것에 대한 주입 명령을 xml설정파일에서 하고있습니다.

constructor-arg와 property를 사용하여 xml 설정 파일에서 컨테이너로 직접 주입명령을 등록 하고 있습니다.

 

레스토랑은 세프가 필요하니까 세터로 이렇~게 주입하면된다. 라고 설정을 기록하고 있지만, 우리가 자동주입이라는 기능을 사용하게 되면 bean 등록만 하면 됩니다.

호텔하나 등록해놓고 레스토랑,세프를 단순하게 '등록만'합니다.

 

"야 컨테이너에 호텔,세프,레스토랑 있거든? 너 딱보면 몰라? 호텔에 레스토랑 필요한거? 레스토랑에 세프 필요한거? 니가 보고 알아서 주입해~ 나한테 시키지말고~"  자동주입Automatic으로 객체를 주입

"야 그거 좀 뒤져바 박스안에있네~ 세프~ 넣어~! 그거 니가! 왜 나를 시키는거야 니가 넣어 알아서좀 해 스프링아"

 

 

 

스프링 MVC에서 핵심으로 쓰게될 기능입니다.

 

스프링 설정 파일에서 의존 객체를 주입할때<constructor-org> 또는<property>태그로 의존 대상 객체를 명시하지 않아도 스프링 컨테이너가자동으로 필요한 의존 대상 객체를 찾아서 의존 대상 객체가 필요한 객체에 주입해 주는 기능입니다.

구현방법은 @Autowired와@Resource어노테이션을이용해서 쉽게 구현할 수 있습니다.

 

 

코드를통해서 자동주입 기능을 살펴보겠습니다.

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


public class Printer { 	

	private Paper paper;//프린터를 하기위해 용지가 필요

	}
}

 

 

Paper클래스에 String 배열의 data변수를 선언하고 3개의 문자열 데이터를 저장해보겠습니다.

public class Paper {
	public String[] data = {
			"스프링 프레임워크",
			"자동 객체 주입",
			"Autowired는 객체의 타입을 검색하여 자동주입!"
			}; //프린터에게 이것을 인쇄하라고 작업하기
}

Printer에게 배열의 데이터들을 인쇄하라고 시켜보겠습니다.

 

Printer에게 기능을 넣어보겠습니다.

객체를 생성할때 객체에 들어온 매개값을통해 필드 멤버를 초기화해 줄 생성자와, forEach반복문을 통해서 용지에 써있는 문자열 데이터를 반복해서 출력해 보도록 코드를 작성 하겠습니다

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


public class Printer { 	

	private Paper paper;

    public Printer(Paper paper) {
		this.paper=paper;
	}
	public void showPaperInfo() {
		for(String info : paper.data) {
			System.out.println(info);
		}
	}
}

생성자와 paper객체에 있는 data배열에서 하나씩 꺼내 info에 변수에 넣고 반복해서 출력하도록 하는 코드를 구현했습니다.

 

테스트를 위한 메인클래스생성 합니다.

import java.util.Arrays;

import org.springframework.context.support.GenericXmlApplicationContext;

//7
public class MainClass {
	public static void main(String[] args) {
		
		Printer printer = new Printer(new Paper()); 
		printer.showPaperInfo(); //paper에 써있는 데이터들이 반복으로 출력된다.
		
	}
}

 

스프링의 방식이 아닌 Low한 방식으로 프린터 객체를 생성 합니다.

Printer객체에 Paper객체를 직접주입하는 방법으로 프린터를 만들 때 용지를 직접 넣어주는 형태를 구현합니다.

 

 

 

 

정상적으로 showPrinterInfo()메서드가 실행됩니다.

 

		Printer printer = new Printer(null); 

 

Printer는 Paper에 의존적입니다.

만약 Printer객체에 용지가 null이라면 NullPointerException 에러를 띄우게됩니다.

 

 

auto-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">

	<bean id="printer" class="com.spring.basic.ex04.Printer">
		<constructor-arg ref="paper"/>
	</bean>
	<bean id="paper" class="com.spring.basic.ex04.Paper"/>
	
</beans>

xml파일에 bean등록

생성자를 통하여 주입했기 때문에 생성자 태그인 constructor-arg태그를 사용하여 의존관계에 있는 Paper객체를 Printer객체에 주입 시켜줬습니다.

 

import java.util.Arrays;

import org.springframework.context.support.GenericXmlApplicationContext;

public class MainClass {
	public static void main(String[] args) {
		
		
		GenericXmlApplicationContext ct = 
				new GenericXmlApplicationContext("classpath:auto-config.xml");
				Printer printer = ct.getBean("printer",Printer.class);
				
		printer.showPaperInfo();
		ct.close();
		
	}
}

 

메인클래스 코드작성 완료

 

 

이번에는 자동의존성 주입을 해주는 코드를 구현해 보겠습니다.

우선 xml파일에서 Printer객체의 <bean> 태그안에 선언된 생성자 주입 코드인

<constructor-arg ref="paper">태그를 삭제하고 <bean>태그를 AutoClosing 하겠습니다

<?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="printer" class="com.spring.basic.ex04.Printer"/>
	<bean id="paper" class="com.spring.basic.ex04.Paper"/>
	
</beans>

 

paper생성자를 주입하지 않게 되었습니다

 

<?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">

    <context:annotation-config/>
	<bean id="printer" class="com.spring.basic.ex04.Printer"/>
	<bean id="paper" class="com.spring.basic.ex04.Paper"/>
	
</beans>
<context:annotation-config/> 코드는 자동스캔 명령에 대한 태그입니다.
해당 태그를 사용하기위해서는 <beans>태그에 코드를 추가하겠습니다.

//<beans xmlns 의 밑에 줄에 추가해 줍니다.
xmlns:context="http://www.springframework.org/schema/context" 

//<xsi:schemaLocation= 의 마지막줄에서 쌍따옴표와 닫는태그 "> 를 지우고 아래 코드를 추가해줍니다.
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-4.3.xsd">

 

xml 코드작성완료.

 

이제 Printer클래스로 돌아가 paper객체를 받아주는 부분에서 @Autowired 어노테이션을 선언해줘야합니다

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

public class Printer { 	

	private Paper paper;

	@Autowired
	public Printer(Paper paper) {
		this.paper=paper;
	}

	public void showPaperInfo() {
		for(String info : paper.data) {
			System.out.println(info);
		}
	}
}

 

생성자를 통해서 객체를 주입할것 이므로 생성자 위에 @Autowired를 선언 해 준 후

일괄 import 단축키인 Ctrl+Shift+O를 눌러줍니다.

 

Autowired에 의해서 객체가 주입되는것을 확인할 수 있습니다.

Autowired에 의해서 객체가 주입되는것을 확인할 수 있습니다.

 

단순히 빈을 따로 등록만 해 놓고서 xml에서 context에 대한 네임스페이스를 추가 한 후 <context:annotation-config/> 라는 태그를 사용하여 자동스캔을 하도록 구현 했습니다.

 

@Autowired:

타입을 기준으로 의존성을 주입합니다.

같은 타입 빈이 두개 이상 있을 경우 변수 이름으로 빈을 찾습니다.

Spring어노테이션 입니다.

 

 

주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입한다.

 

컨테이너(Container)
Paper <-- 의존성 관계 --- Printer
bean id : paper bean id : printer

 

현재 스프링 컨테이너에는 xml설정에 의해서 두개의 bean이 등록되어 있습니다.

하나는 Printer라는 클래스의 bean 하나는 Paper라는 클래스의 bean 이고, bean id는 각각 printer paper 라고 id를 부여 해줬습니다.

Printer가 Paper와 의존성 관계를 가지고 있지만 xml설정 파일에서는 의존성관계 설정을 해놓지 않았습니다

 

@Autowired

- 객체를 자동 주입 할 때 사용하는 어노테이션 입니다.

- 스캔명령을 통해 객체를 찾아 주입하는데 타입 이름으로 검색합니다.

- 타입을 찾아내지 못하면 이름(id속성값)을 통해 검색합니다.

- 생성자,필드,메서드에 적용 가능합니다.

 

Printer 클래스 내부에서 생성자쪽에 위와 같은 기능을 가지고 있는 @Autowired를 선언한 순간 컨테이너가 이렇게 반응합니다.

 

[Spring Container] : "프린터 너 있자나 너 객체가 필요할때 getBean()으로 꺼내올거지?"

"너 생성자쪽에 보니까 너 paper가 없으면 안될거같은데?"

"너 생성자를 통해서 주입하는 방법을선택하고있구나?"

"잠깐만 내가 박스(Container)좀 뒤져볼게"박스검색

"박스(Container)안에 페이퍼가있네?"자동주입

 

검색후: "야 너 내가 박스한번 뒤져보니까 페이퍼가잇구나?

너 딱보니까 생성자에서 페이퍼가 필요하네 내가 넣어주면되는거지?"

검색기준: 예를들어 빈아이디와 setPaper()메서드의 관례적인 이름인 'paper'가 서로 같으면 set메서드가 작동합니다.

 

 

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


public class Printer { 	

	private Paper paper;//프린터를 하기위해 용지가 필요

	@Autowired
	public void setPaper(Paper paper) {
		this.paper = paper;
		System.out.println("세터를통해서주입됬다");
		
	}

	//@Autowired
	public Printer(Paper paper) {
		this.paper=paper;
		System.out.println("생성자를통해서주입됬다");
	}
	public void showPaperInfo() {
		for(String info : paper.data) {//paper에있는 배열에서 하나씩 꺼내 info에넣고 출력반복
			System.out.println(info);
		}
	}
}

setter쪽에 autowired를 붙여버리면 프로퍼티로 paper객체를 넣어준적없지만

"너 객체를 세터를통해 넣어주는구나? 페이퍼 타입이 필요하네?

컨테이너 뒤져보니까 Paper타입의 빈을 발견했어

이거넣어줄겡~"

하고 객체를 자동으로 넣어주게 됩니다.

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


public class Printer {
 	
	@Autowired
	private Paper paper;//프린터를 하기위해 용지가 필요

	public void setPaper(Paper paper) {
		this.paper = paper;
		System.out.println("세터를통해서주입됬다");
		
	}

	//@Autowired
	public Printer(Paper paper) {
		this.paper=paper;
		System.out.println("생성자를통해서주입됬다");
	}
	public void showPaperInfo() {
		for(String info : paper.data) {//paper에있는 배열에서 하나씩 꺼내 info에넣고 출력반복
			System.out.println(info);
		}
	}
}

 

(Container가 Autowired를 확인 하고)

"야.. 너는 뭐... 너 setter()로 할꺼야?

여기 생성자에는 private가 붙어있네?

setter()로하겠구만뭘~ 그냥 setter()로 해~"

하고 setter()로 객체를 주입시켜 버립니다.


@Autowired어노테이션의 우선순위

@Autowired적용할때 생성자 혹은 set메서드를 자동으로 찾아갑니다.

이때 우선순위가 되는것은 생성자 입니다.

set메서드랑 생성자에 둘다 System.out.println()으로 setter(), 생성자 각각 문자열 출력을 지정해주고

분명 setter메서드()에 @Autowired를 했는데 출력은 생성자가 출력되버립니다

그래서 둘다 @Autowired 하고 출력하니까 둘다 출력 되긴 하는데 but, 출력 순서가 선언된 순서는 setter() 메서드가 먼저인데 생성자가 먼저출력됩니다.

따라서 생성자와 setter()메서드중 우선순위가 되는것은 생성자 입니다.


Autowired의 문제점

타입을 기반으로 검색할 때 만약 동일타입의 빈이 두개가 등록되있을때는 어떻게 될까요?

<?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-4.3.xsd">
	
 	<context:annotation-config/> <!-- 자동스캔명령에대한태그추가 -->
	<bean id="printer" class="com.spring.basic.ex04.Printer" />
	<bean id="paper1" class="com.spring.basic.ex04.Paper" />
	<bean id="paper2" class="com.spring.basic.ex04.Paper" />

	
</beans>

 

위와같이 동일한 타입의 빈을 선언해 주고 실행하게되면 아래와 같은 에러가 발생합니다

 

No qualifying bean of type 'co m.spring.basic.ex04.Paper' available: expected single matching bean but found 2: paper1,paper2

나는 페이퍼라는 빈을 한개만 매칭될줄 알았었는데 발견된게 두개다. paper1과 paper2중에 뭘 주입해야할지 모르겠다.

 

이러한 오류를 해결해 줄 수 있는@Qualifier("bean id")라는 어노테이션이 있습니다

 

@Qualifier("bean id")

Autowired를 사용할 때 동일타입의 빈이 여러개 있을 경우

어떤빈을 주입해야 하는지 선택해주는 추가 어노테이션 입니다.←@Autowired밑에 추가해주는 추가 어노테이션

 

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

public class Printer {
 	
	@Autowired
    @Qualifier("paper2")
	private Paper paper;

	public void setPaper(Paper paper) {
		this.paper = paper;
		System.out.println("세터를통해서주입됬다");
	}

	//@Autowired
	public Printer(Paper paper) {
		this.paper=paper;
		System.out.println("생성자를통해서주입됬다");
	}
	public void showPaperInfo() {
		for(String info : paper.data) {//paper에있는 배열에서 하나씩 꺼내 info에넣고 출력반복
			System.out.println(info);
		}
	}
}

에러가발생하네요

 

@Qualifier("bean id") 어노테이션을 쓰기 위해서는 기본 생성자가 반드시 필요합니다.

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

public class Printer {
 	
	@Autowired
    @Qualifier("paper2")
	private Paper paper;

    public Printer(){
		System.out.println("기본생성자 호출");
    }

    //아래 코드생략
}

 

기본 생성자와 맞물려서 돌아갑니다.

동일한 클래스타입을 가리키는 두가지 객체중 한가지만을 선택했을때에는 기본생성자를 통해 객체를 주입 하도록 설정 되어 있는것 같습니다.

우리가 자바의 기본문법에서 생성자가 오버로딩이 되면 자바 컴파일러는 기본생성자를 묵시적 제공하지 않습니다. 묵시적 기본생성자를 선언해줘야 할 때는 명시적인 생성자를 선언하여 필요한 필드만 초기화 하려 할 때 생략된 묵시적 기본생성자를 함께 선언해줘야만 합니다.

왠지 이것과 관련이 있을거 같지만.. 이것과 연관이 있는건지 까지는 잘 모르겠습니다..

우리가 메인 메서드에서 객체를 생성할 때 직접주입을 하지 않고 스프링의 Low한 방식의 코드를 작성하여 객체를 주입시켰기 때문입니다.

하지만 짧은 생각으로 추측해보면 확실하지 않지만(틀릴수있음) 우리가 컨테이너 방식으로 객체를 불러들여왔지만 내부적으로는 자바단에 선언된 Qualifier어노테이션이 다시 한번 직접주입 시켜버리는 행위를 하는게 아닐까... 생각도 듭니다. (객체를 직접 선택했으므로?)

음.. 좀더 깊게 추측하자면 bean이 xml에 등록되어있지만 qualifier에 의해서 자바단에서 한번 더 객체화 하기 때문에 생략된 기본생성자를 호출한것이 아닐까요...?


Autowired 혹은 Qualifier과 비슷한 기능을 해주는 다른 어노테이션들도 있습니다.

 

@Inject- Autowired와 완벽히 똑같은 기능을 구현합니다

Autowired는 스프링에서 지원해주고 Inject는 자바에서 지원해줍니다.

취향차이로 사용하면 될 것 같습니다

하지만 Inject는 pom.xml에서추가로 라이브러리를 다운받아줘야 사용이 가능해 집니다.

Inject도 Qualifier와 함께 사용 할 수 있습니다. (회사에서 상사가 뭘 쓰는지 보고 따라쓰면됩니다..ㅋㅋㅋㅋ)

 

@Resource

Autowired와 Qualifier를 함께써야할때 두줄로쓰기싫을때 사용합니다

자바 8 윗버전 11버전에서는 사용할수 없습니다. 하지만 아직까지 대세인 자바 8버전 에서는 사용이 가능합니다.

 

 

이번에는 @Resource 어노테이션을 활용하는 코드를 작성하겠습니다

먼저 Book이라는 클래스를 생성하겠습니다.

package com.spring.basic.ex04;

import javax.annotation.Resource;

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

public class Book {
    //@Autowired
    //@Qualifier("book")
	@Resource
	private Paper paper;

	public Paper getPaper() {
		return paper;
	}

	public void setPaper(Paper paper) {
		this.paper = paper;
	}
	
}

 

필드 멤버에 @Resouce어노테이션을 선언해 줍니다.

setPaper()와 getPaper()로 setter,getter메서드를 만들어 줍니다.

<?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-4.3.xsd">
	
 	<context:annotation-config/>
	<bean id="printer" class="com.spring.basic.ex04.Printer" />
	<bean id="paper1" class="com.spring.basic.ex04.Paper" />
	<bean id="paper2" class="com.spring.basic.ex04.Paper" />

	<bean id="book" class="com.spring.basic.ex04.Book" />

</beans>

 

 

 

import java.util.Arrays;

import org.springframework.context.support.GenericXmlApplicationContext;

public class MainClass {
	public static void main(String[] args) {

		GenericXmlApplicationContext ct = 
				new GenericXmlApplicationContext("classpath:auto-config.xml");
				Printer printer = ct.getBean("printer",Printer.class);
				
		printer.showPaperInfo();	

		System.out.println("=====================추가 코드====================");
		System.out.println("@Resource어노테이션을 통한 예제 : 책내용 확인");
		Book book = ct.getBean("book",Book.class);
		String datas = Arrays.toString(book.getPaper().data);
		System.out.println(datas);
		ct.close();
		
	}
}

 

Book객체를 getBean()한 후 Arrays.toString()메서드로 book배열에있는@!#$@%@$.....@#%@#...

 

자! 오랜만에 코드리딩을 한번 해보겠습니다.

book.getPaper()는 Book클래스에 선언되어있는 참조변수 paper 입니다

앞선 코드 ct.getBean() 메서드에 의해 객체가 생성되었으며 setter()가 자동으로 동작하여 생성된 객체 Book클래스의 paper 참조변수에 주입 시켜줬습니다.←객체 자동 주입 (자동의존성주입)

getter()를 통해 Book클래스에 선언된 Paper클래스 타입에는 객체가 주입되어 있으므로 paper객체를 반환 받게 된것 입니다.

따라서 book.getPaper()는 paper객체를 반환받은 것이며

book.getPaper().data는 반환받은 paper객체를 통해 data변수에 접근한것 입니다. ← paper.data;

Paper클래스에 있는 data배열을 paper객체를 통해 접근한것입니다.

 

 

실행 해 보면 정상적으로 작동이 됩니다.

 

 

마지막으로 @Resource 어노테이션에 대해 간단히 정리해보겠습니다.

 

@Resource

- 빈을 자동주입하는 어노테이션입니다.

- 필드, 메서드에만 적용이 가능하며 생성자에는 적용 불가능.

- 네임속성을 통해 특정 bean의 id를 지정할 수 있습니다.

@Resource어노테이션은 기본적으로 생성자 주입은 안됩니다.

 

 

 

728x90
반응형