안녕하세요 coding-knowjam입니다.
오늘은 의존성 주입에 사용하는 @Autowired, @Inject, @Resource에 대해서 각각에 대해 어떤 특징과 차이점이 있는지 알아보겠습니다.
예시로 사용할 클래스는 아래와 같습니다.
StreamingService interface
package com.nojam.coding.service;
public interface StreamingService {
public void streaming(String str);
}
MusicStreamingService class
package com.nojam.coding.service;
import org.springframework.stereotype.Service;
@Service
public class MusicStreamingService implements StreamingService {
@Override
public void streaming(String str) {
System.out.println("#### " + str + " : MusicStreamingService ####");
}
}
VideoStreamingService class
package com.nojam.coding.service;
import org.springframework.stereotype.Service;
@Service
public class VideoStreamingService implements StreamingService {
@Override
public void streaming(String str) {
System.out.println("#### " + str + " : VideoStreamingService ####");
}
}
1. @Autowired
@Autowired의 특징은 다음과 같습니다.
- Field, Setter Method, Constructor에 사용 가능
- 기본적으로 타입을 기준으로 의존성을 주입
- 동일한 타입의 빈이 여러 개 존재할 경우 기본적으로 참조 변수의 이름과 동일한 빈을 찾아서 주입
- 이름을 기준으로 의존성을 주입할 때 @Qualifier 사용해서 주입될 빈 지정 가능
- 동일한 타입의 빈이 여러 개 존재할 경우 @Primary을 사용해서 주입될 빈 지정 가능
일반적으로는 @Controller가 선언된 클래스에 service 빈을 주입하지만 제대로 주입되는지 매번 서버를 실행하기에는 번거로우므로 간단하게 테스트 코드를 통해 추가 설명을 드리겠습니다.
AutowiredTest Class
package com.nojam.coding;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.nojam.coding.service.MusicStreamingService;
import com.nojam.coding.service.VideoStreamingService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class AutowiredTest {
@Autowired
//타입을 기준으로 의존성이 주입됩니다.
MusicStreamingService music;
@Autowired
//타입을 기준으로 의존성이 주입됩니다.
VideoStreamingService video
@Test
public void autowiredTest() {
music.streaming("타입을 기준으로 빈 주입");
video.streaming("타입을 기준으로 빈 주입");
}
}
타입 별 빈이 1개씩만 존재할 경우 타입을 기준으로 의존성이 주입됩니다.
실행하면 아래와 같이 정상적으로 주입이 된 걸 볼 수 있습니다.
동일한 타입의 빈이 2개 이상 존재할 경우 타입으로는 구분이 안되므로 어떤 빈을 주입할 건지 명시를 해줘야 합니다.
명시를 해주기 위해 MusicStreamingService class에 빈 이름을 아래와 같이 지정해주겠습니다.
MusicStreamingService Class
package com.nojam.coding.service;
import org.springframework.stereotype.Service;
@Service("music")
public class MusicStreamingService implements StreamingService {
@Override
public void streaming(String str) {
System.out.println("#### " + str + " : MusicStreamingService ####");
}
}
테스트 코드를 작성해보겠습니다.
AutowiredTest Class
package com.nojam.coding;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.nojam.coding.service.MusicStreamingService;
import com.nojam.coding.service.StreamingService;
import com.nojam.coding.service.VideoStreamingService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class AutowiredTest {
// #### 타입 별로 빈이 1개만 존재할 경우 ####
/*
타입을 기준으로 의존성이 주입됩니다.
@Autowired
MusicStreamingService music;
//타입을 기준으로 의존성이 주입됩니다.
@Autowired
VideoStreamingService video;
*/
// #### 동일한 타입의 빈이 2개이상 존재 할 경우 ####
/*
타입만으로는 구분이 안되서 에러가 발생합니다.
@Autowired
StreamingService StreamingService;
*/
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Autowired
StreamingService videoStreamingService;
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Autowired
StreamingService music;
// @Qualifier("빈 이름") 애너테이션을 통해서 주입 될 빈을 명시합니다.
@Autowired
@Qualifier("music")
StreamingService streamingService;
@Test
public void autowiredTest() {
videoStreamingService.streaming("이름을 기준으로 빈 주입");
music.streaming("이름을 기준으로 빈 주입");
streamingService.streaming("이름을 기준으로 빈 주입");
}
}
실행을 하면 bean이름을 기준으로 주입이 된 걸 볼 수 있습니다.
동일한 타입의 빈이 2개 이상 존재할 경우 이름을 기준으로 특정 bean을 명시하는 방법 말고 @Primary을 통해 지정하는 방법도 있습니다.
VideoStreamingService Class에 @Primary을 아래와 같이 붙여주겠습니다.
VideoStreamingService Class
package com.nojam.coding.service;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Primary
@Service
public class VideoStreamingService implements StreamingService {
@Override
public void streaming(String str) {
System.out.println("#### " + str + " : VideoStreamingService ####");
}
}
테스트 코드를 작성해보겠습니다.
AutowiredTest Class
package com.nojam.coding;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.nojam.coding.service.MusicStreamingService;
import com.nojam.coding.service.StreamingService;
import com.nojam.coding.service.VideoStreamingService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class AutowiredTest {
// #### 타입 별로 빈이 1개만 존재할 경우 ####
/*
타입을 기준으로 의존성이 주입됩니다.
@Autowired
MusicStreamingService music;
//타입을 기준으로 의존성이 주입됩니다.
@Autowired
VideoStreamingService video;
*/
// #### 동일한 타입의 빈이 2개이상 존재 할 경우 ####
/*
타입만으로는 구분이 안되서 에러가 발생합니다.
@Autowired
StreamingService StreamingService;
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Autowired
StreamingService videoStreamingService;
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Autowired
StreamingService music;
// @Qualifier("빈 이름") 애너테이션을 통해서 주입 될 빈을 명시합니다.
@Autowired
@Qualifier("music")
StreamingService streamingService;
*/
// @Priamry 애너테이션이 붙은 클래스가 항상 우선 주입됩니다.
@Autowired
StreamingService streamingService2;
// 참조변수의 이름과 동일한 빈이 있어도 @Primary가 붙은 클래스가 우선 주입됩니다.
@Autowired
StreamingService music ;
@Test
public void autowiredTest() {
streamingService2.streaming("@Primary가 붙은 클래스 우선 주입");
music.streaming("이름을 기준으로 빈 주입");
}
}
@Primary 가 붙은 클래스가 존재하면 참조 변수의 이름과 동일한 빈이 존재해도 @Primary가 붙은 객체가 우선 주입되므로 이름으로 주입을 하고자 할 때는 @Qualifier를 붙여서 사용하셔야 합니다.
테스트 코드에서는 간단하게 필드 주입방법을 사용하였지만 실제 코드 작성을 하실 때는 생성자 주입을 권고드립니다.
이와 관련해서는 제가 작성한 글을 참고해주세요.
참고 글 - [Spring] @Autowired DI 정리 (feat. 왜 생성자 주입을 사용해야 하는가??)
2. @Resource
@Resource의 특징은 다음과 같습니다.
- Field, Setter Method에 사용 가능합니다. (생성자에는 사용 불가능)
- 기본적으로 참조 변수의 이름과 동일한 빈이 존재하면 해당 빈을 주입해줍니다.
- name속성을 사용해서 주입받을 빈을 지정할 수 있습니다.
- 이름으로 빈을 찾지 못하면 타입을 기준으로 의존성을 주입합니다.
- Java9 이후부터는 삭제되어서 사용할 수 없습니다.
@Autowired를 설명할 때 수정했던 코드들은 전부 처음 상태로 수정해주세요.
그럼 이어서 테스트 코드를 작성해보겠습니다.
ResourceTest Class
package com.nojam.coding;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.nojam.coding.service.MusicStreamingService;
import com.nojam.coding.service.StreamingService;
import com.nojam.coding.service.VideoStreamingService;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class ResourceTest {
//name속성을 사용해서 특정 빈을 주입합니다.
@Resource(name="musicStreamingService")
StreamingService StreamingService;
//name 속성이 생략 될 경우 참조변수의 이름과 일치하는 빈이 주입됩니다.
@Resource
StreamingService videoStreamingService;
//name 속성이 생략 될 경우 참조변수의 이름과 일치하는 빈이 주입됩니다.
@Resource
StreamingService musicStreamingService;
//참조변수의 이름과 일치하는 빈이 존재하지 않으면 타입으로 주입됩니다.
@Resource
VideoStreamingService video;
@Test
public void resourceTest() {
StreamingService.streaming("name속성을 사용해서 이름을 기준으로 주입");
videoStreamingService.streaming("name속성을 생략하고 이름을 기준으로 주입");
musicStreamingService.streaming("name속성을 생략하고 이름을 기준으로 주입");
video.streaming("타입으로 주입");
}
}
@Resource의 경우 Java8(JDK1.8)까지만 지원을 하고 Java9(JDK1.9)에서는 삭제되었습니다.
아직까지 실무에서는 Java 8을 많이 쓰고 계시겠지만 최근 11로도 많이 프로젝트를 진행하고 있으므로 @Resource을 사용하실 때는 유의하시길 바랍니다.
3. @Inject
@Inject 은 기본적으로 @Autowired와 동일하게 사용할 수 있으며 추가적으로 @Named을 같이 사용할 수 도 있습니다.
특징은 다음과 같습니다.
- Field, Setter Method, Constructor에 사용 가능
- 기본적으로 타입을 기준으로 의존성을 주입
- 동일한 타입의 빈이 여러 개 존재할 경우 기본적으로 참조 변수의 이름과 동일한 빈을 찾아서 주입
- 이름을 기준으로 의존성을 주입할 때 @Qualifier을 사용해서 주입될 빈 지정 가능
- 동일한 타입의 빈이 여러 개 존재할 경우 @Primary을 사용해서 주입될 빈 지정 가능
- 이름을 기준으로 의존성을 주입할 때 @Named을 사용해서 주입될 빈 지정 가능
@Inject은 앞서 테스트한 @Autowired의 테스트 코드에서 @Autowired를 @Inject으로 바꿔도 모두 정상적으로 동작하므로 @Named을 사용한 경우만 추가적으로 테스트 코드를 작성해 보겠습니다.
InjectTest Class
package com.nojam.coding;
import javax.inject.Inject;
import javax.inject.Named;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.nojam.coding.service.MusicStreamingService;
import com.nojam.coding.service.StreamingService;
import com.nojam.coding.service.VideoStreamingService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class InjectTest {
// #### 타입 별로 빈이 1개만 존재할 경우 ####
/*
타입을 기준으로 의존성이 주입됩니다.
@Inject
MusicStreamingService music;
//타입을 기준으로 의존성이 주입됩니다.
@Inject
VideoStreamingService video;
*/
// #### 동일한 타입의 빈이 2개이상 존재 할 경우 ####
/*
타입만으로는 구분이 안되서 에러가 발생합니다.
@Inject
StreamingService StreamingService;
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Inject
StreamingService videoStreamingService;
//참조변수의 이름과 일치하는 빈이 존재할 경우 해당 빈이 주입됩니다.
@Inject
StreamingService music;
// @Qualifier("빈 이름") 애너테이션을 통해서 주입 될 빈을 명시합니다.
@Inject
@Qualifier("music")
StreamingService streamingService;
// @Priamry 애너테이션이 붙은 클래스가 항상 우선 주입됩니다.
@Inject
StreamingService streamingService2;
// 참조변수의 이름과 동일한 빈이 있어도 @Primary가 붙은 클래스가 우선 주입됩니다.
@Inject
StreamingService music ;
*/
// @Named(value="빈 이름") 지정한 이름의 빈이 존재하면 주입됩니다.
@Inject
@Named(value="videoStreamingService")
StreamingService streamingService ;
@Test
public void injectTest() {
streamingService.streaming("이름을 기준으로 빈 주입");
}
}
실행하면 정상적으로 빈이 주입되는 걸 볼 수 있을 겁니다.
@Inject과 @Autowired는 서로 대체가 가능한데 무슨 차이점이 있을까요??
먼저 @Inject은 Java에서 지원하고 @Autowired는 SpringFramwork에서 지원을 합니다.
두 어노테이션의 FQCN (fully Qualified Class Name)을 보면 바로 아실 겁니다.
@Inject : javax.inject.Inject
@Autowired : org.springframework.beans.factory.annotation.Autowired
그래서 @Inject은 Spring에 종속적이지 않은 어노테이션이고, @Autowired는 Spring에서만 사용 가능합니다.
추가적으로 @Inject은 nullable 하게 만들 수가 없습니다. 무조건 빈을 주입받아야 하고 해당하는 빈이 없다면 에러가 발생합니다. (Spring 4.xxx , Java 8 이상 일 경우 Optional을 사용해서 할 순 있지만 어노테이션 자체에서 사용 가능한 속성은 없습니다.)
그러나 @Autowired은 @Autowired (required = false)와 같이 선언하면 참조 변수에 빈을 주입하지 못해도 에러가 발생하지 않고 null로 처리됩니다.
4. @Autowired vs @Resource vs Inject
앞서 확인했던 내용을 하나의 표로 정리하면 다음과 같습니다.
@Autowired | @Resource | @Inject | |
FQCN | org.springframework.beans. factory.annotation.Autowired |
javax.annotation.Resource | javax.inject.Inject |
Target | Field, Setter Method ,Constructor |
Field, Setter Method | Field, Setter Method ,Constructor |
의존성 주입순서 |
타입 -> 이름 | 이름 -> 타입 | 타입 -> 이름 |
빈 지정 방법 |
1. @Qualifier("빈 이름") 2. @Primary 사용 |
@Resource(name="빈 이름") | 1. @Qualifier("빈 이름") 2. @Primary 사용 3. @Named(value="빈 이름") |
Nullable | required=false 속성 사용 | X | X |
비고 | SpringFramwork 안에서만 사용 가능 |
Java 9 이후로 삭제 | 특정 프레임워크에 종속적이지 않음 |
지금까지 각 어노테이션에 대해서 알아보았습니다.
개인적인 견해로는 @Resource는 사용하지 않는 것이 좋을 것 같습니다.
저는 @Autowired를 선호하는데 Spring으로 프로젝트를 진행하다가 중간에 프레임워크를 바꾸는 경우는 거의 없을 것 같고, Java언어로 웹 개발하는데 Spring을 안 쓰는 경우도 좀 드물 것 같네요.
그리고 사실 처음부터 @Autowired를 써왔어서 이게 제일 편하기도 합니다.
@Autowired와 @Inject은 각자의 주관에 따라 사용을 하시면 될 것 같습니다.
'Spring' 카테고리의 다른 글
[SpringBoot] javax.validation이 import가 안되는 경우 (1) | 2021.09.04 |
---|---|
[Spring] MariaDB + HikariCP + MyBatis 설정하기 (0) | 2020.12.20 |
[Spring] @Autowired DI 정리 (feat. 왜 생성자 주입을 사용해야 하는가??) (0) | 2020.12.16 |
댓글